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.SQLJoinTableSource;
17 
18 
19 import hunt.collection;
20 
21 import hunt.sql.SQLUtils;
22 import hunt.sql.ast.SQLExpr;
23 import hunt.sql.ast.SQLReplaceable;
24 import hunt.sql.ast.expr.SQLBinaryOpExpr;
25 import hunt.sql.ast.expr.SQLIdentifierExpr;
26 import hunt.sql.ast.expr.SQLPropertyExpr;
27 import hunt.sql.visitor.SQLASTVisitor;
28 import hunt.sql.util.FnvHash;
29 import hunt.sql.ast.statement.SQLExprTableSource;
30 import hunt.sql.ast.statement.SQLTableSourceImpl;
31 import hunt.sql.ast.statement.SQLTableSource;
32 import hunt.sql.ast.statement.SQLColumnDefinition;
33 import hunt.util.StringBuilder;
34 import std.uni;
35 import hunt.sql.ast.SQLObject;
36 
37 public class SQLJoinTableSource : SQLTableSourceImpl , SQLReplaceable {
38 
39     protected SQLTableSource      left;
40     protected JoinType            joinType;
41     protected SQLTableSource      right;
42     protected SQLExpr             condition;
43     protected  List!SQLExpr _using;
44 
45 
46     protected bool             natural = false;
47 
48     public this(string alias_p){
49         _using = new ArrayList!SQLExpr();
50         super(alias_p);
51     }
52 
53     public this(){
54         _using = new ArrayList!SQLExpr();
55     }
56 
57     public this(SQLTableSource left, JoinType joinType, SQLTableSource right, SQLExpr condition){
58         this();
59         this.setLeft(left);
60         this.setJoinType(joinType);
61         this.setRight(right);
62         this.setCondition(condition);
63     }
64 
65     public this(SQLTableSource left, JoinType joinType, SQLTableSource right){
66         this();
67         this.setLeft(left);
68         this.setJoinType(joinType);
69         this.setRight(right);
70     }
71 
72     override  protected void accept0(SQLASTVisitor visitor) {
73         if (visitor.visit(this)) {
74             acceptChild(visitor, this.left);
75             acceptChild(visitor, this.right);
76             acceptChild(visitor, this.condition);
77             acceptChild!SQLExpr(visitor, this._using);
78         }
79 
80         visitor.endVisit(this);
81     }
82 
83     public JoinType getJoinType() {
84         return this.joinType;
85     }
86 
87     public void setJoinType(JoinType joinType) {
88         this.joinType = joinType;
89     }
90 
91     public SQLTableSource getLeft() {
92         return this.left;
93     }
94 
95     public void setLeft(SQLTableSource left) {
96         if (left !is null) {
97             left.setParent(this);
98         }
99         this.left = left;
100     }
101 
102     public void setLeft(string tableName, string alias_p) {
103         SQLExprTableSource tableSource;
104         if (tableName is null || tableName.length == 0) {
105             tableSource = null;
106         } else {
107             tableSource = new SQLExprTableSource(new SQLIdentifierExpr(tableName), alias_p);
108         }
109         this.setLeft(tableSource);
110     }
111 
112     public void setRight(string tableName, string alias_p) {
113         SQLExprTableSource tableSource;
114         if (tableName is null || tableName.length == 0) {
115             tableSource = null;
116         } else {
117             tableSource = new SQLExprTableSource(new SQLIdentifierExpr(tableName), alias_p);
118         }
119         this.setRight(tableSource);
120     }
121 
122     public SQLTableSource getRight() {
123         return this.right;
124     }
125 
126     public void setRight(SQLTableSource right) {
127         if (right !is null) {
128             right.setParent(this);
129         }
130         this.right = right;
131     }
132 
133     public SQLExpr getCondition() {
134         return this.condition;
135     }
136 
137     public void setCondition(SQLExpr condition) {
138         if (condition !is null) {
139             condition.setParent(this);
140         }
141         this.condition = condition;
142     }
143 
144     public void addConditionn(SQLExpr condition) {
145         this.condition = SQLBinaryOpExpr.and(this.condition, condition);
146     }
147 
148     public void addConditionnIfAbsent(SQLExpr condition) {
149         if (this.containsCondition(condition)) {
150             return;
151         }
152         this.condition = SQLBinaryOpExpr.and(this.condition, condition);
153     }
154 
155     public bool containsCondition(SQLExpr condition) {
156         if (this.condition is null) {
157             return false;
158         }
159 
160         if ((cast(Object)(this.condition)).opEquals(cast(Object)(condition))) {
161             return false;
162         }
163 
164         if (cast(SQLBinaryOpExpr)(this.condition) !is null ) {
165             return (cast(SQLBinaryOpExpr) this.condition).contains(condition);
166         }
167 
168         return false;
169     }
170 
171     public List!SQLExpr getUsing() {
172         return this._using;
173     }
174 
175     public bool isNatural() {
176         return natural;
177     }
178 
179     public void setNatural(bool natural) {
180         this.natural = natural;
181     }
182 
183     override public void output(StringBuilder buf) {
184         this.left.output(buf);
185         buf.append(' ');
186         buf.append(JoinType.toString(this.joinType));
187         buf.append(' ');
188         this.right.output(buf);
189 
190         if (this.condition !is null) {
191             buf.append(" ON ");
192             this.condition.output(buf);
193         }
194     }
195 
196     override
197     public bool opEquals(Object o) {
198         if (this == o) return true;
199         if (o is null || typeid(this) != typeid(o)) return false;
200 
201         SQLJoinTableSource that = cast(SQLJoinTableSource) o;
202 
203         if (natural != that.natural) return false;
204         if (left !is null ? !(cast(Object)(left)).opEquals(cast(Object)(that.left)) : that.left !is null) return false;
205         if (joinType != that.joinType) return false;
206         if (right !is null ? !(cast(Object)(right)).opEquals(cast(Object)(that.right)) : that.right !is null) return false;
207         if (condition !is null ? !(cast(Object)(condition)).opEquals(cast(Object)(that.condition)) : that.condition !is null) return false;
208         return _using !is null ? (cast(Object)(_using)).opEquals(cast(Object)(that._using)) : that._using is null;
209     }
210 
211     override
212     public bool replace(SQLExpr expr, SQLExpr target) {
213         if (condition == expr) {
214             setCondition(target);
215             return true;
216         }
217 
218         return false;
219     }
220 
221     public static struct  JoinType {
222         enum JoinType COMMA = JoinType(","); //
223         enum JoinType JOIN = JoinType("JOIN"); //
224         enum JoinType INNER_JOIN = JoinType("INNER JOIN"); //
225         enum JoinType CROSS_JOIN = JoinType("CROSS JOIN"); //
226         enum JoinType NATURAL_JOIN = JoinType("NATURAL JOIN"); //
227         enum JoinType NATURAL_INNER_JOIN = JoinType("NATURAL INNER JOIN"); //
228         enum JoinType LEFT_OUTER_JOIN = JoinType("LEFT JOIN"); //
229         enum JoinType LEFT_SEMI_JOIN = JoinType("LEFT SEMI JOIN"); //
230         enum JoinType LEFT_ANTI_JOIN = JoinType("LEFT ANTI JOIN"); //
231         enum JoinType RIGHT_OUTER_JOIN = JoinType("RIGHT JOIN"); //
232         enum JoinType FULL_OUTER_JOIN = JoinType("FULL JOIN");//
233         enum JoinType STRAIGHT_JOIN = JoinType("STRAIGHT_JOIN"); //
234         enum JoinType OUTER_APPLY = JoinType("OUTER APPLY");//
235         enum JoinType CROSS_APPLY = JoinType("CROSS APPLY");
236 
237         public  string name;
238         public  string name_lcase;
239 
240         this(string name){
241             this.name = name;
242             this.name_lcase = toLower(name);
243         }
244 
245         public static string toString(JoinType joinType) {
246             return joinType.name;
247         }
248 
249         bool opEquals(const JoinType h) nothrow {
250             return name == h.name ;
251         } 
252 
253         bool opEquals(ref const JoinType h) nothrow {
254             return name == h.name ;
255         }
256     }
257 
258 
259     public void cloneTo(SQLJoinTableSource x) {
260         x._alias = _alias;
261 
262         if (left !is null) {
263             x.setLeft(left.clone());
264         }
265 
266         x.joinType = joinType;
267 
268         if (right !is null) {
269             x.setRight(right.clone());
270         }
271 
272         if(condition !is null){
273             x.setCondition(condition);
274         }
275 
276         foreach (SQLExpr item ; _using) {
277             SQLExpr item2 = item.clone();
278             item2.setParent(x);
279             x._using.add(item2);
280         }
281 
282         x.natural = natural;
283     }
284 
285     override public SQLJoinTableSource clone() {
286         SQLJoinTableSource x = new SQLJoinTableSource();
287         cloneTo(x);
288         return x;
289     }
290 
291     public void reverse() {
292         SQLTableSource temp = left;
293         left = right;
294         right = temp;
295 
296         if (cast(SQLJoinTableSource)(left) !is null ) {
297             (cast(SQLJoinTableSource) left).reverse();
298         }
299 
300         if (cast(SQLJoinTableSource)(right) !is null ) {
301             (cast(SQLJoinTableSource) right).reverse();
302         }
303     }
304 
305     /**
306      * a inner_join (b inner_join c) -< a inner_join b innre_join c
307      */
308     public void rearrangement() {
309         if (joinType != JoinType.COMMA && joinType != JoinType.INNER_JOIN) {
310             return;
311         }
312         if (cast(SQLJoinTableSource)(right) !is null ) {
313             SQLJoinTableSource rightJoin = cast(SQLJoinTableSource) right;
314 
315             if (rightJoin.joinType != JoinType.COMMA && rightJoin.joinType != JoinType.INNER_JOIN) {
316                 return;
317             }
318 
319             SQLTableSource a = left;
320             SQLTableSource b = rightJoin.getLeft();
321             SQLTableSource c = rightJoin.getRight();
322             SQLExpr on_ab = condition;
323             SQLExpr on_bc = rightJoin.condition;
324 
325             setLeft(rightJoin);
326             rightJoin.setLeft(a);
327             rightJoin.setRight(b);
328 
329 
330             bool on_ab_match = false;
331             if (cast(SQLBinaryOpExpr)(on_ab) !is null ) {
332                 SQLBinaryOpExpr on_ab_binaryOpExpr = cast(SQLBinaryOpExpr) on_ab;
333                 if ( cast(SQLPropertyExpr)(on_ab_binaryOpExpr.getLeft()) !is null
334                         && (cast(SQLPropertyExpr)on_ab_binaryOpExpr.getRight()) !is null ) {
335                     string leftOwnerName = (cast(SQLPropertyExpr) on_ab_binaryOpExpr.getLeft()).getOwnernName();
336                     string rightOwnerName = (cast(SQLPropertyExpr) on_ab_binaryOpExpr.getRight()).getOwnernName();
337 
338                     if (rightJoin.containsAlias(leftOwnerName) && rightJoin.containsAlias(rightOwnerName)) {
339                         on_ab_match = true;
340                     }
341                 }
342             }
343 
344             if (on_ab_match) {
345                 rightJoin.setCondition(on_ab);
346             } else {
347                 rightJoin.setCondition(null);
348                 on_bc = SQLBinaryOpExpr.and(on_bc, on_ab);
349             }
350 
351             setRight(c);
352             setCondition(on_bc);
353         }
354     }
355 
356     public bool contains(SQLTableSource tableSource, SQLExpr condition) {
357         if ((cast(Object)(right)).opEquals(cast(Object)(tableSource))) {
358             if (this.condition == condition) {
359                 return true;
360             }
361 
362             return this.condition !is null && (cast(Object)(this.condition)).opEquals(cast(Object)(condition));
363         }
364 
365         if (cast(SQLJoinTableSource)(left) !is null ) {
366             SQLJoinTableSource joinLeft = cast(SQLJoinTableSource) left;
367 
368             if (cast(SQLJoinTableSource)(tableSource) !is null ) {
369                 SQLJoinTableSource join = cast(SQLJoinTableSource) tableSource;
370 
371                 if ((cast(Object)(join.right)).opEquals(cast(Object)(right)) && (cast(Object)(this.condition)).opEquals(cast(Object)(condition)) && (cast(Object)(joinLeft.right)).opEquals(cast(Object)(join.left))) {
372                     return true;
373                 }
374             }
375 
376             return joinLeft.contains(tableSource, condition);
377         }
378 
379         return false;
380     }
381 
382     public bool contains(SQLTableSource tableSource, SQLExpr condition, JoinType joinType) {
383         if ((cast(Object)(right)).opEquals(cast(Object)(tableSource))) {
384             if (this.condition == condition) {
385                 return true;
386             }
387 
388             return this.condition !is null && (cast(Object)(this.condition)).opEquals(cast(Object)(condition)) && this.joinType == joinType;
389         }
390 
391         if (cast(SQLJoinTableSource)(left) !is null ) {
392             SQLJoinTableSource joinLeft = cast(SQLJoinTableSource) left;
393 
394             if (cast(SQLJoinTableSource)(tableSource) !is null ) {
395                 SQLJoinTableSource join = cast(SQLJoinTableSource) tableSource;
396 
397                 if ((cast(Object)(join.right)).opEquals(cast(Object)(right))
398                         && this.condition !is null && (cast(Object)(this.condition)).opEquals(cast(Object)(join.condition))
399                         && (cast(Object)(joinLeft.right)).opEquals(cast(Object)(join.left))
400                         && this.joinType == join.joinType
401                         && joinLeft.condition !is null && (cast(Object)(joinLeft.condition)).opEquals(cast(Object)(condition))
402                         && joinLeft.joinType == joinType) {
403                     return true;
404                 }
405             }
406 
407             return joinLeft.contains(tableSource, condition, joinType);
408         }
409 
410         return false;
411     }
412 
413     public SQLJoinTableSource findJoin(SQLTableSource tableSource, JoinType joinType) {
414         if ((cast(Object)(right)).opEquals(cast(Object)(tableSource))) {
415             if (this.joinType == joinType) {
416                 return this;
417             }
418             return null;
419         }
420 
421         if (cast(SQLJoinTableSource)(left) !is null ) {
422             return (cast(SQLJoinTableSource) left).findJoin(tableSource, joinType);
423         }
424 
425         return null;
426     }
427 
428     override public bool containsAlias(string alias_p) {
429         if (SQLUtils.nameEquals(this._alias, alias_p)) {
430             return true;
431         }
432 
433         if (left !is null && left.containsAlias(alias_p)) {
434             return true;
435         }
436 
437         if (right !is null && right.containsAlias(alias_p)) {
438             return true;
439         }
440 
441         return false;
442     }
443 
444     override public SQLColumnDefinition findColumn(string columnName) {
445         long hash = FnvHash.hashCode64(columnName);
446         return findColumn(hash);
447     }
448 
449     override public SQLColumnDefinition findColumn(long columnNameHash) {
450         if (left !is null) {
451             SQLColumnDefinition column = left.findColumn(columnNameHash);
452             if (column !is null) {
453                 return column;
454             }
455         }
456 
457         if (right !is null) {
458             return right.findColumn(columnNameHash);
459         }
460 
461         return null;
462     }
463 
464     override
465     public SQLTableSource findTableSourceWithColumn(string columnName) {
466         long hash = FnvHash.hashCode64(columnName);
467         return findTableSourceWithColumn(hash);
468     }
469 
470     override public SQLTableSource findTableSourceWithColumn(long columnNameHash) {
471         if (left !is null) {
472             SQLTableSource tableSource = left.findTableSourceWithColumn(columnNameHash);
473             if (tableSource !is null) {
474                 return tableSource;
475             }
476         }
477 
478         if (right !is null) {
479             return right.findTableSourceWithColumn(columnNameHash);
480         }
481 
482         return null;
483     }
484 
485     public bool match(string alias_a, string alias_b) {
486         if (left is null || right is null) {
487             return false;
488         }
489 
490         if (left.containsAlias(alias_a)
491                 && right.containsAlias(alias_b)) {
492             return true;
493         }
494 
495         return right.containsAlias(alias_a)
496                 && left.containsAlias(alias_b);
497     }
498 
499     public bool conditionContainsTable(string alias_p) {
500         if (condition is null) {
501             return false;
502         }
503 
504         if (cast(SQLBinaryOpExpr)(condition) !is null ) {
505             return (cast(SQLBinaryOpExpr) condition).conditionContainsTable(alias_p);
506         }
507 
508         return false;
509     }
510 
511     public SQLJoinTableSource join(SQLTableSource right, JoinType joinType, SQLExpr condition) {
512         SQLJoinTableSource joined = new SQLJoinTableSource(this, joinType, right, condition);
513         return joined;
514     }
515 
516     override public SQLTableSource findTableSource(long alias_hash) {
517         if (alias_hash == 0) {
518             return null;
519         }
520 
521         if (aliasHashCode64() == alias_hash) {
522             return this;
523         }
524 
525         SQLTableSource result = left.findTableSource(alias_hash);
526         if (result !is null) {
527             return result;
528         }
529 
530         return right.findTableSource(alias_hash);
531     }
532 
533     public SQLTableSource other(SQLTableSource x) {
534         if (left == x) {
535             return right;
536         }
537 
538         if (right == x) {
539             return left;
540         }
541 
542         return null;
543     }
544 }
545 
546 //  public  struct  JoinType {
547 //         enum JoinType COMMA = JoinType(","); //
548 //         enum JoinType JOIN = JoinType("JOIN"); //
549 //         enum JoinType INNER_JOIN = JoinType("INNER JOIN"); //
550 //         enum JoinType CROSS_JOIN = JoinType("CROSS JOIN"); //
551 //         enum JoinType NATURAL_JOIN = JoinType("NATURAL JOIN"); //
552 //         enum JoinType NATURAL_INNER_JOIN = JoinType("NATURAL INNER JOIN"); //
553 //         enum JoinType LEFT_OUTER_JOIN = JoinType("LEFT JOIN"); //
554 //         enum JoinType LEFT_SEMI_JOIN = JoinType("LEFT SEMI JOIN"); //
555 //         enum JoinType LEFT_ANTI_JOIN = JoinType("LEFT ANTI JOIN"); //
556 //         enum JoinType RIGHT_OUTER_JOIN = JoinType("RIGHT JOIN"); //
557 //         enum JoinType FULL_OUTER_JOIN = JoinType("FULL JOIN");//
558 //         enum JoinType STRAIGHT_JOIN = JoinType("STRAIGHT_JOIN"); //
559 //         enum JoinType OUTER_APPLY = JoinType("OUTER APPLY");//
560 //         enum JoinType CROSS_APPLY = JoinType("CROSS APPLY");
561 
562 //         public  string name;
563 //         public  string name_lcase;
564 
565 //         this(string name){
566 //             this.name = name;
567 //             this.name_lcase = toLower(name);
568 //         }
569 
570 //         public static string toString(JoinType joinType) {
571 //             return joinType.name;
572 //         }
573 
574 //         bool opEquals(const JoinType h) nothrow {
575 //             return name == h.name ;
576 //         } 
577 
578 //         bool opEquals(ref const JoinType h) nothrow {
579 //             return name == h.name ;
580 //         }
581 //     }