forked from JocaPC/sql-server-rest-api
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathQueryBuilder.cs
200 lines (173 loc) · 7.09 KB
/
QueryBuilder.cs
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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
// Copyright (c) Jovan Popovic. All Rights Reserved.
// Licensed under the BSD License. See LICENSE.txt in the project root for license information.
using System.Collections;
using System.Data.SqlClient;
using System.Text;
namespace SqlServerRestApi
{
public static class QueryBuilder
{
public static SqlCommand Build(QuerySpec spec, TableSpec table)
{
SqlCommand res = new SqlCommand();
StringBuilder sql = new StringBuilder();
BuildSelectFromClause(spec, table, sql);
// We should build WHERE clause even if we need just a count.
// Client may need count of filtered rows.
BuildWherePredicate(spec, res, sql, table);
if (!spec.count)
{
// Don't need ORDER BY for count
BuildOrderByClause(spec, sql);
// Don't need pagination for count
BuildOffsetFetchClause(spec, sql);
}
res.CommandText = sql.ToString();
return res;
}
private static void BuildSelectFromClause(QuerySpec spec, TableSpec table, StringBuilder sql)
{
sql.Append("SELECT ");
if (spec.count) {
sql.Append("CAST(count(*) as nvarchar(50)) ");
}
else
{
if (spec.top != 0 && spec.skip == 0)
sql.Append("TOP ").Append(spec.top).Append(" ");
sql.Append(spec.select ?? table.columnList ?? "*");
}
sql.Append(" FROM ");
sql.Append(table.FullName);
}
/// <summary>
/// co11 LIKE @kwd OR col2 LIKE @kwd ...
/// OR
/// (col1 LIKE @srch1 AND col2 LIKE srch2 ...)
/// </summary>
/// <param name="spec"></param>
/// <param name="res"></param>
private static void BuildWherePredicate(QuerySpec spec, SqlCommand res, StringBuilder sql, TableSpec table)
{
bool isWhereClauseAdded = false;
// If there is a global search by keyword in JQuery Datatable or $search param in OData add this parameter.
if ( !string.IsNullOrEmpty(spec.keyword) )
{
var p = new SqlParameter("kwd", System.Data.SqlDbType.NVarChar, 4000);
p.Value = "%" + spec.keyword + "%";
res.Parameters.Add(p);
}
// If there are some literals that are transformed to parameters add them in command.
if (spec.parameters != null && spec.parameters.Count > 0)
{
foreach (var parameter in spec.parameters)
{
res.Parameters.Add(parameter);
}
}
// If there are filters per columns add them as parameters
if (spec.columnFilter != null && spec.columnFilter.Count > 0)
{
foreach (DictionaryEntry entry in spec.columnFilter)
{
if (string.IsNullOrEmpty(entry.Value.ToString()))
continue;
var p = new SqlParameter(entry.Key.ToString(), System.Data.SqlDbType.NVarChar, 4000);
p.Value = "%"+entry.Value+"%";
res.Parameters.Add(p);
}
}
if ( !string.IsNullOrEmpty(spec.predicate) )
{
// This is T-SQL predicate that is provided via Url (e.g. using OData $filter clause)
sql.Append(" WHERE (").Append(spec.predicate).Append(")");
isWhereClauseAdded = true;
}
if (!string.IsNullOrEmpty(spec.keyword) )
{
if (!isWhereClauseAdded)
{
sql.Append(" WHERE (");
isWhereClauseAdded = true;
}
else
{
sql.Append(" OR (");
}
bool isFirstColumn = true;
foreach (var column in table.columns)
{
if (!isFirstColumn)
{
sql.Append(" OR ");
}
sql.Append("(").Append(column.Name).Append(" like @kwd)");
isFirstColumn = false;
}
sql.Append(" ) "); // Add closing ) for WHERE ( or OR ( that is added in this block
}
// Add filter predicates for individual columns.
if (spec.columnFilter != null && spec.columnFilter.Count > 0)
{
bool isFirstColumn = true, isWhereClauseAddedInColumnFiler = false;
foreach (DictionaryEntry entry in spec.columnFilter)
{
if (!string.IsNullOrEmpty(entry.Value.ToString())) {
if (isFirstColumn)
{
if (!isWhereClauseAdded)
{
sql.Append(" WHERE (");
}
else
{
sql.Append(" OR (");
}
isWhereClauseAddedInColumnFiler = true;
} else
{
sql.Append(" AND ");
}
sql.Append("(").Append(entry.Key.ToString()).Append(" LIKE @").Append(entry.Key.ToString()).Append(")");
isFirstColumn = false;
}
}
if (isWhereClauseAddedInColumnFiler)
{
sql.Append(")");
}
}
}
private static void BuildOrderByClause(QuerySpec spec, StringBuilder sql)
{
if (spec.order != null && spec.order.Count > 0)
{
bool first = true;
foreach (DictionaryEntry entry in spec.order)
{
if (first)
{
sql.Append(" ORDER BY ");
first = false;
}
else
sql.Append(" , ");
sql.Append(entry.Key.ToString()).Append(" ").Append(entry.Value.ToString());
}
}
}
private static void BuildOffsetFetchClause(QuerySpec spec, StringBuilder sql)
{
if (spec.top == 0 && spec.skip == 0)
return; // Nothing to generate
if (spec.top != 0 && spec.skip == 0)
return; // This case is covered with TOP
// At this point we know that spec.skip is != null
if (spec.order == null || spec.order.Keys.Count == 0)
sql.Append(" ORDER BY 1 "); // Add mandatory order by clause if it is not yet there.
sql.AppendFormat(" OFFSET {0} ROWS ", spec.skip);
if (spec.top != 0)
sql.AppendFormat(" FETCH NEXT {0} ROWS ONLY ", spec.top);
}
}
}