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.MySqlSelectIntoParser;
17 
18 
19 import hunt.collection;
20 
21 import hunt.sql.ast.SQLExpr;
22 import hunt.sql.ast.SQLSetQuantifier;
23 import hunt.sql.ast.expr.SQLIdentifierExpr;
24 import hunt.sql.ast.expr.SQLLiteralExpr;
25 import hunt.sql.ast.expr.SQLVariantRefExpr;
26 import hunt.sql.ast.statement.SQLSelect;
27 import hunt.sql.ast.statement.SQLSelectQuery;
28 import hunt.sql.ast.statement.SQLSelectQueryBlock;
29 import hunt.sql.ast.statement.SQLTableSource;
30 import hunt.sql.ast.statement.SQLUnionQuery;
31 import hunt.sql.dialect.mysql.ast.MySqlForceIndexHint;
32 import hunt.sql.dialect.mysql.ast.MySqlIgnoreIndexHint;
33 import hunt.sql.dialect.mysql.ast.MySqlIndexHint;
34 import hunt.sql.dialect.mysql.ast.MySqlIndexHintImpl;
35 import hunt.sql.dialect.mysql.ast.MySqlUseIndexHint;
36 import hunt.sql.dialect.mysql.ast.clause.MySqlSelectIntoStatement;
37 import hunt.sql.dialect.mysql.ast.expr.MySqlOutFileExpr;
38 import hunt.sql.dialect.mysql.ast.statement.MySqlSelectQueryBlock;
39 import hunt.sql.parser.ParserException;
40 import hunt.sql.parser.SQLExprParser;
41 import hunt.sql.parser.SQLSelectParser;
42 import hunt.sql.parser.Token;
43 import hunt.sql.dialect.mysql.parser.MySqlExprParser;
44 import hunt.sql.ast.SQLObject;
45 import hunt.Boolean;
46 import hunt.sql.ast.SQLCommentHint;
47 
48 public class MySqlSelectIntoParser : SQLSelectParser {
49 	private List!(SQLExpr) argsList;
50 
51     public this(SQLExprParser exprParser){
52         super(exprParser);
53     }
54 
55     public this(string sql){
56         this(new MySqlExprParser(sql));
57     }
58     
59     public MySqlSelectIntoStatement parseSelectInto()
60     {
61     	SQLSelect select=select();
62     	MySqlSelectIntoStatement stmt=new MySqlSelectIntoStatement();
63     	stmt.setSelect(select);
64     	stmt.setVarList(argsList);
65     	return stmt;
66     	
67     }
68 
69     override
70     public SQLSelectQuery query() {
71         if (lexer.token() == (Token.LPAREN)) {
72             lexer.nextToken();
73 
74             SQLSelectQuery select = query();
75             accept(Token.RPAREN);
76 
77             return queryRest(select);
78         }
79 
80         MySqlSelectQueryBlock queryBlock = new MySqlSelectQueryBlock();
81 
82         if (lexer.token() == Token.SELECT) {
83             lexer.nextToken();
84 
85             if (lexer.token() == Token.HINT) {
86                 this.exprParser.parseHints!(SQLCommentHint)((queryBlock.getHints()));
87             }
88 
89             if (lexer.token() == Token.COMMENT) {
90                 lexer.nextToken();
91             }
92 
93             if (lexer.token() == (Token.DISTINCT)) {
94                 queryBlock.setDistionOption(SQLSetQuantifier.DISTINCT);
95                 lexer.nextToken();
96             } else if (lexer.identifierEquals("DISTINCTROW")) {
97                 queryBlock.setDistionOption(SQLSetQuantifier.DISTINCTROW);
98                 lexer.nextToken();
99             } else if (lexer.token() == (Token.ALL)) {
100                 queryBlock.setDistionOption(SQLSetQuantifier.ALL);
101                 lexer.nextToken();
102             }
103 
104             if (lexer.identifierEquals("HIGH_PRIORITY")) {
105                 queryBlock.setHignPriority(true);
106                 lexer.nextToken();
107             }
108 
109             if (lexer.identifierEquals("STRAIGHT_JOIN")) {
110                 queryBlock.setStraightJoin(true);
111                 lexer.nextToken();
112             }
113 
114             if (lexer.identifierEquals("SQL_SMALL_RESULT")) {
115                 queryBlock.setSmallResult(true);
116                 lexer.nextToken();
117             }
118 
119             if (lexer.identifierEquals("SQL_BIG_RESULT")) {
120                 queryBlock.setBigResult(true);
121                 lexer.nextToken();
122             }
123 
124             if (lexer.identifierEquals("SQL_BUFFER_RESULT")) {
125                 queryBlock.setBufferResult(true);
126                 lexer.nextToken();
127             }
128 
129             if (lexer.identifierEquals("SQL_CACHE")) {
130                 queryBlock.setCache(new Boolean(true));
131                 lexer.nextToken();
132             }
133 
134             if (lexer.identifierEquals("SQL_NO_CACHE")) {
135                 queryBlock.setCache(new Boolean(false));
136                 lexer.nextToken();
137             }
138 
139             if (lexer.identifierEquals("SQL_CALC_FOUND_ROWS")) {
140                 queryBlock.setCalcFoundRows(true);
141                 lexer.nextToken();
142             }
143 
144             parseSelectList(queryBlock);
145             
146             argsList=parseIntoArgs();
147         }
148 
149         parseFrom(queryBlock);
150 
151         parseWhere(queryBlock);
152 
153         parseGroupBy(queryBlock);
154 
155         queryBlock.setOrderBy(this.exprParser.parseOrderBy());
156 
157         if (lexer.token() == Token.LIMIT) {
158             queryBlock.setLimit(this.exprParser.parseLimit());
159         }
160 
161         if (lexer.token() == Token.PROCEDURE) {
162             lexer.nextToken();
163             throw new ParserException("TODO. " ~ lexer.info());
164         }
165 
166         parseInto(queryBlock);
167 
168         if (lexer.token() == Token.FOR) {
169             lexer.nextToken();
170             accept(Token.UPDATE);
171 
172             queryBlock.setForUpdate(true);
173         }
174 
175         if (lexer.token() == Token.LOCK) {
176             lexer.nextToken();
177             accept(Token.IN);
178             acceptIdentifier("SHARE");
179             acceptIdentifier("MODE");
180             queryBlock.setLockInShareMode(true);
181         }
182 
183         return queryRest(queryBlock);
184     }
185     /**
186      * parser the select into arguments
187      * @return
188      */
189 	protected List!(SQLExpr) parseIntoArgs() {
190 		
191 		List!(SQLExpr) args=new ArrayList!(SQLExpr)();
192 		if (lexer.token() == (Token.INTO)) {
193 			accept(Token.INTO);
194 			//lexer.nextToken();
195 			for (;;) {
196 				SQLExpr var = exprParser.primary();
197 				if (cast(SQLIdentifierExpr)(var) !is null) {
198 					var = new SQLVariantRefExpr(
199 							(cast(SQLIdentifierExpr) var).getName());
200 				}
201 				args.add(var);
202 				if (lexer.token() == Token.COMMA) {
203 					accept(Token.COMMA);
204 					continue;
205 				}
206 				else
207 				{
208 					break;
209 				}
210 			}
211 		}
212 		return args;
213 	}
214     
215     
216     protected void parseInto(SQLSelectQueryBlock queryBlock) {
217         if (lexer.token() == (Token.INTO)) {
218             lexer.nextToken();
219 
220             if (lexer.identifierEquals("OUTFILE")) {
221                 lexer.nextToken();
222 
223                 MySqlOutFileExpr outFile = new MySqlOutFileExpr();
224                 outFile.setFile(expr());
225 
226                 queryBlock.setInto(outFile);
227 
228                 if (lexer.identifierEquals("FIELDS") || lexer.identifierEquals("COLUMNS")) {
229                     lexer.nextToken();
230 
231                     if (lexer.identifierEquals("TERMINATED")) {
232                         lexer.nextToken();
233                         accept(Token.BY);
234                     }
235                     outFile.setColumnsTerminatedBy(cast(SQLLiteralExpr) expr());
236 
237                     if (lexer.identifierEquals("OPTIONALLY")) {
238                         lexer.nextToken();
239                         outFile.setColumnsEnclosedOptionally(true);
240                     }
241 
242                     if (lexer.identifierEquals("ENCLOSED")) {
243                         lexer.nextToken();
244                         accept(Token.BY);
245                         outFile.setColumnsEnclosedBy(cast(SQLLiteralExpr) expr());
246                     }
247 
248                     if (lexer.identifierEquals("ESCAPED")) {
249                         lexer.nextToken();
250                         accept(Token.BY);
251                         outFile.setColumnsEscaped(cast(SQLLiteralExpr) expr());
252                     }
253                 }
254 
255                 if (lexer.identifierEquals("LINES")) {
256                     lexer.nextToken();
257 
258                     if (lexer.identifierEquals("STARTING")) {
259                         lexer.nextToken();
260                         accept(Token.BY);
261                         outFile.setLinesStartingBy(cast(SQLLiteralExpr) expr());
262                     } else {
263                         lexer.identifierEquals("TERMINATED");
264                         lexer.nextToken();
265                         accept(Token.BY);
266                         outFile.setLinesTerminatedBy(cast(SQLLiteralExpr) expr());
267                     }
268                 }
269             } else {
270                 queryBlock.setInto(this.exprParser.name());
271             }
272         }
273     }
274 
275     override protected SQLTableSource parseTableSourceRest(SQLTableSource tableSource) {
276         if (lexer.identifierEquals("USING")) {
277             return tableSource;
278         }
279 
280         parseIndexHintList(tableSource);
281 	
282         return super.parseTableSourceRest(tableSource);
283     }
284 
285     private void parseIndexHintList(SQLTableSource tableSource) {
286 	if (lexer.token() == Token.USE) {
287             lexer.nextToken();
288             MySqlUseIndexHint hint = new MySqlUseIndexHint();
289             parseIndexHint(hint);
290             tableSource.getHints().add(hint);
291 	    parseIndexHintList(tableSource);
292         }
293 
294         if (lexer.identifierEquals("IGNORE")) {
295             lexer.nextToken();
296             MySqlIgnoreIndexHint hint = new MySqlIgnoreIndexHint();
297             parseIndexHint(hint);
298             tableSource.getHints().add(hint);
299 	    parseIndexHintList(tableSource);
300         }
301 
302         if (lexer.identifierEquals("FORCE")) {
303             lexer.nextToken();
304             MySqlForceIndexHint hint = new MySqlForceIndexHint();
305             parseIndexHint(hint);
306             tableSource.getHints().add(hint);
307 	    parseIndexHintList(tableSource);
308         }
309     }
310 
311     private void parseIndexHint(MySqlIndexHintImpl hint) {
312         if (lexer.token() == Token.INDEX) {
313             lexer.nextToken();
314         } else {
315             accept(Token.KEY);
316         }
317 
318         if (lexer.token() == Token.FOR) {
319             lexer.nextToken();
320 
321             if (lexer.token() == Token.JOIN) {
322                 lexer.nextToken();
323                 hint.setOption(MySqlIndexHint.Option.JOIN);
324             } else if (lexer.token() == Token.ORDER) {
325                 lexer.nextToken();
326                 accept(Token.BY);
327                 hint.setOption(MySqlIndexHint.Option.ORDER_BY);
328             } else {
329                 accept(Token.GROUP);
330                 accept(Token.BY);
331                 hint.setOption(MySqlIndexHint.Option.GROUP_BY);
332             }
333         }
334 
335         accept(Token.LPAREN);
336         if (lexer.token() == Token.PRIMARY) {
337             lexer.nextToken();
338             hint.getIndexList().add(new SQLIdentifierExpr("PRIMARY"));
339         } else {
340             this.exprParser.names(hint.getIndexList());
341         }
342         accept(Token.RPAREN);
343     }
344 
345     override public SQLUnionQuery unionRest(SQLUnionQuery union_p) {
346         if (lexer.token() == Token.LIMIT) {
347             union_p.setLimit(this.exprParser.parseLimit());
348         }
349         return super.unionRest(union_p);
350     }
351     
352     public MySqlExprParser getExprParser() {
353         return cast(MySqlExprParser) exprParser;
354     }
355 }