1 | package org.sqlorm.querybuilder; |
2 | |
3 | import java.util.ArrayList; |
4 | import java.util.List; |
5 | |
6 | import org.sqlorm.metadatadumper.IColumnName; |
7 | import org.sqlorm.metadatadumper.ITableName; |
8 | |
9 | import spiffy.core.lang.StringHelper; |
10 | |
11 | /** |
12 | * A class to easily create dynamic SELECT SQL |
13 | * |
14 | * @author kasper graversen |
15 | * @extends IPositionSelectFrom because you may put a SELECT..FROM..WHERE in a FROM part of a query |
16 | */ |
17 | public class SelectBuilder implements ItfSelectBuilder { |
18 | |
19 | static final String indentDelta = " "; |
20 | |
21 | private final List<IPositionSelectColumn> selectColumns = new ArrayList<IPositionSelectColumn>(); |
22 | private final List<IPositionSelectFrom> froms = new ArrayList<IPositionSelectFrom>(); |
23 | private final SurogateAndOrListSelectBuilder wheres = new SurogateAndOrListSelectBuilder(this, new AndOrList()); |
24 | private final List<String> groupByTableNames = new ArrayList<String>(); |
25 | private final SurogateAndOrListSelectBuilder havings = new SurogateAndOrListSelectBuilder(this, new AndOrList()); |
26 | private final OrderByList orderBys = new OrderByList(); |
27 | |
28 | private boolean isDistinct = false; |
29 | |
30 | /** field used when this select-object is a sub-select, e.g. in a from clause where it can be aliased */ |
31 | private String selectAlias = ""; |
32 | |
33 | /** |
34 | * if true, do not add "()" around the entries of the list |
35 | */ |
36 | private boolean isNestedSelect = false; |
37 | |
38 | public SelectBuilder() { |
39 | wheres.internal_setIsOutermostList(); // avoid "()" in the where-block |
40 | havings.internal_setIsOutermostList(); // avoid "()" in the having-block |
41 | } |
42 | |
43 | private <T> T addList(final T expr, final List<T> list) { |
44 | T result; |
45 | final int foundPos = list.indexOf(expr); |
46 | if(foundPos > -1) { |
47 | result = list.get(foundPos); |
48 | } |
49 | else { |
50 | list.add(expr); |
51 | result = expr; |
52 | } |
53 | return result; |
54 | } |
55 | |
56 | public MultiJoinOnExpr crossJoin(ITableName table) { |
57 | return crossJoin(table.toString()); |
58 | } |
59 | |
60 | public MultiJoinOnExpr crossJoin(ITableName table, String alias) { |
61 | return crossJoin(table.toString(), alias); |
62 | } |
63 | |
64 | public MultiJoinOnExpr crossJoin(final String table) { |
65 | return (MultiJoinOnExpr) addList(new MultiJoinOnExpr(MultiJoinOnExpr.JoinType.CROSS_JOIN, table), froms); |
66 | } |
67 | |
68 | public MultiJoinOnExpr crossJoin(final String table, final String alias) { |
69 | return (MultiJoinOnExpr) addList(new MultiJoinOnExpr(MultiJoinOnExpr.JoinType.CROSS_JOIN, table, alias), froms); |
70 | } |
71 | |
72 | public ItfSelectBuilder distinct() { |
73 | isDistinct = true; |
74 | return this; |
75 | } |
76 | |
77 | public ItfSelectBuilder from(ITableName from) { |
78 | return from(from.toString()); |
79 | } |
80 | |
81 | public ItfSelectBuilder from(ITableName from, String alias) { |
82 | return from(from.toString(), alias); |
83 | } |
84 | |
85 | public ItfSelectBuilder from(final ItfSelectBuilder subSelect, final String alias) { |
86 | ItfSelectBuilder expr = subSelect.internal_setAlias(alias).internal_setIsNestedSelect(); |
87 | if(froms.contains(expr) == false) { |
88 | froms.add(expr); |
89 | } |
90 | return this; |
91 | |
92 | } |
93 | |
94 | public ItfSelectBuilder from(final String from) { |
95 | TableIdentifierExpr expr = new TableIdentifierExpr(from); |
96 | if(froms.contains(expr) == false) { |
97 | froms.add(expr); |
98 | } |
99 | return this; |
100 | } |
101 | |
102 | public ItfSelectBuilder from(final String from, final String alias) { |
103 | TableIdentifierExpr expr = new TableIdentifierExpr(from, alias); |
104 | if(froms.contains(expr) == false) { |
105 | froms.add(expr); |
106 | } |
107 | return this; |
108 | } |
109 | |
110 | public ItfSelectBuilder groupBy(ITableName table) { |
111 | return groupBy(table.toString()); |
112 | } |
113 | |
114 | /** |
115 | * @param string |
116 | * @return |
117 | */ |
118 | public ItfSelectBuilder groupBy(final String... tableNames) { |
119 | for(final String name : tableNames) { |
120 | groupByTableNames.add(name); |
121 | } |
122 | return this; |
123 | } |
124 | |
125 | public SurogateAndOrListSelectBuilder having() { |
126 | return havings; |
127 | } |
128 | |
129 | public MultiJoinOnExpr innerJoin(ITableName table) { |
130 | return innerJoin(table.toString()); |
131 | } |
132 | |
133 | public MultiJoinOnExpr innerJoin(ITableName table, String alias) { |
134 | return innerJoin(table.toString(), alias); |
135 | } |
136 | |
137 | public MultiJoinOnExpr innerJoin(final String table) { |
138 | return (MultiJoinOnExpr) addList(new MultiJoinOnExpr(MultiJoinOnExpr.JoinType.INNER_JOIN, table), froms); |
139 | } |
140 | |
141 | public MultiJoinOnExpr innerJoin(final String table, final String alias) { |
142 | return (MultiJoinOnExpr) addList(new MultiJoinOnExpr(MultiJoinOnExpr.JoinType.INNER_JOIN, table, alias), froms); |
143 | } |
144 | |
145 | public ItfSelectBuilder internal_setAlias(final String alias) { |
146 | this.selectAlias = alias; |
147 | return this; |
148 | } |
149 | |
150 | public ItfSelectBuilder internal_setIsNestedSelect() { |
151 | this.isNestedSelect = true; |
152 | return this; |
153 | } |
154 | |
155 | public MultiJoinOnExpr leftJoin(ITableName table) { |
156 | return leftJoin(table.toString()); |
157 | } |
158 | |
159 | public MultiJoinOnExpr leftJoin(ITableName table, String alias) { |
160 | return leftJoin(table.toString(), alias); |
161 | } |
162 | |
163 | public MultiJoinOnExpr leftJoin(final String table) { |
164 | return (MultiJoinOnExpr) addList(new MultiJoinOnExpr(MultiJoinOnExpr.JoinType.LEFT_JOIN, table), froms); |
165 | } |
166 | |
167 | public MultiJoinOnExpr leftJoin(final String table, final String alias) { |
168 | return (MultiJoinOnExpr) addList(new MultiJoinOnExpr(MultiJoinOnExpr.JoinType.LEFT_JOIN, table, alias), froms); |
169 | } |
170 | |
171 | public ItfSelectBuilder orderBy(ITableName table) { |
172 | return orderBy(table.toString()); |
173 | } |
174 | |
175 | public ItfSelectBuilder orderBy(final String tableName) { |
176 | orderBys.add(tableName, OrderByExpr.Ordering.NOTHING); |
177 | return this; |
178 | } |
179 | |
180 | public ItfSelectBuilder orderByAsc(ITableName table) { |
181 | return orderByAsc(table.toString()); |
182 | } |
183 | |
184 | public ItfSelectBuilder orderByAsc(final String tableName) { |
185 | orderBys.add(tableName, OrderByExpr.Ordering.ASCENDING); |
186 | return this; |
187 | } |
188 | |
189 | public ItfSelectBuilder orderByDesc(ITableName table) { |
190 | return orderByDesc(table.toString()); |
191 | } |
192 | |
193 | public ItfSelectBuilder orderByDesc(final String tableName) { |
194 | orderBys.add(tableName, OrderByExpr.Ordering.DESCENDING); |
195 | return this; |
196 | } |
197 | |
198 | public MultiJoinOnExpr outerJoin(ITableName table) { |
199 | return outerJoin(table.toString()); |
200 | } |
201 | |
202 | public MultiJoinOnExpr outerJoin(ITableName table, String alias) { |
203 | return outerJoin(table.toString(), alias); |
204 | } |
205 | |
206 | public MultiJoinOnExpr outerJoin(final String table) { |
207 | return (MultiJoinOnExpr) addList(new MultiJoinOnExpr(MultiJoinOnExpr.JoinType.OUTER_JOIN, table), froms); |
208 | } |
209 | |
210 | public MultiJoinOnExpr outerJoin(final String table, final String alias) { |
211 | return (MultiJoinOnExpr) addList(new MultiJoinOnExpr(MultiJoinOnExpr.JoinType.OUTER_JOIN, table, alias), froms); |
212 | } |
213 | |
214 | public MultiJoinOnExpr rightJoin(ITableName table) { |
215 | return rightJoin(table.toString()); |
216 | } |
217 | |
218 | public MultiJoinOnExpr rightJoin(ITableName table, String alias) { |
219 | return rightJoin(table.toString(), alias); |
220 | } |
221 | |
222 | public MultiJoinOnExpr rightJoin(final String table) { |
223 | return (MultiJoinOnExpr) addList(new MultiJoinOnExpr(MultiJoinOnExpr.JoinType.RIGHT_JOIN, table), froms); |
224 | } |
225 | |
226 | public MultiJoinOnExpr rightJoin(final String table, final String alias) { |
227 | return (MultiJoinOnExpr) addList(new MultiJoinOnExpr(MultiJoinOnExpr.JoinType.RIGHT_JOIN, table, alias), froms); |
228 | } |
229 | |
230 | /** |
231 | * Select a column |
232 | * |
233 | * @param column |
234 | * the column to add to the SELECT part of an SQL expression |
235 | * @return this |
236 | */ |
237 | public ItfSelectBuilder select(final String column) { |
238 | ColumnIdentifierExpr expr = new ColumnIdentifierExpr(column); |
239 | if(selectColumns.contains(expr) == false) { |
240 | selectColumns.add(expr); |
241 | } |
242 | return this; |
243 | } |
244 | |
245 | /** |
246 | * Select a column |
247 | * |
248 | * @param column |
249 | * The column to add to the SELECT part of an SQL expression |
250 | * @param alias |
251 | * the AS part when aliasing |
252 | * @return this |
253 | */ |
254 | public ItfSelectBuilder select(final String column, final String alias) { |
255 | ColumnIdentifierExpr expr = new ColumnIdentifierExpr(column, alias); |
256 | if(selectColumns.contains(expr) == false) { |
257 | selectColumns.add(expr); |
258 | } |
259 | return this; |
260 | } |
261 | |
262 | /** |
263 | * Call this method for generating an SQL expression from your object. |
264 | * |
265 | * @return An SQL expresion |
266 | */ |
267 | public String toSql() { |
268 | final StringBuilder sb = new StringBuilder(); |
269 | toSql(sb, ""); |
270 | return sb.toString(); |
271 | } |
272 | |
273 | /** |
274 | * Internal method for sql generation. Use <code>toSql(String)</code> instead. |
275 | * |
276 | * @see toSql() |
277 | */ |
278 | public void toSql(final StringBuilder sb, String indent) { |
279 | if(isNestedSelect) { |
280 | sb.append("(\n"); |
281 | indent = indent + indentDelta; |
282 | sb.append(indent); |
283 | } |
284 | |
285 | toSqlSelect(sb, indent); |
286 | toSqlFrom(sb, indent); |
287 | toSqlWhere(sb, indent); |
288 | |
289 | toSqlGroupBy(sb, indent); |
290 | toSqlHaving(sb, indent); |
291 | toSqlOrderBy(sb, indent); |
292 | if(isNestedSelect) { |
293 | sb.append(")"); |
294 | } |
295 | |
296 | // selectAlias |
297 | sb.append(selectAlias.equals("") ? "" : " AS " + selectAlias); |
298 | } |
299 | |
300 | /** |
301 | * @param sb |
302 | * @param indent |
303 | * @throws IllegalArgumentException |
304 | */ |
305 | protected void toSqlFrom(final StringBuilder sb, final String indent) throws IllegalArgumentException { |
306 | sb.append("\n"); |
307 | sb.append(indent); |
308 | sb.append("FROM "); |
309 | // special case for first entry as it must be a FROM |
310 | boolean firstEntry = true; |
311 | for(final IPositionSelectFrom from : froms) { |
312 | if(firstEntry) { |
313 | firstEntry = false; |
314 | if(from instanceof TableIdentifierExpr == false && from instanceof SelectBuilder == false) { |
315 | final StringBuilder sb2 = new StringBuilder(); |
316 | from.toSql(sb2, ""); |
317 | throw new IllegalArgumentException("First entry in a FROM must be a table name! got '" + sb2.toString() |
318 | + "'"); |
319 | } |
320 | from.toSql(sb, ""); |
321 | continue; |
322 | } |
323 | |
324 | if(from instanceof TableIdentifierExpr || from instanceof SelectBuilder) { |
325 | sb.append(", "); // prepend comma on normal table names or subselects, but not for inner join or outer |
326 | // join |
327 | } |
328 | from.toSql(sb, indent); |
329 | } |
330 | } |
331 | |
332 | /** |
333 | * @param sb |
334 | * @param indent |
335 | */ |
336 | protected void toSqlGroupBy(final StringBuilder sb, final String indent) { |
337 | if(groupByTableNames.size() == 0) { |
338 | return; |
339 | } |
340 | |
341 | sb.append("\n"); |
342 | sb.append(indent); |
343 | sb.append("GROUP BY "); |
344 | sb.append(StringHelper.join(",", groupByTableNames.iterator())); |
345 | } |
346 | |
347 | protected void toSqlHaving(final StringBuilder sb, final String indent) { |
348 | if(havings.internal_size() == 0) { |
349 | return; |
350 | } |
351 | |
352 | sb.append("\n"); |
353 | sb.append(indent); |
354 | sb.append("HAVING "); |
355 | havings.toSql(sb, indent); |
356 | } |
357 | |
358 | protected void toSqlOrderBy(final StringBuilder sb, final String indent) { |
359 | orderBys.toSql(sb, indent); |
360 | } |
361 | |
362 | /** |
363 | * @param sb |
364 | * @param indent |
365 | */ |
366 | protected void toSqlSelect(final StringBuilder sb, final String indent) { |
367 | sb.append(indent); |
368 | sb.append("SELECT "); |
369 | |
370 | // distinct |
371 | if(isDistinct) { |
372 | sb.append("DISTINCT "); |
373 | } |
374 | |
375 | for(final IPositionSelectColumn col : selectColumns) { |
376 | col.toSql(sb, indent); |
377 | sb.append(", "); |
378 | } |
379 | sb.deleteCharAt(sb.length() - 2); // delete last comma |
380 | sb.deleteCharAt(sb.length() - 1); // delete last space |
381 | } |
382 | |
383 | protected void toSqlWhere(final StringBuilder sb, final String indent) { |
384 | if(wheres.internal_size() == 0) { |
385 | return; |
386 | } |
387 | |
388 | sb.append("\n"); |
389 | sb.append(indent); |
390 | sb.append("WHERE "); |
391 | wheres.toSql(sb, indent); |
392 | } |
393 | |
394 | public SurogateAndOrListSelectBuilder where() { |
395 | return wheres; |
396 | } |
397 | |
398 | public ItfSelectBuilder select(IColumnName column) { |
399 | return select(column.toString()); |
400 | } |
401 | |
402 | public ItfSelectBuilder select(IColumnName column, String alias) { |
403 | return select(column.toString(), alias); |
404 | } |
405 | |
406 | } |