1 /*
2  * Copyright 2015-2018 HuntLabs.cn
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 module hunt.sql.ast.statement.SQLSelectQueryBlock;
17 
18 import hunt.sql.ast.statement.SQLSelectGroupByClause;
19 
20 import hunt.collection;
21 
22 import hunt.sql.SQLUtils;
23 import hunt.sql.ast;
24 import hunt.sql.ast.expr;
25 import hunt.sql.visitor.SQLASTVisitor;
26 import hunt.sql.util.FnvHash;
27 import hunt.sql.ast.statement.SQLSelectItem;
28 import hunt.sql.ast.statement.SQLSelectOrderByItem;
29 import hunt.sql.ast.statement.SQLExprTableSource;
30 import hunt.sql.ast.statement.SQLSelect;
31 import hunt.sql.ast.statement.SQLSelectQuery;
32 import hunt.sql.ast.statement.SQLTableSource;
33 import hunt.sql.ast.statement.SQLColumnDefinition;
34 import hunt.sql.ast.statement.SQLSubqueryTableSource;
35 
36 public class SQLSelectQueryBlock : SQLObjectImpl , SQLSelectQuery, SQLReplaceable {
37     private bool                      bracket         = false;
38     protected int                        distionOption;
39     protected  List!SQLSelectItem  selectList;
40 
41     protected SQLTableSource             from;
42     protected SQLExprTableSource         into;
43     protected SQLExpr                    where;
44 
45     // for oracle & oceanbase
46     protected SQLExpr                    startWith;
47     protected SQLExpr                    connectBy;
48     protected bool                    prior           = false;
49     protected bool                    noCycle         = false;
50     protected SQLOrderBy                 orderBySiblings;
51 
52     protected SQLSelectGroupByClause     groupBy;
53     protected List!SQLWindow            windows;
54     protected SQLOrderBy                 orderBy;
55     protected bool                    parenthesized   = false;
56     protected bool                    forUpdate       = false;
57     protected bool                    noWait          = false;
58     protected SQLExpr                    waitTime;
59     protected SQLLimit                   _limit;
60 
61     // for oracle
62     protected List!SQLExpr              forUpdateOf;
63     protected List!SQLExpr              distributeBy;
64     protected List!SQLSelectOrderByItem sortBy;
65 
66     protected string                     cachedSelectList; // optimized for SelectListCache
67     protected long                       cachedSelectListHash; // optimized for SelectListCache
68 
69     protected List!SQLCommentHint       hints;
70     public  string                     dbType;
71 
72     public this(){
73         selectList      = new ArrayList!SQLSelectItem();
74     }
75 
76     public SQLExprTableSource getInto() {
77         return into;
78     }
79 
80     public void setInto(SQLExpr into) {
81         this.setInto(new SQLExprTableSource(into));
82     }
83 
84     public void setInto(SQLExprTableSource into) {
85         if (into !is null) {
86             into.setParent(this);
87         }
88         this.into = into;
89     }
90 
91     public SQLSelectGroupByClause getGroupBy() {
92         return this.groupBy;
93     }
94 
95     public void setGroupBy(SQLSelectGroupByClause groupBy) {
96         if (groupBy !is null) {
97             groupBy.setParent(this);
98         }
99         this.groupBy = groupBy;
100     }
101 
102     public SQLExpr getWhere() {
103         return this.where;
104     }
105 
106     public void setWhere(SQLExpr where) {
107         if (where !is null) {
108             where.setParent(this);
109         }
110         this.where = where;
111     }
112 
113     public void addWhere(SQLExpr condition) {
114         if (condition is null) {
115             return;
116         }
117 
118         if (where is null) {
119             where = condition;
120         } else {
121             where = SQLBinaryOpExpr.and(where, condition);
122         }
123     }
124     
125     public SQLOrderBy getOrderBy() {
126         return orderBy;
127     }
128 
129     public void setOrderBy(SQLOrderBy orderBy) {
130         if (orderBy !is null) {
131             orderBy.setParent(this);
132         }
133         
134         this.orderBy = orderBy;
135     }
136 
137     public SQLOrderBy getOrderBySiblings() {
138         return orderBySiblings;
139     }
140 
141     public void setOrderBySiblings(SQLOrderBy orderBySiblings) {
142         if (orderBySiblings !is null) {
143             orderBySiblings.setParent(this);
144         }
145         this.orderBySiblings = orderBySiblings;
146     }
147 
148     public int getDistionOption() {
149         return this.distionOption;
150     }
151 
152     public void setDistionOption(int distionOption) {
153         this.distionOption = distionOption;
154     }
155 
156     public List!SQLSelectItem getSelectList() {
157         return this.selectList;
158     }
159     
160     public void addSelectItem(SQLSelectItem item) {
161         this.selectList.add(item);
162         item.setParent(this);
163     }
164 
165     public void addSelectItem(SQLExpr expr) {
166         this.addSelectItem(new SQLSelectItem(expr));
167     }
168 
169     public void addSelectItem(SQLExpr expr, string alias_p) {
170         this.addSelectItem(new SQLSelectItem(expr, alias_p));
171     }
172 
173     public SQLTableSource getFrom() {
174         return this.from;
175     }
176 
177     public void setFrom(SQLTableSource from) {
178         if (from !is null) {
179             from.setParent(this);
180         }
181         this.from = from;
182     }
183 
184     public void setFrom(SQLSelectQueryBlock queryBlock, string alias_p) {
185         if (queryBlock is null) {
186             this.from = null;
187             return;
188         }
189 
190         this.setFrom(new SQLSelect(queryBlock), alias_p);
191     }
192 
193     public void setFrom(SQLSelect select, string alias_p) {
194         if (select is null) {
195             this.from = null;
196             return;
197         }
198 
199         SQLSubqueryTableSource from = new SQLSubqueryTableSource(select);
200         from.setAlias(alias_p);
201         this.setFrom(from);
202     }
203 
204     public void setFrom(string tableName, string alias_p) {
205         SQLExprTableSource from;
206         if (tableName is null || tableName.length == 0) {
207             from = null;
208         } else {
209             from = new SQLExprTableSource(new SQLIdentifierExpr(tableName), alias_p);
210         }
211         this.setFrom(from);
212     }
213 
214     public bool isParenthesized() {
215 		return parenthesized;
216 	}
217 
218 	public void setParenthesized(bool parenthesized) {
219 		this.parenthesized = parenthesized;
220 	}
221 	
222     public bool isForUpdate() {
223         return forUpdate;
224     }
225 
226     public void setForUpdate(bool forUpdate) {
227         this.forUpdate = forUpdate;
228     }
229     
230     public bool isNoWait() {
231         return noWait;
232     }
233 
234     public void setNoWait(bool noWait) {
235         this.noWait = noWait;
236     }
237     
238     public SQLExpr getWaitTime() {
239         return waitTime;
240     }
241     
242     public void setWaitTime(SQLExpr waitTime) {
243         if (waitTime !is null) {
244             waitTime.setParent(this);
245         }
246         this.waitTime = waitTime;
247     }
248 
249     public SQLLimit getLimit() {
250         return _limit;
251     }
252 
253     public void setLimit(SQLLimit _limit) {
254         if (_limit !is null) {
255             _limit.setParent(this);
256         }
257         this._limit = _limit;
258     }
259 
260     public SQLExpr getFirst() {
261         if (_limit is null) {
262             return null;
263         }
264 
265         return _limit.getRowCount();
266     }
267 
268     public void setFirst(SQLExpr first) {
269         if (_limit is null) {
270             _limit = new SQLLimit();
271         }
272         this._limit.setRowCount(first);
273     }
274 
275     public SQLExpr getOffset() {
276         if (_limit is null) {
277             return null;
278         }
279 
280         return _limit.getOffset();
281     }
282 
283     public void setOffset(SQLExpr offset) {
284         if (_limit is null) {
285             _limit = new SQLLimit();
286         }
287         this._limit.setOffset(offset);
288     }
289 
290     public bool isPrior() {
291         return prior;
292     }
293 
294     public void setPrior(bool prior) {
295         this.prior = prior;
296     }
297 
298     public SQLExpr getStartWith() {
299         return this.startWith;
300     }
301 
302     public void setStartWith(SQLExpr startWith) {
303         if (startWith !is null) {
304             startWith.setParent(this);
305         }
306         this.startWith = startWith;
307     }
308 
309     public SQLExpr getConnectBy() {
310         return this.connectBy;
311     }
312 
313     public void setConnectBy(SQLExpr connectBy) {
314         if (connectBy !is null) {
315             connectBy.setParent(this);
316         }
317         this.connectBy = connectBy;
318     }
319 
320     public bool isNoCycle() {
321         return this.noCycle;
322     }
323 
324     public void setNoCycle(bool noCycle) {
325         this.noCycle = noCycle;
326     }
327 
328     public List!SQLExpr getDistributeBy() {
329         return distributeBy;
330     }
331 
332     public List!SQLSelectOrderByItem getSortBy() {
333         return sortBy;
334     }
335 
336     public void addSortBy(SQLSelectOrderByItem item) {
337         if (sortBy is null) {
338             sortBy = new ArrayList!SQLSelectOrderByItem();
339         }
340         if (item !is null) {
341             item.setParent(this);
342         }
343         this.sortBy.add(item);
344     }
345 
346 	
347     override  protected void accept0(SQLASTVisitor visitor) {
348         if (visitor.visit(this)) {
349             acceptChild!SQLSelectItem(visitor, this.selectList);
350             acceptChild(visitor, this.from);
351             acceptChild(visitor, this.into);
352             acceptChild(visitor, this.where);
353             acceptChild(visitor, this.startWith);
354             acceptChild(visitor, this.connectBy);
355             acceptChild(visitor, this.groupBy);
356             acceptChild(visitor, this.orderBy);
357             acceptChild!SQLExpr(visitor, this.distributeBy);
358             acceptChild!SQLSelectOrderByItem(visitor, this.sortBy);
359             acceptChild(visitor, this.waitTime);
360             acceptChild(visitor, this._limit);
361         }
362         visitor.endVisit(this);
363     }
364 
365     override
366     public size_t toHash() @trusted nothrow {
367          int prime = 31;
368         size_t result = 1;
369         result = prime * result + hashOf(parenthesized);
370         result = prime * result + distionOption;
371         result = prime * result + ((from is null) ? 0 : (cast(Object)from).toHash());
372         result = prime * result + ((groupBy is null) ? 0 : (cast(Object)groupBy).toHash());
373         result = prime * result + ((into is null) ? 0 : (cast(Object)into).toHash());
374         result = prime * result + ((selectList is null) ? 0 : (cast(Object)selectList).toHash());
375         result = prime * result + ((where is null) ? 0 : (cast(Object)where).toHash());
376         return result;
377     }
378 
379     override
380     public bool opEquals(Object obj) {
381         if (this == obj) return true;
382         if (obj is null) return false;
383         if (typeid(this) != typeid(obj)) return false;
384         SQLSelectQueryBlock other = cast(SQLSelectQueryBlock) obj;
385         if (parenthesized ^ other.parenthesized) return false;
386         if (distionOption != other.distionOption) return false;
387         if (from is null) {
388             if (other.from !is null) return false;
389         } else if (!(cast(Object)(from)).opEquals(cast(Object)(other.from))) return false;
390         if (groupBy is null) {
391             if (other.groupBy !is null) return false;
392         } else if (!(cast(Object)(groupBy)).opEquals(cast(Object)(other.groupBy))) return false;
393         if (into is null) {
394             if (other.into !is null) return false;
395         } else if (!(cast(Object)(into)).opEquals(cast(Object)(other.into))) return false;
396         if (selectList is null) {
397             if (other.selectList !is null) return false;
398         } else if (!(cast(Object)(selectList)).opEquals(cast(Object)(other.selectList))) return false;
399         if (where is null) {
400             if (other.where !is null) return false;
401         } else if (!(cast(Object)(where)).opEquals(cast(Object)(other.where))) return false;
402         return true;
403     }
404 
405     override public SQLSelectQueryBlock clone() {
406         SQLSelectQueryBlock x = new SQLSelectQueryBlock();
407         cloneTo(x);
408         return x;
409     }
410 
411     public List!SQLExpr getForUpdateOf() {
412         if (forUpdateOf is null) {
413             forUpdateOf = new ArrayList!SQLExpr(1);
414         }
415         return forUpdateOf;
416     }
417 
418     public int getForUpdateOfSize() {
419         if (forUpdateOf is null) {
420             return 0;
421         }
422 
423         return forUpdateOf.size();
424     }
425 
426     public void cloneSelectListTo(SQLSelectQueryBlock x) {
427         x.distionOption = distionOption;
428         foreach (SQLSelectItem item ; this.selectList) {
429             SQLSelectItem item2 = item.clone();
430             item2.setParent(x);
431             x.selectList.add(item2);
432         }
433     }
434 
435     public void cloneTo(SQLSelectQueryBlock x) {
436 
437         x.distionOption = distionOption;
438 
439         foreach (SQLSelectItem item ; this.selectList) {
440             x.addSelectItem(item.clone());
441         }
442 
443         if (from !is null) {
444             x.setFrom(from.clone());
445         }
446 
447         if (into !is null) {
448             x.setInto(into.clone());
449         }
450 
451         if (where !is null) {
452             x.setWhere(where.clone());
453         }
454 
455         if (startWith !is null) {
456             x.setStartWith(startWith.clone());
457         }
458 
459         if (connectBy !is null) {
460             x.setConnectBy(connectBy.clone());
461         }
462 
463         x.prior = prior;
464         x.noCycle = noCycle;
465 
466         if (orderBySiblings !is null) {
467             x.setOrderBySiblings(orderBySiblings.clone());
468         }
469 
470         if (groupBy !is null) {
471             x.setGroupBy(groupBy.clone());
472         }
473 
474         if (orderBy !is null) {
475             x.setOrderBy(orderBy.clone());
476         }
477 
478         x.parenthesized = parenthesized;
479         x.forUpdate = forUpdate;
480         x.noWait = noWait;
481         if (waitTime !is null) {
482             x.setWaitTime(waitTime.clone());
483         }
484 
485         if (_limit !is null) {
486             x.setLimit(_limit.clone());
487         }
488     }
489 
490     override
491     public bool isBracket() {
492         return bracket;
493     }
494 
495     override
496     public void setBracket(bool bracket) {
497         this.bracket = bracket;
498     }
499 
500     public SQLTableSource findTableSource(string alias_p) {
501         if (from is null) {
502             return null;
503         }
504         return from.findTableSource(alias_p);
505     }
506 
507     public SQLTableSource findTableSourceWithColumn(string column) {
508         if (from is null) {
509             return null;
510         }
511         return from.findTableSourceWithColumn(column);
512     }
513 
514     public SQLTableSource findTableSourceWithColumn(long columnHash) {
515         if (from is null) {
516             return null;
517         }
518         return from.findTableSourceWithColumn(columnHash);
519     }
520 
521     override
522     public bool replace(SQLExpr expr, SQLExpr target) {
523         if (where == expr) {
524             setWhere(target);
525             return true;
526         }
527         return false;
528     }
529 
530     public SQLSelectItem findSelectItem(string ident) {
531         if (ident is null) {
532             return null;
533         }
534 
535         long hash = FnvHash.hashCode64(ident);
536         return findSelectItem(hash);
537     }
538 
539     public SQLSelectItem findSelectItem(long identHash) {
540         foreach (SQLSelectItem item ; this.selectList) {
541             if (item.match(identHash)) {
542                 return item;
543             }
544         }
545 
546         return null;
547     }
548 
549     public bool selectItemHasAllColumn() {
550         return selectItemHasAllColumn(true);
551     }
552 
553     public bool selectItemHasAllColumn(bool recursive) {
554         foreach (SQLSelectItem item ; this.selectList) {
555             SQLExpr expr = item.getExpr();
556 
557             bool allColumn = (cast(SQLAllColumnExpr)expr !is null)
558                     || ( cast(SQLPropertyExpr)expr !is null && (cast(SQLPropertyExpr) expr).getName() == ("*"));
559 
560             if (allColumn) {
561                 if (recursive &&  cast(SQLSubqueryTableSource)from !is null) {
562                     SQLSelect subSelect = (cast(SQLSubqueryTableSource) from).select;
563                     SQLSelectQueryBlock queryBlock = subSelect.getQueryBlock();
564                     if (queryBlock !is null) {
565                         return queryBlock.selectItemHasAllColumn();
566                     }
567                 }
568                 return true;
569             }
570         }
571 
572         return false;
573     }
574 
575     public SQLSelectItem findAllColumnSelectItem() {
576         SQLSelectItem allColumnItem = null;
577         foreach (SQLSelectItem item ; this.selectList) {
578             SQLExpr expr = item.getExpr();
579 
580             bool allColumn = (cast(SQLAllColumnExpr)expr !is null)
581                     || ( cast(SQLPropertyExpr)expr !is null && (cast(SQLPropertyExpr) expr).getName() == ("*"));
582 
583             if (allColumnItem !is null) {
584                 return null; // duplicateAllColumn
585             }
586             allColumnItem = item;
587         }
588 
589         return allColumnItem;
590     }
591 
592     public SQLColumnDefinition findColumn(string columnName) {
593         if (from is null) {
594             return null;
595         }
596 
597         long hash = FnvHash.hashCode64(columnName);
598         return from.findColumn(hash);
599     }
600 
601     public void addCondition(string conditionSql) {
602         if (conditionSql is null || conditionSql.length == 0) {
603             return;
604         }
605 
606         SQLExpr condition = SQLUtils.toSQLExpr(conditionSql, dbType);
607         addCondition(condition);
608     }
609 
610     public void addCondition(SQLExpr expr) {
611         if (expr is null) {
612             return;
613         }
614 
615         this.setWhere(SQLBinaryOpExpr.and(where, expr));
616     }
617 
618     public bool removeCondition(string conditionSql) {
619         if (conditionSql is null || conditionSql.length == 0) {
620             return false;
621         }
622 
623         SQLExpr condition = SQLUtils.toSQLExpr(conditionSql, dbType);
624 
625         return removeCondition(condition);
626     }
627 
628     public bool removeCondition(SQLExpr condition) {
629         if (condition is null) {
630             return false;
631         }
632 
633         if ( cast(SQLBinaryOpExprGroup)where !is null) {
634             SQLBinaryOpExprGroup group = cast(SQLBinaryOpExprGroup) where;
635 
636             int removedCount = 0;
637             List!SQLExpr items = group.getItems();
638             for (int i = items.size() - 1; i >= 0; i--) {
639                 if ((cast(Object)(items.get(i))).opEquals(cast(Object)(condition))) {
640                     items.removeAt(i);
641                     removedCount++;
642                 }
643             }
644             if (items.size() == 0) {
645                 where = null;
646             }
647 
648             return removedCount > 0;
649         }
650 
651         if ( cast(SQLBinaryOpExpr)where !is null) {
652             SQLBinaryOpExpr binaryOpWhere = cast(SQLBinaryOpExpr) where;
653             SQLBinaryOperator operator = binaryOpWhere.getOperator();
654             if (operator == SQLBinaryOperator.BooleanAnd || operator == SQLBinaryOperator.BooleanOr) {
655                 List!SQLExpr items = SQLBinaryOpExpr.split(binaryOpWhere);
656 
657                 int removedCount = 0;
658                 for (int i = items.size() - 1; i >= 0; i--) {
659                     SQLExpr item = items.get(i);
660                     if ((cast(Object)(item)).opEquals(cast(Object)(condition))) {
661                         if (SQLUtils.replaceInParent(item, null)) {
662                             removedCount++;
663                         }
664                     }
665                 }
666 
667                 return removedCount > 0;
668             }
669         }
670 
671         if ((cast(Object)(condition)).opEquals(cast(Object)(where))) {
672             where = null;
673             return true;
674         }
675 
676         return false;
677     }
678 
679     public void limit(int rowCount, int offset) {
680         SQLLimit _limit = new SQLLimit();
681         _limit.setRowCount(new SQLIntegerExpr(rowCount));
682         if (offset > 0) {
683             _limit.setOffset(new SQLIntegerExpr(offset));
684         }
685 
686         setLimit(_limit);
687     }
688 
689     public string getCachedSelectList() {
690         return cachedSelectList;
691     }
692 
693     public void setCachedSelectList(string cachedSelectList, long cachedSelectListHash) {
694         this.cachedSelectList = cachedSelectList;
695         this.cachedSelectListHash = cachedSelectListHash;
696     }
697 
698     public long getCachedSelectListHash() {
699         return cachedSelectListHash;
700     }
701 
702     public List!SQLCommentHint getHintsDirect() {
703         return hints;
704     }
705 
706     public List!SQLCommentHint getHints() {
707         if (hints is null) {
708             hints = new ArrayList!SQLCommentHint(2);
709         }
710         return hints;
711     }
712 
713     public void setHints(List!SQLCommentHint hints) {
714         this.hints = hints;
715     }
716 
717     public int getHintsSize() {
718         if (hints is null) {
719             return 0;
720         }
721 
722         return hints.size();
723     }
724 
725     public string getDbType() {
726         return dbType;
727     }
728 
729     public void setDbType(string dbType) {
730         this.dbType = dbType;
731     }
732 
733     public List!SQLWindow getWindows() {
734         return windows;
735     }
736 
737     public void addWindow(SQLWindow x) {
738         if (x !is null) {
739             x.setParent(this);
740         }
741         if (windows is null) {
742             windows = new ArrayList!SQLWindow(4);
743         }
744         this.windows.add(x);
745     }
746 }