Skip to content

Commit 17337cb

Browse files
unknownunknown
authored andcommitted
Added ability to create Custom Query Builders and Select for Update in SQL Server
1 parent 02866af commit 17337cb

5 files changed

Lines changed: 463 additions & 0 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using System.Collections.Generic;
2+
3+
namespace Simple.Data.Ado
4+
{
5+
public interface ICustomQueryBuilder
6+
{
7+
ICommandBuilder Build(AdoAdapter adapter, int bulkIndex, SimpleQuery query, out IEnumerable<SimpleQueryClauseBase> unhandledClauses);
8+
}
9+
}
Lines changed: 378 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,378 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using Simple.Data.Ado.Schema;
5+
6+
namespace Simple.Data.Ado
7+
{
8+
public abstract class QueryBuilderBase
9+
{
10+
protected readonly SimpleReferenceFormatter _simpleReferenceFormatter;
11+
protected readonly AdoAdapter _adoAdapter;
12+
protected readonly int _bulkIndex;
13+
protected readonly DatabaseSchema _schema;
14+
15+
protected ObjectName _tableName;
16+
protected Table _table;
17+
protected SimpleQuery _query;
18+
protected SimpleExpression _whereCriteria;
19+
protected SimpleExpression _havingCriteria;
20+
protected IList<SimpleReference> _columns;
21+
protected CommandBuilder _commandBuilder;
22+
23+
protected QueryBuilderBase(AdoAdapter adapter) : this(adapter, -1)
24+
{
25+
}
26+
27+
protected QueryBuilderBase(AdoAdapter adapter, int bulkIndex) : this(adapter, bulkIndex, null)
28+
{
29+
30+
}
31+
32+
protected QueryBuilderBase(AdoAdapter adapter, int bulkIndex, IFunctionNameConverter functionNameConverter)
33+
{
34+
_adoAdapter = adapter;
35+
_bulkIndex = bulkIndex;
36+
_schema = _adoAdapter.GetSchema();
37+
_commandBuilder = new CommandBuilder(_schema, _bulkIndex);
38+
_simpleReferenceFormatter = new SimpleReferenceFormatter(_schema, _commandBuilder, functionNameConverter);
39+
}
40+
41+
public abstract ICommandBuilder Build(SimpleQuery query, out IEnumerable<SimpleQueryClauseBase> unhandledClauses);
42+
43+
protected virtual void SetQueryContext(SimpleQuery query)
44+
{
45+
_query = query;
46+
_tableName = _schema.BuildObjectName(query.TableName);
47+
_table = _schema.FindTable(_tableName);
48+
var selectClause = _query.Clauses.OfType<SelectClause>().SingleOrDefault();
49+
if (selectClause != null)
50+
{
51+
if (selectClause.Columns.OfType<AllColumnsSpecialReference>().Any())
52+
{
53+
_columns = ExpandAllColumnsReferences(selectClause.Columns).ToArray();
54+
}
55+
else
56+
{
57+
_columns = selectClause.Columns.ToArray();
58+
}
59+
}
60+
else
61+
{
62+
_columns = _table.Columns.Select(c => ObjectReference.FromStrings(_table.Schema, _table.ActualName, c.ActualName)).ToArray();
63+
}
64+
65+
HandleWithClauses();
66+
67+
_whereCriteria = _query.Clauses.OfType<WhereClause>().Aggregate(SimpleExpression.Empty,
68+
(seed, where) => seed && where.Criteria);
69+
_havingCriteria = _query.Clauses.OfType<HavingClause>().Aggregate(SimpleExpression.Empty,
70+
(seed, having) => seed && having.Criteria);
71+
72+
_commandBuilder.SetText(GetSelectClause(_tableName));
73+
}
74+
75+
protected IEnumerable<SimpleReference> ExpandAllColumnsReferences(IEnumerable<SimpleReference> columns)
76+
{
77+
foreach (var column in columns)
78+
{
79+
var allColumns = column as AllColumnsSpecialReference;
80+
if (ReferenceEquals(allColumns, null)) yield return column;
81+
else
82+
{
83+
foreach (var allColumn in _schema.FindTable(allColumns.Table.GetName()).Columns)
84+
{
85+
yield return new ObjectReference(allColumn.ActualName, allColumns.Table);
86+
}
87+
}
88+
}
89+
}
90+
91+
protected virtual void HandleWithClauses()
92+
{
93+
var withClauses = _query.Clauses.OfType<WithClause>().ToList();
94+
var relationTypeDict = new Dictionary<ObjectReference, RelationType>();
95+
if (withClauses.Count > 0)
96+
{
97+
foreach (var withClause in withClauses)
98+
{
99+
if (withClause.ObjectReference.GetOwner().IsNull())
100+
{
101+
HandleWithClauseUsingAssociatedJoinClause(relationTypeDict, withClause);
102+
}
103+
else
104+
{
105+
if (withClause.Type == WithType.NotSpecified)
106+
{
107+
InferWithType(withClause);
108+
}
109+
HandleWithClauseUsingNaturalJoin(withClause, relationTypeDict);
110+
}
111+
}
112+
_columns =
113+
_columns.OfType<ObjectReference>()
114+
.Select(c => IsCoreTable(c.GetOwner()) ? c : AddWithAlias(c, relationTypeDict[c.GetOwner()]))
115+
.ToArray();
116+
}
117+
}
118+
119+
protected void InferWithType(WithClause withClause)
120+
{
121+
var objectReference = withClause.ObjectReference;
122+
while (!ReferenceEquals(objectReference.GetOwner(), null))
123+
{
124+
var toTable = _schema.FindTable(objectReference.GetName());
125+
var fromTable = _schema.FindTable(objectReference.GetOwner().GetName());
126+
if (_schema.GetRelationType(fromTable.ActualName, toTable.ActualName) == RelationType.OneToMany)
127+
{
128+
withClause.Type = WithType.Many;
129+
return;
130+
}
131+
objectReference = objectReference.GetOwner();
132+
}
133+
}
134+
135+
protected void HandleWithClauseUsingAssociatedJoinClause(Dictionary<ObjectReference, RelationType> relationTypeDict, WithClause withClause)
136+
{
137+
var joinClause =
138+
_query.Clauses.OfType<JoinClause>().FirstOrDefault(
139+
j => j.Table.GetAliasOrName() == withClause.ObjectReference.GetAliasOrName());
140+
if (joinClause != null)
141+
{
142+
_columns =
143+
_columns.Concat(
144+
_schema.FindTable(joinClause.Table.GetName()).Columns.Select(
145+
c => new ObjectReference(c.ActualName, joinClause.Table)))
146+
.ToArray();
147+
relationTypeDict[joinClause.Table] = WithTypeToRelationType(withClause.Type, RelationType.OneToMany);
148+
}
149+
}
150+
151+
protected void HandleWithClauseUsingNaturalJoin(WithClause withClause, Dictionary<ObjectReference, RelationType> relationTypeDict)
152+
{
153+
relationTypeDict[withClause.ObjectReference] = WithTypeToRelationType(withClause.Type, RelationType.None);
154+
_columns =
155+
_columns.Concat(
156+
_schema.FindTable(withClause.ObjectReference.GetName()).Columns.Select(
157+
c => new ObjectReference(c.ActualName, withClause.ObjectReference)))
158+
.ToArray();
159+
}
160+
161+
protected static RelationType WithTypeToRelationType(WithType withType, RelationType defaultRelationType)
162+
{
163+
switch (withType)
164+
{
165+
case WithType.One:
166+
return RelationType.ManyToOne;
167+
case WithType.Many:
168+
return RelationType.OneToMany;
169+
default:
170+
return defaultRelationType;
171+
}
172+
}
173+
174+
protected bool IsCoreTable(ObjectReference tableReference)
175+
{
176+
if (ReferenceEquals(tableReference, null)) throw new ArgumentNullException("tableReference");
177+
if (!string.IsNullOrWhiteSpace(tableReference.GetAlias())) return false;
178+
return _schema.FindTable(tableReference.GetName()) == _table;
179+
}
180+
181+
protected ObjectReference AddWithAlias(ObjectReference c, RelationType relationType = RelationType.None)
182+
{
183+
if (relationType == RelationType.None)
184+
relationType = _schema.GetRelationType(c.GetOwner().GetOwner().GetName(), c.GetOwner().GetName());
185+
if (relationType == RelationType.None) throw new InvalidOperationException("No Join found");
186+
return c.As(string.Format("__with{0}__{1}__{2}",
187+
relationType == RelationType.OneToMany
188+
? "n"
189+
: "1", c.GetOwner().GetAliasOrName(), c.GetName()));
190+
}
191+
192+
protected virtual void HandleJoins()
193+
{
194+
if (_whereCriteria == SimpleExpression.Empty && _havingCriteria == SimpleExpression.Empty
195+
&& (!_query.Clauses.OfType<JoinClause>().Any())
196+
&& (_columns.All(r => (r is CountSpecialReference)))) return;
197+
198+
var joiner = new Joiner(JoinType.Inner, _schema);
199+
200+
string dottedTables = RemoveSchemaFromQueryTableName();
201+
202+
var fromTable = dottedTables.Contains('.')
203+
? joiner.GetJoinClauses(_tableName, dottedTables.Split('.').Reverse())
204+
: Enumerable.Empty<string>();
205+
206+
var joinClauses = _query.Clauses.OfType<JoinClause>().ToArray();
207+
var fromJoins = joiner.GetJoinClauses(joinClauses, _commandBuilder);
208+
209+
var fromCriteria = joiner.GetJoinClauses(_tableName, _whereCriteria);
210+
211+
var fromHavingCriteria = joiner.GetJoinClauses(_tableName, _havingCriteria);
212+
213+
var fromColumnList = _columns.Any(r => !(r is SpecialReference))
214+
? GetJoinClausesFromColumnList(joinClauses, joiner)
215+
: Enumerable.Empty<string>();
216+
217+
var joinList = fromTable.Concat(fromJoins).Concat(fromCriteria).Concat(fromHavingCriteria).Concat(fromColumnList).Select(s => s.Trim()).Distinct().ToList();
218+
219+
var leftJoinList = joinList.Where(s => s.StartsWith("LEFT ", StringComparison.OrdinalIgnoreCase)).ToList();
220+
221+
foreach (var leftJoin in leftJoinList)
222+
{
223+
if (joinList.Any(s => s.Equals(leftJoin.Substring(5), StringComparison.OrdinalIgnoreCase)))
224+
{
225+
joinList.Remove(leftJoin);
226+
}
227+
}
228+
229+
var joins = string.Join(" ", joinList);
230+
231+
if (!string.IsNullOrWhiteSpace(joins))
232+
{
233+
_commandBuilder.Append(" " + joins);
234+
}
235+
}
236+
237+
protected IEnumerable<string>
238+
GetJoinClausesFromColumnList(IEnumerable<JoinClause> joinClauses, Joiner joiner)
239+
{
240+
return joiner.GetJoinClauses(_tableName, GetObjectReferences(_columns)
241+
.Where(o => !joinClauses.Any(j => ObjectReferenceIsInJoinClause(j, o))), JoinType.Outer);
242+
243+
}
244+
245+
protected static bool ObjectReferenceIsInJoinClause(JoinClause clause, ObjectReference reference)
246+
{
247+
return reference.GetOwner().GetAliasOrName().Equals(clause.Table.GetAliasOrName());
248+
}
249+
250+
protected IEnumerable<ObjectReference> GetObjectReferences(IEnumerable<SimpleReference> source)
251+
{
252+
var list = source.ToList();
253+
foreach (var objectReference in list.OfType<ObjectReference>())
254+
{
255+
yield return objectReference;
256+
}
257+
258+
foreach (var objectReference in list.OfType<FunctionReference>().Select(fr => fr.Argument).OfType<ObjectReference>())
259+
{
260+
yield return objectReference;
261+
}
262+
}
263+
264+
protected string RemoveSchemaFromQueryTableName()
265+
{
266+
return _query.TableName.StartsWith(_table.Schema + '.', StringComparison.InvariantCultureIgnoreCase)
267+
? _query.TableName.Substring(_query.TableName.IndexOf('.') + 1)
268+
: _query.TableName;
269+
}
270+
271+
protected virtual void HandleQueryCriteria()
272+
{
273+
if (_whereCriteria == SimpleExpression.Empty) return;
274+
_commandBuilder.Append(" WHERE " + new ExpressionFormatter(_commandBuilder, _schema).Format(_whereCriteria));
275+
}
276+
277+
protected virtual void HandleHavingCriteria()
278+
{
279+
if (_havingCriteria == SimpleExpression.Empty) return;
280+
_commandBuilder.Append(" HAVING " + new ExpressionFormatter(_commandBuilder, _schema).Format(_havingCriteria));
281+
}
282+
283+
protected virtual void HandleGrouping()
284+
{
285+
if (_havingCriteria == SimpleExpression.Empty && !_columns.OfType<FunctionReference>().Any(f => f.IsAggregate)) return;
286+
287+
var groupColumns =
288+
GetColumnsToSelect(_table).Where(c => (!(c is FunctionReference)) || !((FunctionReference)c).IsAggregate).ToList();
289+
290+
if (groupColumns.Count == 0) return;
291+
292+
_commandBuilder.Append(" GROUP BY " + string.Join(",", groupColumns.Select(_simpleReferenceFormatter.FormatColumnClauseWithoutAlias)));
293+
}
294+
295+
protected virtual void HandleOrderBy()
296+
{
297+
if (!_query.Clauses.OfType<OrderByClause>().Any()) return;
298+
299+
var orderNames = _query.Clauses.OfType<OrderByClause>().Select(ToOrderByDirective);
300+
_commandBuilder.Append(" ORDER BY " + string.Join(", ", orderNames));
301+
}
302+
303+
protected string ToOrderByDirective(OrderByClause item)
304+
{
305+
string name;
306+
if (_columns.Any(r => (!string.IsNullOrWhiteSpace(r.GetAlias())) && r.GetAlias().Equals(item.Reference.GetName())))
307+
{
308+
name = item.Reference.GetName();
309+
}
310+
else
311+
{
312+
name = _table.FindColumn(item.Reference.GetName()).QualifiedName;
313+
}
314+
315+
var direction = item.Direction == OrderByDirection.Descending ? " DESC" : string.Empty;
316+
return name + direction;
317+
}
318+
319+
protected virtual string GetSelectClause(ObjectName tableName)
320+
{
321+
var table = _schema.FindTable(tableName);
322+
string template = _query.Clauses.OfType<DistinctClause>().Any()
323+
? "select distinct {0} from {1}"
324+
: "select {0} from {1}";
325+
return string.Format(template,
326+
GetColumnsClause(table),
327+
table.QualifiedName);
328+
}
329+
330+
protected virtual string GetColumnsClause(Table table)
331+
{
332+
if (_columns != null && _columns.Count == 1 && _columns[0] is SpecialReference)
333+
{
334+
return FormatSpecialReference((SpecialReference)_columns[0]);
335+
}
336+
337+
return string.Join(",", GetColumnsToSelect(table).Select(_simpleReferenceFormatter.FormatColumnClause));
338+
}
339+
340+
protected static string FormatSpecialReference(SpecialReference reference)
341+
{
342+
if (reference.GetType() == typeof(CountSpecialReference)) return "COUNT(*)";
343+
if (reference.GetType() == typeof(ExistsSpecialReference)) return "DISTINCT 1";
344+
throw new InvalidOperationException("SpecialReference type not recognised.");
345+
}
346+
347+
protected IEnumerable<SimpleReference> GetColumnsToSelect(Table table)
348+
{
349+
if (_columns != null && _columns.Count > 0)
350+
{
351+
return _columns;
352+
}
353+
else
354+
{
355+
return table.Columns.Select(c => ObjectReference.FromStrings(table.Schema, table.ActualName, c.ActualName));
356+
}
357+
}
358+
359+
protected string FormatGroupByColumnClause(SimpleReference reference)
360+
{
361+
var objectReference = reference as ObjectReference;
362+
if (!ReferenceEquals(objectReference, null))
363+
{
364+
var table = _schema.FindTable(objectReference.GetOwner().GetName());
365+
var column = table.FindColumn(objectReference.GetName());
366+
return string.Format("{0}.{1}", table.QualifiedName, column.QuotedName);
367+
}
368+
369+
var functionReference = reference as FunctionReference;
370+
if (!ReferenceEquals(functionReference, null))
371+
{
372+
return FormatGroupByColumnClause(functionReference.Argument);
373+
}
374+
375+
throw new InvalidOperationException("SimpleReference type not supported.");
376+
}
377+
}
378+
}

0 commit comments

Comments
 (0)