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.MySqlCreateTableParser;
17 
18 import hunt.sql.ast.SQLDataTypeImpl;
19 import hunt.sql.ast.SQLExpr;
20 import hunt.sql.ast.SQLName;
21 import hunt.sql.ast.SQLOrderingSpecification;
22 import hunt.sql.ast.SQLPartition;
23 import hunt.sql.ast.SQLPartitionBy;
24 import hunt.sql.ast.SQLPartitionByHash;
25 import hunt.sql.ast.SQLPartitionByList;
26 import hunt.sql.ast.SQLPartitionByRange;
27 import hunt.sql.ast.SQLSubPartitionBy;
28 import hunt.sql.ast.SQLSubPartitionByHash;
29 import hunt.sql.ast.expr.SQLIdentifierExpr;
30 import hunt.sql.ast.expr.SQLIntegerExpr;
31 import hunt.sql.ast.expr.SQLNumberExpr;
32 import hunt.sql.ast.statement;
33 import hunt.sql.dialect.mysql.ast.MySqlKey;
34 import hunt.sql.dialect.mysql.ast.MySqlPrimaryKey;
35 import hunt.sql.dialect.mysql.ast.MySqlUnique;
36 import hunt.sql.dialect.mysql.ast.MysqlForeignKey;
37 import hunt.sql.dialect.mysql.ast.expr.MySqlOrderingExpr;
38 import hunt.sql.dialect.mysql.ast.statement.MySqlCreateTableStatement;
39 // import hunt.sql.dialect.mysql.ast.statement.MySqlCreateTableStatement.TableSpaceOption;
40 import hunt.sql.dialect.mysql.ast.statement.MySqlPartitionByKey;
41 import hunt.sql.dialect.mysql.ast.statement.MySqlSubPartitionByKey;
42 import hunt.sql.dialect.mysql.ast.statement.MySqlSubPartitionByList;
43 import hunt.sql.dialect.mysql.ast.statement.MySqlTableIndex;
44 import hunt.sql.parser;
45 import hunt.sql.util.FnvHash;
46 import hunt.sql.dialect.mysql.parser.MySqlExprParser;
47 import hunt.sql.dialect.mysql.parser.MySqlSelectParser;
48 import hunt.String;
49 import hunt.Boolean;
50 import hunt.collection;
51 import hunt.sql.ast.SQLObject;
52 import hunt.text;
53 import hunt.Number;
54 import hunt.sql.ast.SQLCommentHint;
55 
56 public class MySqlCreateTableParser : SQLCreateTableParser {
57 
58     public this(string sql){
59         super(new MySqlExprParser(sql));
60     }
61 
62     public this(SQLExprParser exprParser){
63         super(exprParser);
64     }
65 
66     override public SQLCreateTableStatement parseCreateTable() {
67         return parseCreateTable(true);
68     }
69 
70     override public MySqlExprParser getExprParser() {
71         return cast(MySqlExprParser) exprParser;
72     }
73 
74     override public MySqlCreateTableStatement parseCreateTable(bool acceptCreate) {
75         MySqlCreateTableStatement stmt = new MySqlCreateTableStatement();
76         if (acceptCreate) {
77             if (lexer.hasComment() && lexer.isKeepComments()) {
78                 stmt.addBeforeComment(lexer.readAndResetComments());
79             }
80             accept(Token.CREATE);
81         }
82 
83         if (lexer.identifierEquals("TEMPORARY")) {
84             lexer.nextToken();
85             stmt.setType(SQLCreateTableStatement.Type.GLOBAL_TEMPORARY);
86         }
87 
88         accept(Token.TABLE);
89 
90         if (lexer.token() == Token.IF || lexer.identifierEquals("IF")) {
91             lexer.nextToken();
92             accept(Token.NOT);
93             accept(Token.EXISTS);
94 
95             stmt.setIfNotExiists(true);
96         }
97 
98         stmt.setName(this.exprParser.name());
99 
100         if (lexer.token() == Token.LIKE) {
101             lexer.nextTokenValue();
102             SQLName name = this.exprParser.name();
103             stmt.setLike(name);
104         }
105 
106         if (lexer.token() == (Token.LPAREN)) {
107             lexer.nextToken();
108 
109             if (lexer.token() == Token.LIKE) {
110                 lexer.nextTokenValue();
111                 SQLName name = this.exprParser.name();
112                 stmt.setLike(name);
113             } else if (lexer.token() == Token.SELECT) {
114                 SQLSelect query = new MySqlSelectParser(this.exprParser).select();
115                 stmt.setSelect(query);
116             } else {
117                 for (;;) {
118                     if (lexer.token() == Token.FULLTEXT) {
119                         Lexer.SavePoint mark = lexer.mark();
120                         lexer.nextToken();
121                         if (lexer.token() == Token.KEY) {
122                             MySqlKey fulltextKey = cast(MySqlKey) parseConstraint();
123                             fulltextKey.setIndexType("FULLTEXT");
124                             stmt.getTableElementList().add(fulltextKey);
125                             if (lexer.token() == Token.RPAREN) {
126                                 break;
127                             } else if (lexer.token() == Token.COMMA) {
128                                 lexer.nextToken();
129                                 continue;
130                             }
131                         } else if (lexer.token() == Token.INDEX) {
132                             lexer.nextToken();
133                             MySqlTableIndex idx = new MySqlTableIndex();
134                             idx.setIndexType("FULLTEXT");
135                             idx.setName(this.exprParser.name());
136 
137                             accept(Token.LPAREN);
138                             for (; ; ) {
139                                 idx.addColumn(this.exprParser.parseSelectOrderByItem());
140                                 if (!(lexer.token() == (Token.COMMA))) {
141                                     break;
142                                 } else {
143                                     lexer.nextToken();
144                                 }
145                             }
146                             stmt.getTableElementList().add(idx);
147                             accept(Token.RPAREN);
148                             if (lexer.token() == Token.RPAREN) {
149                                 break;
150                             } else if (lexer.token() == Token.COMMA) {
151                                 lexer.nextToken();
152                                 continue;
153                             }
154                         } else {
155                             MySqlTableIndex idx = new MySqlTableIndex();
156                             idx.setIndexType("FULLTEXT");
157                             idx.setName(this.exprParser.name());
158 
159                             accept(Token.LPAREN);
160                             for (; ; ) {
161                                 idx.addColumn(this.exprParser.parseSelectOrderByItem());
162                                 if (!(lexer.token() == (Token.COMMA))) {
163                                     break;
164                                 } else {
165                                     lexer.nextToken();
166                                 }
167                             }
168                             stmt.getTableElementList().add(idx);
169                             accept(Token.RPAREN);
170                             if (lexer.token() == Token.RPAREN) {
171                                 break;
172                             } else if (lexer.token() == Token.COMMA) {
173                                 lexer.nextToken();
174                                 continue;
175                             }
176                         }
177                     } else if (lexer.identifierEquals(FnvHash.Constants.SPATIAL)) {
178                         Lexer.SavePoint mark = lexer.mark();
179                         lexer.nextToken();
180                         if (lexer.token() == Token.INDEX) {
181                             lexer.nextToken();
182                             MySqlTableIndex idx = new MySqlTableIndex();
183                             idx.setIndexType("SPATIAL");
184 
185                             if (lexer.token() == Token.IDENTIFIER) {
186                                 if (!equalsIgnoreCase("USING",lexer.stringVal())) {
187                                     idx.setName(this.exprParser.name());
188                                 }
189                             }
190 
191                             if (lexer.identifierEquals("USING")) {
192                                 lexer.nextToken();
193                                 idx.setIndexType(lexer.stringVal());
194                                 lexer.nextToken();
195                             }
196 
197                             accept(Token.LPAREN);
198                             for (;;) {
199                                 idx.addColumn(this.exprParser.parseSelectOrderByItem());
200                                 if (!(lexer.token() == (Token.COMMA))) {
201                                     break;
202                                 } else {
203                                     lexer.nextToken();
204                                 }
205                             }
206                             accept(Token.RPAREN);
207 
208                             if (lexer.identifierEquals("USING")) {
209                                 lexer.nextToken();
210                                 idx.setIndexType(lexer.stringVal());
211                                 lexer.nextToken();
212                             }
213 
214                             stmt.getTableElementList().add(idx);
215 
216                             if (lexer.token() == Token.RPAREN) {
217                                 break;
218                             } else if (lexer.token() == Token.COMMA) {
219                                 lexer.nextToken();
220                                 continue;
221                             }
222                         } else {
223                             lexer.reset(mark);
224                         }
225                     }
226 
227                     SQLColumnDefinition column = null;
228                     if (lexer.token() == Token.IDENTIFIER //
229                         || lexer.token() == Token.LITERAL_CHARS) {
230                         column = this.exprParser.parseColumn();
231                         stmt.getTableElementList().add(column);
232 
233                         if (lexer.isKeepComments() && lexer.hasComment()) {
234                             column.addAfterComment(lexer.readAndResetComments());
235                         }
236                     } else if (lexer.token() == Token.CONSTRAINT //
237                                || lexer.token() == Token.PRIMARY //
238                                || lexer.token() == Token.UNIQUE) {
239                         SQLTableConstraint constraint = this.parseConstraint();
240                         constraint.setParent(stmt);
241                         stmt.getTableElementList().add(constraint);
242                     } else if (lexer.token() == (Token.INDEX)) {
243                         lexer.nextToken();
244 
245                         MySqlTableIndex idx = new MySqlTableIndex();
246 
247                         if (lexer.token() == Token.IDENTIFIER) {
248                             if (!equalsIgnoreCase("USING",lexer.stringVal())) {
249                                 idx.setName(this.exprParser.name());
250                             }
251                         }
252 
253                         if (lexer.identifierEquals("USING")) {
254                             lexer.nextToken();
255                             idx.setIndexType(lexer.stringVal());
256                             lexer.nextToken();
257                         }
258 
259                         accept(Token.LPAREN);
260                         for (;;) {
261                             idx.addColumn(this.exprParser.parseSelectOrderByItem());
262                             if (!(lexer.token() == (Token.COMMA))) {
263                                 break;
264                             } else {
265                                 lexer.nextToken();
266                             }
267                         }
268                         accept(Token.RPAREN);
269                         
270                         if (lexer.identifierEquals("USING")) {
271                             lexer.nextToken();
272                             idx.setIndexType(lexer.stringVal());
273                             lexer.nextToken();
274                         }
275 
276                         stmt.getTableElementList().add(idx);
277                     } else if (lexer.token() == (Token.KEY)) {
278                         Lexer.SavePoint savePoint = lexer.mark();
279                         lexer.nextToken();
280 
281                         bool isColumn = false;
282                         if (lexer.identifierEquals(FnvHash.Constants.VARCHAR)) {
283                             isColumn = true;
284                         }
285                         lexer.reset(savePoint);
286 
287                         if (isColumn) {
288                             column = this.exprParser.parseColumn();
289                             stmt.getTableElementList().add(column);
290                         } else {
291                             stmt.getTableElementList().add(parseConstraint());
292                         }
293                     } else if (lexer.token() == (Token.PRIMARY)) {
294                         SQLTableConstraint pk = parseConstraint();
295                         pk.setParent(stmt);
296                         stmt.getTableElementList().add(pk);
297                     } else if (lexer.token() == (Token.FOREIGN)) {
298                         SQLForeignKeyConstraint fk = this.getExprParser().parseForeignKey();
299                         fk.setParent(stmt);
300                         stmt.getTableElementList().add(fk);
301                     } else if (lexer.token() == Token.CHECK) {
302                         SQLCheck check = this.exprParser.parseCheck();
303                         stmt.getTableElementList().add(check);
304                     } else {
305                         column = this.exprParser.parseColumn();
306                         stmt.getTableElementList().add(column);
307                     }
308 
309                     if (lexer.token() != Token.COMMA) {
310                         break;
311                     } else {
312                         lexer.nextToken();
313                         if (lexer.isKeepComments() && lexer.hasComment() && column !is null) {
314                             column.addAfterComment(lexer.readAndResetComments());
315                         }
316                     }
317                 }
318             }
319 
320             accept(Token.RPAREN);
321         }
322 
323         for (;;) {
324             if (lexer.token() == Token.COMMA) {
325                 lexer.nextToken();
326             }
327 
328             if (lexer.identifierEquals("ENGINE")) {
329                 lexer.nextToken();
330                 if (lexer.token() == Token.EQ) {
331                     lexer.nextToken();
332                 }
333 
334                 SQLExpr expr = null;
335                 if (lexer.token() == Token.MERGE) {
336                     expr = new SQLIdentifierExpr(lexer.stringVal());
337                     lexer.nextToken();
338                 } else {
339                     expr = this.exprParser.expr();
340                 }
341                 stmt.getTableOptions().put("ENGINE", expr);
342                 continue;
343             }
344 
345             if (lexer.identifierEquals("AUTO_INCREMENT")) {
346                 lexer.nextToken();
347                 if (lexer.token() == Token.EQ) {
348                     lexer.nextToken();
349                 }
350                 stmt.getTableOptions().put("AUTO_INCREMENT", this.exprParser.expr());
351                 continue;
352             }
353 
354             if (lexer.identifierEquals("AVG_ROW_LENGTH")) {
355                 lexer.nextToken();
356                 if (lexer.token() == Token.EQ) {
357                     lexer.nextToken();
358                 }
359                 stmt.getTableOptions().put("AVG_ROW_LENGTH", this.exprParser.expr());
360                 continue;
361             }
362 
363             if (lexer.token() == Token.DEFAULT) {
364                 lexer.nextToken();
365                 parseTableOptionCharsetOrCollate(stmt);
366                 continue;
367             }
368 
369             if (parseTableOptionCharsetOrCollate(stmt)) {
370                 continue;
371             }
372 
373             if (lexer.identifierEquals("CHECKSUM")) {
374                 lexer.nextToken();
375                 if (lexer.token() == Token.EQ) {
376                     lexer.nextToken();
377                 }
378                 stmt.getTableOptions().put("CHECKSUM", this.exprParser.expr());
379                 continue;
380             }
381 
382             if (lexer.token() == Token.COMMENT) {
383                 lexer.nextToken();
384                 if (lexer.token() == Token.EQ) {
385                     lexer.nextToken();
386                 }
387                 stmt.setComment(this.exprParser.expr());
388                 continue;
389             }
390 
391             if (lexer.identifierEquals("CONNECTION")) {
392                 lexer.nextToken();
393                 if (lexer.token() == Token.EQ) {
394                     lexer.nextToken();
395                 }
396                 stmt.getTableOptions().put("CONNECTION", this.exprParser.expr());
397                 continue;
398             }
399 
400             if (lexer.identifierEquals("DATA")) {
401                 lexer.nextToken();
402                 acceptIdentifier("DIRECTORY");
403                 if (lexer.token() == Token.EQ) {
404                     lexer.nextToken();
405                 }
406                 stmt.getTableOptions().put("DATA DIRECTORY", this.exprParser.expr());
407                 continue;
408             }
409 
410             if (lexer.identifierEquals("DELAY_KEY_WRITE")) {
411                 lexer.nextToken();
412                 if (lexer.token() == Token.EQ) {
413                     lexer.nextToken();
414                 }
415                 stmt.getTableOptions().put("DELAY_KEY_WRITE", this.exprParser.expr());
416                 continue;
417             }
418 
419             if (lexer.identifierEquals("INDEX")) {
420                 lexer.nextToken();
421                 acceptIdentifier("DIRECTORY");
422                 if (lexer.token() == Token.EQ) {
423                     lexer.nextToken();
424                 }
425                 stmt.getTableOptions().put("INDEX DIRECTORY", this.exprParser.expr());
426                 continue;
427             }
428 
429             if (lexer.identifierEquals("INSERT_METHOD")) {
430                 lexer.nextToken();
431                 if (lexer.token() == Token.EQ) {
432                     lexer.nextToken();
433                 }
434                 stmt.getTableOptions().put("INSERT_METHOD", this.exprParser.expr());
435                 continue;
436             }
437 
438             if (lexer.identifierEquals("KEY_BLOCK_SIZE")) {
439                 lexer.nextToken();
440                 if (lexer.token() == Token.EQ) {
441                     lexer.nextToken();
442                 }
443                 stmt.getTableOptions().put("KEY_BLOCK_SIZE", this.exprParser.expr());
444                 continue;
445             }
446 
447             if (lexer.identifierEquals("MAX_ROWS")) {
448                 lexer.nextToken();
449                 if (lexer.token() == Token.EQ) {
450                     lexer.nextToken();
451                 }
452                 stmt.getTableOptions().put("MAX_ROWS", this.exprParser.expr());
453                 continue;
454             }
455 
456             if (lexer.identifierEquals("MIN_ROWS")) {
457                 lexer.nextToken();
458                 if (lexer.token() == Token.EQ) {
459                     lexer.nextToken();
460                 }
461                 stmt.getTableOptions().put("MIN_ROWS", this.exprParser.expr());
462                 continue;
463             }
464 
465             if (lexer.identifierEquals("PACK_KEYS")) {
466                 lexer.nextToken();
467                 if (lexer.token() == Token.EQ) {
468                     lexer.nextToken();
469                 }
470                 stmt.getTableOptions().put("PACK_KEYS", this.exprParser.expr());
471                 continue;
472             }
473 
474             if (lexer.identifierEquals("PASSWORD")) {
475                 lexer.nextToken();
476                 if (lexer.token() == Token.EQ) {
477                     lexer.nextToken();
478                 }
479                 stmt.getTableOptions().put("PASSWORD", this.exprParser.expr());
480                 continue;
481             }
482 
483             if (lexer.identifierEquals("ROW_FORMAT")) {
484                 lexer.nextToken();
485                 if (lexer.token() == Token.EQ) {
486                     lexer.nextToken();
487                 }
488                 stmt.getTableOptions().put("ROW_FORMAT", this.exprParser.expr());
489                 continue;
490             }
491 
492             if (lexer.identifierEquals("STATS_AUTO_RECALC")) {
493                 lexer.nextToken();
494                 if (lexer.token() == Token.EQ) {
495                     lexer.nextToken();
496                 }
497 
498                 stmt.getTableOptions().put("STATS_AUTO_RECALC", this.exprParser.expr());
499                 continue;
500             }
501 
502             if (lexer.identifierEquals("STATS_PERSISTENT")) {
503                 lexer.nextToken();
504                 if (lexer.token() == Token.EQ) {
505                     lexer.nextToken();
506                 }
507 
508                 stmt.getTableOptions().put("STATS_PERSISTENT", this.exprParser.expr());
509                 continue;
510             }
511 
512             if (lexer.identifierEquals("STATS_SAMPLE_PAGES")) {
513                 lexer.nextToken();
514                 if (lexer.token() == Token.EQ) {
515                     lexer.nextToken();
516                 }
517 
518                 stmt.getTableOptions().put("STATS_SAMPLE_PAGES", this.exprParser.expr());
519                 continue;
520             }
521 
522             if (lexer.token() == Token.UNION) {
523                 lexer.nextToken();
524                 if (lexer.token() == Token.EQ) {
525                     lexer.nextToken();
526                 }
527 
528                 accept(Token.LPAREN);
529                 SQLTableSource tableSrc = this.createSQLSelectParser().parseTableSource();
530                 stmt.getTableOptions().put("UNION", tableSrc);
531                 accept(Token.RPAREN);
532                 continue;
533             }
534 
535             if (lexer.token() == Token.TABLESPACE) {
536                 lexer.nextToken();
537 
538                 MySqlCreateTableStatement.TableSpaceOption option = new MySqlCreateTableStatement.TableSpaceOption();
539                 option.setName(this.exprParser.name());
540 
541                 if (lexer.identifierEquals("STORAGE")) {
542                     lexer.nextToken();
543                     option.setStorage(this.exprParser.name());
544                 }
545 
546                 stmt.getTableOptions().put("TABLESPACE", option);
547                 continue;
548             }
549 
550             if (lexer.identifierEquals("TABLEGROUP")) {
551                 lexer.nextToken();
552 
553                 SQLName tableGroup = this.exprParser.name();
554                 stmt.setTableGroup(tableGroup);
555                 continue;
556             }
557 
558             if (lexer.identifierEquals("TYPE")) {
559                 lexer.nextToken();
560                 accept(Token.EQ);
561                 stmt.getTableOptions().put("TYPE", this.exprParser.expr());
562                 continue;
563             }
564 
565             if (lexer.identifierEquals("ENCRYPTION")) {
566                 lexer.nextToken();
567                 if (lexer.token() == Token.EQ) {
568                     lexer.nextToken();
569                 }
570                 stmt.getTableOptions().put("ENCRYPTION", this.exprParser.expr());
571                 continue;
572             }
573 
574             if (lexer.identifierEquals(FnvHash.Constants.COMPRESSION)) {
575                 lexer.nextToken();
576                 if (lexer.token() == Token.EQ) {
577                     lexer.nextToken();
578                 }
579                 stmt.getTableOptions().put("COMPRESSION", this.exprParser.expr());
580                 continue;
581             }
582 
583             if (lexer.token() == Token.PARTITION) {
584                 SQLPartitionBy partitionClause = parsePartitionBy();
585                 stmt.setPartitioning(partitionClause);
586 
587                 continue;
588             }
589 
590             if (lexer.identifierEquals(FnvHash.Constants.DBPARTITION)) {
591                 SQLPartitionBy partitionClause = parsePartitionBy();
592                 stmt.setDbPartitionBy(partitionClause);
593                 continue;
594             }
595 
596             if (lexer.identifierEquals(FnvHash.Constants.TBPARTITION)) {
597                 SQLPartitionBy partitionClause = parsePartitionBy();
598                 stmt.setTablePartitionBy(partitionClause);
599                 continue;
600             }
601 
602             if (lexer.identifierEquals(FnvHash.Constants.TBPARTITIONS)) {
603                 lexer.nextToken();
604                 SQLExpr tbpartitions = this.exprParser.expr();
605                 stmt.setTbpartitions(tbpartitions);
606                 continue;
607             }
608 
609             break;
610         }
611 
612         if (lexer.token() == (Token.ON)) {
613             throw new ParserException("TODO. " ~ lexer.info());
614         }
615 
616         if (lexer.token() == (Token.AS)) {
617             lexer.nextToken();
618         }
619 
620         if (lexer.token() == (Token.SELECT)) {
621             SQLSelect query = new MySqlSelectParser(this.exprParser).select();
622             stmt.setSelect(query);
623         }
624 
625         while (lexer.token() == (Token.HINT)) {
626             this.exprParser.parseHints!(SQLCommentHint)((stmt.getOptionHints()));
627         }
628         return stmt;
629     }
630 
631     private SQLPartitionBy parsePartitionBy() {
632         lexer.nextToken();
633         accept(Token.BY);
634 
635         SQLPartitionBy partitionClause;
636 
637         bool linera = false;
638         if (lexer.identifierEquals("LINEAR")) {
639             lexer.nextToken();
640             linera = true;
641         }
642 
643         if (lexer.token() == Token.KEY) {
644             MySqlPartitionByKey clause = new MySqlPartitionByKey();
645             lexer.nextToken();
646 
647             if (linera) {
648                 clause.setLinear(true);
649             }
650 
651             if (lexer.identifierEquals(FnvHash.Constants.ALGORITHM)) {
652                 lexer.nextToken();
653                 accept(Token.EQ);
654                 clause.setAlgorithm(lexer.integerValue().shortValue());
655                 lexer.nextToken();
656             }
657 
658             accept(Token.LPAREN);
659             if (lexer.token() != Token.RPAREN) {
660                 for (;;) {
661                     clause.addColumn(this.exprParser.name());
662                     if (lexer.token() == Token.COMMA) {
663                         lexer.nextToken();
664                         continue;
665                     }
666                     break;
667                 }
668             }
669             accept(Token.RPAREN);
670 
671             partitionClause = clause;
672 
673             partitionClauseRest(clause);
674         } else if (lexer.identifierEquals("HASH") || lexer.identifierEquals("UNI_HASH")) {
675             SQLPartitionByHash clause = new SQLPartitionByHash();
676 
677             if (lexer.identifierEquals("UNI_HASH")) {
678                 clause.setUnique(true);
679             }
680 
681             lexer.nextToken();
682 
683             if (linera) {
684                 clause.setLinear(true);
685             }
686 
687             if (lexer.token() == Token.KEY) {
688                 lexer.nextToken();
689                 clause.setKey(true);
690             }
691 
692             accept(Token.LPAREN);
693             this.exprParser.exprList(clause.getColumns(), clause);
694             accept(Token.RPAREN);
695             partitionClause = clause;
696 
697             partitionClauseRest(clause);
698 
699         } else if (lexer.identifierEquals("RANGE")) {
700             SQLPartitionByRange clause = partitionByRange();
701             partitionClause = clause;
702 
703             partitionClauseRest(clause);
704 
705         } else if (lexer.identifierEquals("LIST")) {
706             lexer.nextToken();
707             SQLPartitionByList clause = new SQLPartitionByList();
708 
709             if (lexer.token() == Token.LPAREN) {
710                 lexer.nextToken();
711                 clause.addColumn(this.exprParser.expr());
712                 accept(Token.RPAREN);
713             } else {
714                 acceptIdentifier("COLUMNS");
715                 accept(Token.LPAREN);
716                 for (;;) {
717                     clause.addColumn(this.exprParser.name());
718                     if (lexer.token() == Token.COMMA) {
719                         lexer.nextToken();
720                         continue;
721                     }
722                     break;
723                 }
724                 accept(Token.RPAREN);
725             }
726             partitionClause = clause;
727 
728             partitionClauseRest(clause);
729         } else {
730             throw new ParserException("TODO. " ~ lexer.info());
731         }
732 
733         if (lexer.token() == Token.LPAREN) {
734             lexer.nextToken();
735             for (;;) {
736                 SQLPartition partitionDef = this.getExprParser().parsePartition();
737 
738                 partitionClause.addPartition(partitionDef);
739 
740                 if (lexer.token() == Token.COMMA) {
741                     lexer.nextToken();
742                     continue;
743                 } else {
744                     break;
745                 }
746             }
747             accept(Token.RPAREN);
748         }
749         return partitionClause;
750     }
751 
752 
753     protected SQLPartitionByRange partitionByRange() {
754         acceptIdentifier("RANGE");
755 
756         SQLPartitionByRange clause = new SQLPartitionByRange();
757 
758         if (lexer.token() == Token.LPAREN) {
759             lexer.nextToken();
760             clause.addColumn(this.exprParser.expr());
761             accept(Token.RPAREN);
762         } else {
763             acceptIdentifier("COLUMNS");
764             accept(Token.LPAREN);
765             for (;;) {
766                 clause.addColumn(this.exprParser.name());
767                 if (lexer.token() == Token.COMMA) {
768                     lexer.nextToken();
769                     continue;
770                 }
771                 break;
772             }
773             accept(Token.RPAREN);
774         }
775         return clause;
776     }
777 
778     protected void partitionClauseRest(SQLPartitionBy clause) {
779         if (lexer.identifierEquals("PARTITIONS")) {
780             lexer.nextToken();
781 
782             SQLIntegerExpr countExpr = this.exprParser.integerExpr();
783             clause.setPartitionsCount(countExpr);
784         }
785 
786         if (lexer.token() == Token.PARTITION) {
787             lexer.nextToken();
788 
789             if (lexer.identifierEquals("NUM")) {
790                 lexer.nextToken();
791             }
792 
793             clause.setPartitionsCount(this.exprParser.expr());
794 
795             clause.putAttribute("ads.partition", Boolean.TRUE);
796         }
797 
798         if (lexer.identifierEquals("SUBPARTITION")) {
799             lexer.nextToken();
800             accept(Token.BY);
801 
802             SQLSubPartitionBy subPartitionByClause = null;
803 
804             bool linear = false;
805             if (lexer.identifierEquals("LINEAR")) {
806                 lexer.nextToken();
807                 linear = true;
808             }
809 
810             if (lexer.token() == Token.KEY) {
811                 MySqlSubPartitionByKey subPartitionKey = new MySqlSubPartitionByKey();
812                 lexer.nextToken();
813 
814                 if (linear) {
815                     clause.setLinear(true);
816                 }
817 
818                 if (lexer.identifierEquals(FnvHash.Constants.ALGORITHM)) {
819                     lexer.nextToken();
820                     accept(Token.EQ);
821                     subPartitionKey.setAlgorithm(lexer.integerValue().shortValue());
822                     lexer.nextToken();
823                 }
824 
825                 accept(Token.LPAREN);
826                 for (;;) {
827                     subPartitionKey.addColumn(this.exprParser.name());
828                     if (lexer.token() == Token.COMMA) {
829                         lexer.nextToken();
830                         continue;
831                     }
832                     break;
833                 }
834                 accept(Token.RPAREN);
835 
836                 subPartitionByClause = subPartitionKey;
837 
838             } else if (lexer.identifierEquals("HASH")) {
839                 lexer.nextToken();
840                 SQLSubPartitionByHash subPartitionHash = new SQLSubPartitionByHash();
841 
842                 if (linear) {
843                     clause.setLinear(true);
844                 }
845 
846                 if (lexer.token() == Token.KEY) {
847                     lexer.nextToken();
848                     subPartitionHash.setKey(true);
849                 }
850 
851                 accept(Token.LPAREN);
852                 subPartitionHash.setExpr(this.exprParser.expr());
853                 accept(Token.RPAREN);
854                 subPartitionByClause = subPartitionHash;
855 
856             } else if (lexer.identifierEquals("LIST")) {
857                 lexer.nextToken();
858                 MySqlSubPartitionByList subPartitionList = new MySqlSubPartitionByList();
859 
860                 if (lexer.token() == Token.LPAREN) {
861                     lexer.nextToken();
862                     SQLExpr expr = this.exprParser.expr();
863 
864                     if (cast(SQLIdentifierExpr)(expr) !is null && (lexer.identifierEquals("bigint") || lexer.identifierEquals("long"))) {
865                         string dataType = lexer.stringVal();
866                         lexer.nextToken();
867 
868                         SQLColumnDefinition column = this.exprParser.createColumnDefinition();
869                         column.setName(cast(SQLIdentifierExpr) expr);
870                         column.setDataType(new SQLDataTypeImpl(dataType));
871                         subPartitionList.addColumn(column);
872 
873                         subPartitionList.putAttribute("ads.subPartitionList", Boolean.TRUE);
874                     } else {
875                         subPartitionList.setExpr(expr);
876                     }
877                     accept(Token.RPAREN);
878                 } else {
879                     acceptIdentifier("COLUMNS");
880                     accept(Token.LPAREN);
881                     for (;;) {
882                         subPartitionList.addColumn(this.exprParser.parseColumn());
883                         if (lexer.token() == Token.COMMA) {
884                             lexer.nextToken();
885                             continue;
886                         }
887                         break;
888                     }
889                     accept(Token.RPAREN);
890                 }
891                 subPartitionByClause = subPartitionList;
892             }
893 
894             if (lexer.identifierEquals("SUBPARTITION")) {
895                 lexer.nextToken();
896                 acceptIdentifier("OPTIONS");
897                 accept(Token.LPAREN);
898 
899                 SQLAssignItem option = this.exprParser.parseAssignItem();
900                 accept(Token.RPAREN);
901 
902                 option.setParent(subPartitionByClause);
903 
904                 subPartitionByClause.getOptions().add(option);
905             }
906             
907             if (lexer.identifierEquals("SUBPARTITIONS")) {
908                 lexer.nextToken();
909                 Number intValue = lexer.integerValue();
910                 SQLNumberExpr numExpr = new SQLNumberExpr(intValue);
911                 subPartitionByClause.setSubPartitionsCount(numExpr);
912                 lexer.nextToken();
913             }
914 
915             if (subPartitionByClause !is null) {
916                 subPartitionByClause.setLinear(linear);
917 
918                 clause.setSubPartitionBy(subPartitionByClause);
919             }
920         }
921     }
922 
923     private bool parseTableOptionCharsetOrCollate(MySqlCreateTableStatement stmt) {
924         if (lexer.identifierEquals("CHARACTER")) {
925             lexer.nextToken();
926             accept(Token.SET);
927             if (lexer.token() == Token.EQ) {
928                 lexer.nextToken();
929             }
930             stmt.getTableOptions().put("CHARACTER SET", this.exprParser.expr());
931             return true;
932         }
933 
934         if (lexer.identifierEquals("CHARSET")) {
935             lexer.nextToken();
936             if (lexer.token() == Token.EQ) {
937                 lexer.nextToken();
938             }
939             stmt.getTableOptions().put("CHARSET", this.exprParser.expr());
940             return true;
941         }
942 
943         if (lexer.identifierEquals("COLLATE")) {
944             lexer.nextToken();
945             if (lexer.token() == Token.EQ) {
946                 lexer.nextToken();
947             }
948             stmt.getTableOptions().put("COLLATE", this.exprParser.expr());
949             return true;
950         }
951 
952         return false;
953     }
954 
955     override protected SQLTableConstraint parseConstraint() {
956         SQLName name = null;
957         bool hasConstaint = false;
958         if (lexer.token() == (Token.CONSTRAINT)) {
959             hasConstaint = true;
960             lexer.nextToken();
961         }
962 
963         if (lexer.token() == Token.IDENTIFIER) {
964             name = this.exprParser.name();
965         }
966 
967         SQLTableConstraint constraint = null;
968 
969         if (lexer.token() == (Token.KEY)) {
970             lexer.nextToken();
971 
972             MySqlKey key = new MySqlKey();
973             key.setHasConstaint(hasConstaint);
974 
975             // if (identifierEquals("USING")) {
976             // lexer.nextToken();
977             // key.setIndexType(lexer.stringVal());
978             // lexer.nextToken();
979             // }
980 
981             if (lexer.token() == Token.IDENTIFIER || lexer.token() == Token.LITERAL_ALIAS) {
982                 SQLName indexName = this.exprParser.name();
983                 if (indexName !is null) {
984                     key.setName(indexName);
985                 }
986             }
987 
988             // 5.5语法 USING BTREE 放在index 名字后
989             if (lexer.identifierEquals(FnvHash.Constants.USING)) {
990                 lexer.nextToken();
991                 key.setIndexType(lexer.stringVal());
992                 lexer.nextToken();
993             }
994 
995             accept(Token.LPAREN);
996             for (;;) {
997                 SQLExpr expr;
998                 if (lexer.token() == Token.LITERAL_ALIAS) {
999                     expr = this.exprParser.name();
1000                     expr = this.exprParser.primaryRest(expr);
1001                 } else {
1002                     expr = this.exprParser.expr();
1003                 }
1004                 if (lexer.token() == Token.ASC) {
1005                     lexer.nextToken();
1006                     expr = new MySqlOrderingExpr(expr, SQLOrderingSpecification.ASC);
1007                 } else if (lexer.token() == Token.DESC) {
1008                     lexer.nextToken();
1009                     expr = new MySqlOrderingExpr(expr, SQLOrderingSpecification.DESC);
1010                 }
1011 
1012                 key.addColumn(expr);
1013                 if (!(lexer.token() == (Token.COMMA))) {
1014                     break;
1015                 } else {
1016                     lexer.nextToken();
1017                 }
1018             }
1019             accept(Token.RPAREN);
1020 
1021             if (name !is null) {
1022                 key.setName(name);
1023             }
1024 
1025             if (lexer.identifierEquals(FnvHash.Constants.USING)) {
1026                 lexer.nextToken();
1027                 key.setIndexType(lexer.stringVal());
1028                 lexer.nextToken();
1029             }
1030 
1031             constraint = key;
1032         } else if (lexer.token() == Token.PRIMARY) {
1033             MySqlPrimaryKey pk = this.getExprParser().parsePrimaryKey();
1034             if (name !is null) {
1035                 pk.setName(name);
1036             }
1037             pk.setHasConstaint(hasConstaint);
1038             constraint = pk;
1039         } else if (lexer.token() == Token.UNIQUE) {
1040             MySqlUnique uk = this.getExprParser().parseUnique();
1041             if (name !is null) {
1042                 uk.setName(name);
1043             }
1044             uk.setHasConstaint(hasConstaint);
1045 
1046             constraint = uk;
1047         } else if (lexer.token() == Token.FOREIGN) {
1048             MysqlForeignKey fk = this.getExprParser().parseForeignKey();
1049             fk.setName(name);
1050             fk.setHasConstraint(hasConstaint);
1051             constraint = fk;
1052         } else if (lexer.token() == Token.CHECK) {
1053             lexer.nextToken();
1054             SQLCheck check = new SQLCheck();
1055             check.setName(name);
1056             SQLExpr expr = this.exprParser.expr();
1057             check.setExpr(expr);
1058             constraint = check;
1059         }
1060 
1061         if (constraint !is null) {
1062             if (lexer.token() == Token.COMMENT) {
1063                 lexer.nextToken();
1064                 SQLExpr comment = this.exprParser.primary();
1065                 constraint.setComment(comment);
1066             }
1067 
1068             return constraint;
1069         }
1070 
1071         throw new ParserException("TODO. " ~ lexer.info());
1072     }
1073 }