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.dialect.mysql.parser.MySqlSelectParser;
17 
18 import hunt.sql.ast.SQLExpr;
19 import hunt.sql.ast.SQLName;
20 import hunt.sql.ast.SQLObject;
21 import hunt.sql.ast.SQLSetQuantifier;
22 import hunt.sql.ast.expr.SQLIdentifierExpr;
23 import hunt.sql.ast.expr.SQLListExpr;
24 import hunt.sql.ast.expr.SQLLiteralExpr;
25 import hunt.sql.ast.statement;
26 import hunt.sql.dialect.mysql.ast.MySqlForceIndexHint;
27 import hunt.sql.dialect.mysql.ast.MySqlIgnoreIndexHint;
28 import hunt.sql.dialect.mysql.ast.MySqlIndexHint;
29 import hunt.sql.dialect.mysql.ast.MySqlIndexHintImpl;
30 import hunt.sql.dialect.mysql.ast.MySqlUseIndexHint;
31 import hunt.sql.dialect.mysql.ast.expr.MySqlOutFileExpr;
32 import hunt.sql.dialect.mysql.ast.statement.MySqlSelectQueryBlock;
33 import hunt.sql.dialect.mysql.ast.statement.MySqlUpdateStatement;
34 import hunt.sql.dialect.mysql.ast.statement.MySqlUpdateTableSource;
35 import hunt.sql.parser;
36 import hunt.sql.util.FnvHash;
37 import hunt.sql.ast.statement.SQLSelectQuery;
38 import hunt.sql.dialect.mysql.parser.MySqlExprParser;
39 import hunt.Boolean;
40 import hunt.collection;
41 import hunt.sql.ast.SQLCommentHint;
42 import hunt.sql.ast.SQLHint;
43 
44 public class MySqlSelectParser : SQLSelectParser {
45 
46     public bool              returningFlag = false;
47     public MySqlUpdateStatement updateStmt;
48 
49     public this(SQLExprParser exprParser){
50         super(exprParser);
51     }
52 
53     public this(SQLExprParser exprParser, SQLSelectListCache selectListCache){
54         super(exprParser, selectListCache);
55     }
56 
57     public this(string sql){
58         this(new MySqlExprParser(sql));
59     }
60     
61     override public void parseFrom(SQLSelectQueryBlock queryBlock) {
62         if (lexer.token() != Token.FROM) {
63             return;
64         }
65         
66         lexer.nextTokenIdent();
67 
68         if (lexer.token() == Token.UPDATE) { // taobao returning to urgly syntax
69             updateStmt = this.parseUpdateStatment();
70             List!(SQLExpr) returnning = updateStmt.getReturning();
71             foreach(SQLSelectItem item ; queryBlock.getSelectList()) {
72                 SQLExpr itemExpr = item.getExpr();
73                 itemExpr.setParent(updateStmt);
74                 returnning.add(itemExpr);
75             }
76             returningFlag = true;
77             return;
78         }
79         
80         queryBlock.setFrom(parseTableSource());
81     }
82 
83 
84     override
85     public SQLSelectQuery query(SQLObject parent, bool acceptUnion) {
86         if (lexer.token() == Token.LPAREN) {
87             lexer.nextToken();
88 
89             SQLSelectQuery select = super.query(); //@gxc
90             select.setBracket(true);
91             accept(Token.RPAREN);
92 
93             return queryRest(select, acceptUnion);
94         }
95 
96         MySqlSelectQueryBlock queryBlock = new MySqlSelectQueryBlock();
97         queryBlock.setParent(parent);
98 
99         if (lexer.hasComment() && lexer.isKeepComments()) {
100             queryBlock.addBeforeComment(lexer.readAndResetComments());
101         }
102 
103         if (lexer.token() == Token.SELECT) {
104             if (selectListCache !is null) {
105                 selectListCache.match(lexer, queryBlock);
106             }
107         }
108 
109         if (lexer.token() == Token.SELECT) {
110             lexer.nextTokenValue();
111 
112             for(;;) {
113                 if (lexer.token() == Token.HINT) {
114                     this.exprParser.parseHints!(SQLCommentHint)((queryBlock.getHints()));
115                 } else {
116                     break;
117                 }
118             }
119 
120             Token token = lexer.token();
121             if (token == (Token.DISTINCT)) {
122                 queryBlock.setDistionOption(SQLSetQuantifier.DISTINCT);
123                 lexer.nextToken();
124             } else if (lexer.identifierEquals(FnvHash.Constants.DISTINCTROW)) {
125                 queryBlock.setDistionOption(SQLSetQuantifier.DISTINCTROW);
126                 lexer.nextToken();
127             } else if (token == (Token.ALL)) {
128                 queryBlock.setDistionOption(SQLSetQuantifier.ALL);
129                 lexer.nextToken();
130             }
131 
132             if (lexer.identifierEquals(FnvHash.Constants.HIGH_PRIORITY)) {
133                 queryBlock.setHignPriority(true);
134                 lexer.nextToken();
135             }
136 
137             if (lexer.identifierEquals(FnvHash.Constants.STRAIGHT_JOIN)) {
138                 queryBlock.setStraightJoin(true);
139                 lexer.nextToken();
140             }
141 
142             if (lexer.identifierEquals(FnvHash.Constants.SQL_SMALL_RESULT)) {
143                 queryBlock.setSmallResult(true);
144                 lexer.nextToken();
145             }
146 
147             if (lexer.identifierEquals(FnvHash.Constants.SQL_BIG_RESULT)) {
148                 queryBlock.setBigResult(true);
149                 lexer.nextToken();
150             }
151 
152             if (lexer.identifierEquals(FnvHash.Constants.SQL_BUFFER_RESULT)) {
153                 queryBlock.setBufferResult(true);
154                 lexer.nextToken();
155             }
156 
157             if (lexer.identifierEquals(FnvHash.Constants.SQL_CACHE)) {
158                 queryBlock.setCache(new Boolean(true));
159                 lexer.nextToken();
160             }
161 
162             if (lexer.identifierEquals(FnvHash.Constants.SQL_NO_CACHE)) {
163                 queryBlock.setCache(new Boolean(false));
164                 lexer.nextToken();
165             }
166 
167             if (lexer.identifierEquals(FnvHash.Constants.SQL_CALC_FOUND_ROWS)) {
168                 queryBlock.setCalcFoundRows(true);
169                 lexer.nextToken();
170             }
171 
172             parseSelectList(queryBlock);
173 
174             if (lexer.identifierEquals(FnvHash.Constants.FORCE)) {
175                 lexer.nextToken();
176                 accept(Token.PARTITION);
177                 SQLName partition = this.exprParser.name();
178                 queryBlock.setForcePartition(partition);
179             }
180 
181             parseInto(queryBlock);
182         }
183 
184         parseFrom(queryBlock);
185 
186         parseWhere(queryBlock);
187 
188         parseHierachical(queryBlock);
189 
190         parseGroupBy(queryBlock);
191 
192         queryBlock.setOrderBy(this.exprParser.parseOrderBy());
193 
194         if (lexer.token() == Token.LIMIT) {
195             queryBlock.setLimit(this.exprParser.parseLimit());
196         }
197 
198         if (lexer.token() == Token.PROCEDURE) {
199             lexer.nextToken();
200             throw new ParserException("TODO. " ~ lexer.info());
201         }
202 
203         parseInto(queryBlock);
204 
205         if (lexer.token() == Token.FOR) {
206             lexer.nextToken();
207             accept(Token.UPDATE);
208 
209             queryBlock.setForUpdate(true);
210 
211             if (lexer.identifierEquals(FnvHash.Constants.NO_WAIT) || lexer.identifierEquals(FnvHash.Constants.NOWAIT)) {
212                 lexer.nextToken();
213                 queryBlock.setNoWait(true);
214             } else if (lexer.identifierEquals(FnvHash.Constants.WAIT)) {
215                 lexer.nextToken();
216                 SQLExpr waitTime = this.exprParser.primary();
217                 queryBlock.setWaitTime(waitTime);
218             }
219         }
220 
221         if (lexer.token() == Token.LOCK) {
222             lexer.nextToken();
223             accept(Token.IN);
224             acceptIdentifier("SHARE");
225             acceptIdentifier("MODE");
226             queryBlock.setLockInShareMode(true);
227         }
228 
229         return queryRest(queryBlock, acceptUnion);
230     }
231     
232     override public SQLTableSource parseTableSource() {
233         if (lexer.token() == Token.LPAREN) {
234             lexer.nextToken();
235             SQLTableSource tableSource;
236             if (lexer.token() == Token.SELECT || lexer.token() == Token.WITH) {
237                 SQLSelect select = select();
238 
239                 accept(Token.RPAREN);
240 
241                 SQLSelectQuery query = queryRest(select.getQuery());
242                 if (cast(SQLUnionQuery)(query) !is null && select.getWithSubQuery() is null) {
243                     select.getQuery().setBracket(true);
244                     tableSource = new SQLUnionQueryTableSource(cast(SQLUnionQuery) query);
245                 } else {
246                     tableSource = new SQLSubqueryTableSource(select);
247                 }
248             } else if (lexer.token() == Token.LPAREN) {
249                 tableSource = parseTableSource();
250                 accept(Token.RPAREN);
251             } else {
252                 tableSource = parseTableSource();
253                 accept(Token.RPAREN);
254             }
255 
256             return parseTableSourceRest(tableSource);
257         }
258         
259         if(lexer.token() == Token.UPDATE) {
260             SQLTableSource tableSource = new MySqlUpdateTableSource(parseUpdateStatment());
261             return parseTableSourceRest(tableSource);
262         }
263 
264         if (lexer.token() == Token.SELECT) {
265             throw new ParserException("TODO. " ~ lexer.info());
266         }
267 
268         SQLExprTableSource tableReference = new SQLExprTableSource();
269 
270         parseTableSourceQueryTableExpr(tableReference);
271 
272         SQLTableSource tableSrc = parseTableSourceRest(tableReference);
273         
274         if (lexer.hasComment() && lexer.isKeepComments()) {
275             tableSrc.addAfterComment(lexer.readAndResetComments());
276         }
277         
278         return tableSrc;
279     }
280     
281     public MySqlUpdateStatement parseUpdateStatment() {
282         MySqlUpdateStatement update = new MySqlUpdateStatement();
283 
284         lexer.nextToken();
285 
286         if (lexer.identifierEquals(FnvHash.Constants.LOW_PRIORITY)) {
287             lexer.nextToken();
288             update.setLowPriority(true);
289         }
290 
291         if (lexer.identifierEquals(FnvHash.Constants.IGNORE)) {
292             lexer.nextToken();
293             update.setIgnore(true);
294         }
295         
296         if (lexer.identifierEquals(FnvHash.Constants.COMMIT_ON_SUCCESS)) {
297             lexer.nextToken();
298             update.setCommitOnSuccess(true);
299         }
300         
301         if (lexer.identifierEquals(FnvHash.Constants.ROLLBACK_ON_FAIL)) {
302             lexer.nextToken();
303             update.setRollBackOnFail(true);
304         }
305         
306         if (lexer.identifierEquals(FnvHash.Constants.QUEUE_ON_PK)) {
307             lexer.nextToken();
308             update.setQueryOnPk(true);
309         }
310         
311         if (lexer.identifierEquals(FnvHash.Constants.TARGET_AFFECT_ROW)) {
312             lexer.nextToken();
313             SQLExpr targetAffectRow = this.exprParser.expr();
314             update.setTargetAffectRow(targetAffectRow);
315         }
316 
317         if (lexer.identifierEquals(FnvHash.Constants.FORCE)) {
318             lexer.nextToken();
319 
320             if (lexer.token() == Token.ALL) {
321                 lexer.nextToken();
322                 acceptIdentifier("PARTITIONS");
323                 update.setForceAllPartitions(true);
324             } else if (lexer.identifierEquals(FnvHash.Constants.PARTITIONS)){
325                 lexer.nextToken();
326                 update.setForceAllPartitions(true);
327             } else if (lexer.token() == Token.PARTITION) {
328                 lexer.nextToken();
329                 SQLName partition = this.exprParser.name();
330                 update.setForcePartition(partition);
331             } else {
332                 throw new ParserException("TODO. " ~ lexer.info());
333             }
334         }
335 
336         while (lexer.token() == Token.HINT) {
337             this.exprParser.parseHints!(SQLHint)((update.getHints()));
338         }
339 
340         SQLSelectParser selectParser = this.exprParser.createSelectParser();
341         SQLTableSource updateTableSource = selectParser.parseTableSource();
342         update.setTableSource(updateTableSource);
343 
344         accept(Token.SET);
345 
346         for (;;) {
347             SQLUpdateSetItem item = this.exprParser.parseUpdateSetItem();
348             update.addItem(item);
349 
350             if (lexer.token() != Token.COMMA) {
351                 break;
352             }
353 
354             lexer.nextToken();
355         }
356 
357         if (lexer.token() == (Token.WHERE)) {
358             lexer.nextToken();
359             update.setWhere(this.exprParser.expr());
360         }
361 
362         update.setOrderBy(this.exprParser.parseOrderBy());
363         update.setLimit(this.exprParser.parseLimit());
364         
365         return update;
366     }
367     
368     protected void parseInto(SQLSelectQueryBlock queryBlock) {
369         if (lexer.token() == (Token.INTO)) {
370             lexer.nextToken();
371 
372             if (lexer.identifierEquals("OUTFILE")) {
373                 lexer.nextToken();
374 
375                 MySqlOutFileExpr outFile = new MySqlOutFileExpr();
376                 outFile.setFile(expr());
377 
378                 queryBlock.setInto(outFile);
379 
380                 if (lexer.identifierEquals("FIELDS") || lexer.identifierEquals("COLUMNS")) {
381                     lexer.nextToken();
382 
383                     if (lexer.identifierEquals("TERMINATED")) {
384                         lexer.nextToken();
385                         accept(Token.BY);
386                     }
387                     outFile.setColumnsTerminatedBy(expr());
388 
389                     if (lexer.identifierEquals("OPTIONALLY")) {
390                         lexer.nextToken();
391                         outFile.setColumnsEnclosedOptionally(true);
392                     }
393 
394                     if (lexer.identifierEquals("ENCLOSED")) {
395                         lexer.nextToken();
396                         accept(Token.BY);
397                         outFile.setColumnsEnclosedBy(cast(SQLLiteralExpr) expr());
398                     }
399 
400                     if (lexer.identifierEquals("ESCAPED")) {
401                         lexer.nextToken();
402                         accept(Token.BY);
403                         outFile.setColumnsEscaped(cast(SQLLiteralExpr) expr());
404                     }
405                 }
406 
407                 if (lexer.identifierEquals("LINES")) {
408                     lexer.nextToken();
409 
410                     if (lexer.identifierEquals("STARTING")) {
411                         lexer.nextToken();
412                         accept(Token.BY);
413                         outFile.setLinesStartingBy(cast(SQLLiteralExpr) expr());
414                     } else {
415                         lexer.identifierEquals("TERMINATED");
416                         lexer.nextToken();
417                         accept(Token.BY);
418                         outFile.setLinesTerminatedBy(cast(SQLLiteralExpr) expr());
419                     }
420                 }
421             } else {
422                 SQLExpr intoExpr = this.exprParser.name();
423                 if (lexer.token() == Token.COMMA) {
424                     SQLListExpr list = new SQLListExpr();
425                     list.addItem(intoExpr);
426 
427                     while (lexer.token() == Token.COMMA) {
428                         lexer.nextToken();
429                         SQLName name = this.exprParser.name();
430                         list.addItem(name);
431                     }
432 
433                     intoExpr = list;
434                 }
435                 queryBlock.setInto(intoExpr);
436             }
437         }
438     }
439 
440     override protected SQLTableSource primaryTableSourceRest(SQLTableSource tableSource) {
441         parseIndexHintList(tableSource);
442 
443         if (lexer.token() == Token.PARTITION) {
444             lexer.nextToken();
445             accept(Token.LPAREN);
446             this.exprParser.names((cast(SQLExprTableSource) tableSource).getPartitions(), tableSource);
447             accept(Token.RPAREN);
448         }
449 
450         return tableSource;
451     }
452 
453     override protected SQLTableSource parseTableSourceRest(SQLTableSource tableSource) {
454         if (lexer.identifierEquals(FnvHash.Constants.USING)) {
455             return tableSource;
456         }
457 
458         parseIndexHintList(tableSource);
459         
460         if (lexer.token() == Token.PARTITION) {
461             lexer.nextToken();
462             accept(Token.LPAREN);
463             this.exprParser.names((cast(SQLExprTableSource) tableSource).getPartitions(), tableSource);
464             accept(Token.RPAREN);
465         }
466 
467         return super.parseTableSourceRest(tableSource);
468     }
469 
470     private void parseIndexHintList(SQLTableSource tableSource) {
471 	if (lexer.token() == Token.USE) {
472             lexer.nextToken();
473             MySqlUseIndexHint hint = new MySqlUseIndexHint();
474             parseIndexHint(hint);
475             tableSource.getHints().add(hint);
476 	    parseIndexHintList(tableSource);
477         }
478 
479         if (lexer.identifierEquals(FnvHash.Constants.IGNORE)) {
480             lexer.nextToken();
481             MySqlIgnoreIndexHint hint = new MySqlIgnoreIndexHint();
482             parseIndexHint(hint);
483             tableSource.getHints().add(hint);
484 	    parseIndexHintList(tableSource);
485         }
486 
487         if (lexer.identifierEquals(FnvHash.Constants.FORCE)) {
488             lexer.nextToken();
489             MySqlForceIndexHint hint = new MySqlForceIndexHint();
490             parseIndexHint(hint);
491             tableSource.getHints().add(hint);
492 	    parseIndexHintList(tableSource);
493         }
494     }
495 
496     private void parseIndexHint(MySqlIndexHintImpl hint) {
497         if (lexer.token() == Token.INDEX) {
498             lexer.nextToken();
499         } else {
500             accept(Token.KEY);
501         }
502 
503         if (lexer.token() == Token.FOR) {
504             lexer.nextToken();
505 
506             if (lexer.token() == Token.JOIN) {
507                 lexer.nextToken();
508                 hint.setOption(MySqlIndexHint.Option.JOIN);
509             } else if (lexer.token() == Token.ORDER) {
510                 lexer.nextToken();
511                 accept(Token.BY);
512                 hint.setOption(MySqlIndexHint.Option.ORDER_BY);
513             } else {
514                 accept(Token.GROUP);
515                 accept(Token.BY);
516                 hint.setOption(MySqlIndexHint.Option.GROUP_BY);
517             }
518         }
519 
520         accept(Token.LPAREN);
521         if (lexer.token() == Token.PRIMARY) {
522             lexer.nextToken();
523             hint.getIndexList().add(new SQLIdentifierExpr("PRIMARY"));
524         } else {
525             this.exprParser.names(hint.getIndexList());
526         }
527         accept(Token.RPAREN);
528     }
529 
530     override public SQLUnionQuery unionRest(SQLUnionQuery union_p) {
531         if (lexer.token() == Token.LIMIT) {
532             union_p.setLimit(this.exprParser.parseLimit());
533         }
534         return super.unionRest(union_p);
535     }
536 
537     public MySqlExprParser getExprParser() {
538         return cast(MySqlExprParser) exprParser;
539     }
540 }