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.SQLCreateTableStatement;
17 
18 
19 import hunt.sql.SQLUtils;
20 import hunt.sql.ast;
21 import hunt.sql.ast.expr.SQLIdentifierExpr;
22 import hunt.sql.ast.expr.SQLMethodInvokeExpr;
23 import hunt.sql.ast.expr.SQLPropertyExpr;
24 import hunt.sql.dialect.mysql.ast.MySqlKey;
25 import hunt.sql.dialect.mysql.ast.MySqlUnique;
26 import hunt.sql.dialect.mysql.ast.statement.MySqlTableIndex;
27 // import hunt.sql.dialect.oracle.ast.stmt.OracleCreateSynonymStatement;
28 import hunt.sql.visitor.SQLASTVisitor;
29 import hunt.sql.util.FnvHash;
30 import hunt.sql.util.DBType;
31 import hunt.sql.util.ListDG;
32 import hunt.Functions;
33 import hunt.sql.ast.statement.SQLAlterTableItem;
34 import hunt.sql.ast.statement.SQLColumnDefinition;
35 import hunt.sql.ast.statement.SQLConstraint;
36 import hunt.sql.ast.statement.SQLUniqueConstraint;
37 import hunt.sql.ast.statement.SQLSelectOrderByItem;
38 import hunt.sql.ast.statement.SQLTableElement;
39 import hunt.sql.ast.statement.SQLExprTableSource;
40 
41 import hunt.sql.ast.statement.SQLForeignKeyConstraint;
42 import hunt.sql.ast.statement.SQLSelect;
43 import hunt.sql.ast.statement.SQLCreateStatement;
44 import hunt.sql.ast.statement.SQLDDLStatement;
45 import hunt.collection;
46 import hunt.sql.ast.statement.SQLExternalRecordFormat;
47 import hunt.sql.ast.statement.SQLPrimaryKey;
48 import hunt.sql.ast.statement.SQLAlterTableStatement;
49 import hunt.sql.ast.statement.SQLAlterTableRename;
50 import hunt.sql.ast.statement.SQLAlterTableRenameColumn;
51 import hunt.sql.ast.statement.SQLAlterTableDropKey;
52 import hunt.sql.ast.statement.SQLAlterTableDropConstraint;
53 import hunt.sql.ast.statement.SQLAlterTableDropForeignKey;
54 import hunt.sql.ast.statement.SQLAlterTableDropIndex;
55 import hunt.sql.ast.statement.SQLAlterTableDropPrimaryKey;
56 import hunt.sql.ast.statement.SQLAlterTableAddConstraint;
57 import hunt.sql.ast.statement.SQLAlterTableDropColumnItem;
58 import hunt.sql.ast.statement.SQLAlterTableAddIndex;
59 import hunt.sql.ast.statement.SQLAlterTableAddColumn;
60 import hunt.sql.ast.statement.SQLDropIndexStatement;
61 import hunt.sql.ast.statement.SQLCommentStatement;
62 import hunt.String;
63 import std.uni;
64 import hunt.sql.ast.statement.SQLUnique;
65 import hunt.text;
66 
67 public class SQLCreateTableStatement : SQLStatementImpl , SQLDDLStatement, SQLCreateStatement {
68 
69     protected bool                          ifNotExiists = false;
70     protected Type                             type;
71     protected SQLExprTableSource               tableSource;
72 
73     protected List!SQLTableElement            tableElementList;
74 
75     // for postgresql
76     protected SQLExprTableSource               inherits;
77 
78     protected SQLSelect                        select;
79 
80     protected SQLExpr                          comment;
81 
82     protected SQLExprTableSource               like;
83 
84     protected bool                          compress;
85     protected bool                          logging;
86 
87     protected SQLName                          tablespace;
88     protected SQLPartitionBy                   partitioning;
89     protected SQLName                          storedAs;
90 
91     protected bool                          onCommitPreserveRows;
92     protected bool                          onCommitDeleteRows;
93 
94     // for hive & odps
95     protected SQLExternalRecordFormat          rowFormat;
96     protected  List!SQLColumnDefinition  partitionColumns;
97     protected  List!SQLName              clusteredBy;
98     protected  List!SQLSelectOrderByItem sortedBy;
99     protected int                              buckets;
100 
101     protected Map!(string, SQLObject) tableOptions;
102 
103     public this(){
104         tableElementList = new ArrayList!SQLTableElement();
105         partitionColumns = new ArrayList!SQLColumnDefinition(2);
106         clusteredBy = new ArrayList!SQLName();
107         sortedBy = new ArrayList!SQLSelectOrderByItem();
108         tableOptions = new LinkedHashMap!(string, SQLObject)();
109     }
110 
111     public this(string dbType){
112         tableElementList = new ArrayList!SQLTableElement();
113         partitionColumns = new ArrayList!SQLColumnDefinition(2);
114         clusteredBy = new ArrayList!SQLName();
115         sortedBy = new ArrayList!SQLSelectOrderByItem();
116         tableOptions = new LinkedHashMap!(string, SQLObject)();
117         super(dbType);
118     }
119 
120     public SQLExpr getComment() {
121         return comment;
122     }
123 
124     public void setComment(SQLExpr comment) {
125         if (comment !is null) {
126             comment.setParent(this);
127         }
128         this.comment = comment;
129     }
130 
131     public SQLName getName() {
132         if (tableSource is null) {
133             return null;
134         }
135 
136         return cast(SQLName) tableSource.getExpr();
137     }
138 
139     public string getSchema() {
140         SQLName name = getName();
141         if (name is null) {
142             return null;
143         }
144 
145         if (cast(SQLPropertyExpr)(name) !is null ) {
146             return (cast(SQLPropertyExpr) name).getOwnernName();
147         }
148 
149         return null;
150     }
151 
152     public void setSchema(string name) {
153         if (this.tableSource is null) {
154             return;
155         }
156         tableSource.setSchema(name);
157     }
158 
159     public void setName(SQLName name) {
160         this.setTableSource(new SQLExprTableSource(name));
161     }
162 
163     public void setName(string name) {
164         this.setName(new SQLIdentifierExpr(name));
165     }
166 
167     public SQLExprTableSource getTableSource() {
168         return tableSource;
169     }
170 
171     public void setTableSource(SQLExprTableSource tableSource) {
172         if (tableSource !is null) {
173             tableSource.setParent(this);
174         }
175         this.tableSource = tableSource;
176     }
177 
178     public Type getType() {
179         return type;
180     }
181 
182     public void setType(Type type) {
183         this.type = type;
184     }
185 
186     public static enum Type {
187                              GLOBAL_TEMPORARY, LOCAL_TEMPORARY
188     }
189 
190     public List!SQLTableElement getTableElementList() {
191         return tableElementList;
192     }
193 
194     public bool isIfNotExiists() {
195         return ifNotExiists;
196     }
197 
198     public void setIfNotExiists(bool ifNotExiists) {
199         this.ifNotExiists = ifNotExiists;
200     }
201 
202     public SQLExprTableSource getInherits() {
203         return inherits;
204     }
205 
206     public void setInherits(SQLExprTableSource inherits) {
207         if (inherits !is null) {
208             inherits.setParent(this);
209         }
210         this.inherits = inherits;
211     }
212 
213     public SQLSelect getSelect() {
214         return select;
215     }
216 
217     public void setSelect(SQLSelect select) {
218         if (select !is null) {
219             select.setParent(this);
220         }
221         this.select = select;
222     }
223 
224     public SQLExprTableSource getLike() {
225         return like;
226     }
227 
228     public void setLike(SQLName like) {
229         this.setLike(new SQLExprTableSource(like));
230     }
231 
232     public void setLike(SQLExprTableSource like) {
233         if (like !is null) {
234             like.setParent(this);
235         }
236         this.like = like;
237     }
238 
239     public bool getCompress() {
240         return compress;
241     }
242 
243     public void setCompress(bool compress) {
244         this.compress = compress;
245     }
246 
247     public bool getLogging() {
248         return logging;
249     }
250 
251     public void setLogging(bool logging) {
252         this.logging = logging;
253     }
254 
255     public SQLName getTablespace() {
256         return tablespace;
257     }
258 
259     public void setTablespace(SQLName tablespace) {
260         if (tablespace !is null) {
261             tablespace.setParent(this);
262         }
263         this.tablespace = tablespace;
264     }
265 
266     public SQLPartitionBy getPartitioning() {
267         return partitioning;
268     }
269 
270     public void setPartitioning(SQLPartitionBy partitioning) {
271         if (partitioning !is null) {
272             partitioning.setParent(this);
273         }
274 
275         this.partitioning = partitioning;
276     }
277 
278     public Map!(string, SQLObject) getTableOptions() {
279         return tableOptions;
280     }
281 
282     
283     override  protected void accept0(SQLASTVisitor visitor) {
284         if (visitor.visit(this)) {
285             this.acceptChild(visitor, tableSource);
286             this.acceptChild!SQLTableElement(visitor, tableElementList);
287             this.acceptChild(visitor, inherits);
288             this.acceptChild(visitor, select);
289         }
290         visitor.endVisit(this);
291     }
292 
293     override
294     public List!SQLObject getChildren() {
295         List!SQLObject children = new ArrayList!SQLObject();
296         children.add(tableSource);
297         children.addAll(cast(List!SQLObject)(tableElementList));
298         if (inherits !is null) {
299             children.add(inherits);
300         }
301         if (select !is null) {
302             children.add(select);
303         }
304         return children;
305     }
306 
307     public void addBodyBeforeComment(List!string comments) {
308         if (attributes is null) {
309             attributes = new HashMap!(string, Object)();
310         }
311         
312         List!string attrComments = cast(List!string) attributes.get("format.body_before_comment");
313         if (attrComments is null) {
314             attributes.put("format.body_before_comment", cast(Object)comments);
315         } else {
316             attrComments.addAll(comments);
317         }
318     }
319     
320     public List!string getBodyBeforeCommentsDirect() {
321         if (attributes is null) {
322             return null;
323         }
324         
325         return cast(List!string) attributes.get("format.body_before_comment");
326     }
327     
328     public bool hasBodyBeforeComment() {
329         List!string comments = getBodyBeforeCommentsDirect();
330         if (comments is null) {
331             return false;
332         }
333         
334         return !comments.isEmpty();
335     }
336 
337     public string computeName() {
338         if (tableSource is null) {
339             return null;
340         }
341 
342         SQLExpr expr = tableSource.getExpr();
343         if (cast(SQLName)(expr) !is null ) {
344             string name = (cast(SQLName) expr).getSimpleName();
345             return SQLUtils.normalize(name);
346         }
347 
348         return null;
349     }
350 
351     public SQLColumnDefinition findColumn(string columName) {
352         if (columName is null) {
353             return null;
354         }
355 
356         long hash = FnvHash.hashCode64(columName);
357         return findColumn(hash);
358     }
359 
360     public SQLColumnDefinition findColumn(long columName_hash) {
361         foreach (SQLTableElement element ; tableElementList) {
362             if (cast(SQLColumnDefinition)(element) !is null ) {
363                 SQLColumnDefinition column = cast(SQLColumnDefinition) element;
364                 SQLName columnName = column.getName();
365                 if (columnName !is null && columnName.nameHashCode64() == columName_hash) {
366                     return column;
367                 }
368             }
369         }
370 
371         return null;
372     }
373 
374     public bool isPrimaryColumn(string columnName) {
375         SQLPrimaryKey pk = this.findPrimaryKey();
376         if (pk is null) {
377             return false;
378         }
379 
380         return pk.containsColumn(columnName);
381     }
382 
383     /**
384      * only for show columns
385      */
386     public bool isMUL(string columnName) {
387         foreach (SQLTableElement element ; this.tableElementList) {
388             if (cast(MySqlUnique)(element) !is null ) {
389                 MySqlUnique unique = cast(MySqlUnique) element;
390 
391                 SQLExpr column = unique.getColumns().get(0).getExpr();
392                 if ( (cast(SQLIdentifierExpr)column !is null)
393                         && SQLUtils.nameEquals(columnName, (cast(SQLIdentifierExpr) column).getName())) {
394                     return unique.columns.size() > 1;
395                 } else if ( (cast(SQLMethodInvokeExpr)column !is null)
396                         && SQLUtils.nameEquals((cast(SQLMethodInvokeExpr) column).getMethodName(), columnName)) {
397                     return true;
398                 }
399             } else if (cast(MySqlKey)(element) !is null ) {
400                 MySqlKey unique = cast(MySqlKey) element;
401 
402                 SQLExpr column = unique.getColumns().get(0).getExpr();
403                 if (  (cast(SQLIdentifierExpr)column !is null)
404                         && SQLUtils.nameEquals(columnName, (cast(SQLIdentifierExpr) column).getName())) {
405                     return true;
406                 } else if ( (cast(SQLMethodInvokeExpr)column !is null)
407                         && SQLUtils.nameEquals((cast(SQLMethodInvokeExpr) column).getMethodName(), columnName)) {
408                     return true;
409                 }
410             }
411         }
412         return false;
413     }
414 
415     /**
416      * only for show columns
417      */
418     public bool isUNI(string columnName) {
419         foreach (SQLTableElement element ; this.tableElementList) {
420             if (cast(MySqlUnique)(element) !is null ) {
421                 MySqlUnique unique = cast(MySqlUnique) element;
422 
423                 if (unique.getColumns().size() == 0) {
424                     continue;
425                 }
426 
427                 SQLExpr column = unique.getColumns().get(0).getExpr();
428                 if ( (cast(SQLIdentifierExpr)column !is null)
429                         && SQLUtils.nameEquals(columnName, (cast(SQLIdentifierExpr) column).getName())) {
430                     return unique.columns.size() == 1;
431                 } else if ( (cast(SQLMethodInvokeExpr)column !is null)
432                         && SQLUtils.nameEquals((cast(SQLMethodInvokeExpr) column).getMethodName(), columnName)) {
433                     return true;
434                 }
435             }
436         }
437         return false;
438     }
439 
440     public MySqlUnique findUnique(string columnName) {
441         foreach (SQLTableElement element ; this.tableElementList) {
442             if (cast(MySqlUnique)(element) !is null ) {
443                 MySqlUnique unique = cast(MySqlUnique) element;
444 
445                 if (unique.containsColumn(columnName)) {
446                     return unique;
447                 }
448             }
449         }
450 
451         return null;
452     }
453 
454     public SQLTableElement findIndex(string columnName) {
455         foreach (SQLTableElement element ; tableElementList) {
456             if (cast(SQLUniqueConstraint)(element) !is null ) {
457                 SQLUniqueConstraint unique = cast(SQLUniqueConstraint) element;
458                 foreach (SQLSelectOrderByItem item ; unique.getColumns()) {
459                     SQLExpr columnExpr = item.getExpr();
460                     if (cast(SQLIdentifierExpr)(columnExpr) !is null ) {
461                         string keyColumName = (cast(SQLIdentifierExpr) columnExpr).getName();
462                         keyColumName = SQLUtils.normalize(keyColumName);
463                         if (equalsIgnoreCase(keyColumName, columnName)) {
464                             return element;
465                         }
466                     }
467                 }
468 
469             } else if (cast(MySqlTableIndex)(element) !is null ) {
470                 List!SQLSelectOrderByItem indexColumns = (cast(MySqlTableIndex) element).getColumns();
471                 foreach (SQLSelectOrderByItem orderByItem ; indexColumns) {
472                     SQLExpr columnExpr = orderByItem.getExpr();
473                     if (cast(SQLIdentifierExpr)(columnExpr) !is null ) {
474                         string keyColumName = (cast(SQLIdentifierExpr) columnExpr).getName();
475                         keyColumName = SQLUtils.normalize(keyColumName);
476                         if (equalsIgnoreCase(keyColumName, columnName)) {
477                             return element;
478                         }
479                     }
480                 }
481             }
482 
483         }
484 
485         return null;
486     }
487 
488     public void forEachColumn(Consumer!SQLColumnDefinition columnConsumer) {
489         if (columnConsumer is null) {
490             return;
491         }
492 
493         foreach (SQLTableElement element ; this.tableElementList) {
494             if (cast(SQLColumnDefinition)(element) !is null ) {
495                 columnConsumer(cast(SQLColumnDefinition) element);
496             }
497         }
498     }
499 
500     public SQLPrimaryKey findPrimaryKey() {
501         foreach (SQLTableElement element ; this.tableElementList) {
502             if (cast(SQLPrimaryKey)(element) !is null ) {
503                 return cast(SQLPrimaryKey) element;
504             }
505         }
506 
507         return null;
508     }
509 
510     public List!SQLForeignKeyConstraint findForeignKey() {
511         List!SQLForeignKeyConstraint fkList = new ArrayList!SQLForeignKeyConstraint();
512         foreach (SQLTableElement element ; this.tableElementList) {
513             if (cast(SQLForeignKeyConstraint)(element) !is null ) {
514                 fkList.add(cast(SQLForeignKeyConstraint) element);
515             }
516         }
517         return fkList;
518     }
519 
520     public bool hashForeignKey() {
521         foreach (SQLTableElement element ; this.tableElementList) {
522             if (cast(SQLForeignKeyConstraint)(element) !is null ) {
523                 return true;
524             }
525         }
526         return false;
527     }
528 
529     public bool isReferenced(SQLName tableName) {
530         if (tableName is null) {
531             return false;
532         }
533 
534         return isReferenced(tableName.getSimpleName());
535     }
536 
537     public bool isReferenced(string tableName) {
538         if (tableName is null) {
539             return false;
540         }
541 
542         tableName = SQLUtils.normalize(tableName);
543 
544         foreach (SQLTableElement element ; this.tableElementList) {
545             if (cast(SQLForeignKeyConstraint)(element) !is null ) {
546                 SQLForeignKeyConstraint fk = cast(SQLForeignKeyConstraint) element;
547                 string refTableName = fk.getReferencedTableName().getSimpleName();
548 
549                 if (SQLUtils.nameEquals(tableName, refTableName)) {
550                     return true;
551                 }
552             }
553         }
554 
555         return false;
556     }
557 
558     public SQLAlterTableStatement foreignKeyToAlterTable() {
559         SQLAlterTableStatement stmt = new SQLAlterTableStatement();
560         for (int i = this.tableElementList.size() - 1; i >= 0; --i) {
561             SQLTableElement element = this.tableElementList.get(i);
562             if (cast(SQLForeignKeyConstraint)(element) !is null ) {
563                 SQLForeignKeyConstraint fk = cast(SQLForeignKeyConstraint) element;
564                 this.tableElementList.removeAt(i);
565                 stmt.addItem(new SQLAlterTableAddConstraint(fk));
566             }
567         }
568 
569         if (stmt.getItems().size() == 0) {
570             return null;
571         }
572 
573         stmt.setDbType(getDbType());
574         stmt.setTableSource(this.tableSource.clone());
575 
576        // Collections.reverse(stmt.getItems()); @gxc
577 
578         return stmt;
579     }
580 
581     public static void sort(List!SQLStatement stmtList) {
582         Map!(string, SQLCreateTableStatement) tables = new HashMap!(string, SQLCreateTableStatement)();
583         Map!(string, List!SQLCreateTableStatement) referencedTables = new HashMap!(string, List!SQLCreateTableStatement)();
584 
585         foreach (SQLStatement stmt ; stmtList) {
586             if (cast(SQLCreateTableStatement)(stmt) !is null ) {
587                 SQLCreateTableStatement createTableStmt = cast(SQLCreateTableStatement) stmt;
588                 string tableName = createTableStmt.getName().getSimpleName();
589                 tableName = toLower(SQLUtils.normalize(tableName));
590                 tables.put(tableName, createTableStmt);
591             }
592         }
593 
594         List!(ListDG.Edge) edges = new ArrayList!(ListDG.Edge)();
595 
596         foreach (SQLCreateTableStatement stmt ; tables.values()) {
597             foreach (SQLTableElement element ; stmt.getTableElementList()) {
598                 if (cast(SQLForeignKeyConstraint)(element) !is null ) {
599                     SQLForeignKeyConstraint fk = cast(SQLForeignKeyConstraint) element;
600                     string refTableName = fk.getReferencedTableName().getSimpleName();
601                     refTableName = toLower(SQLUtils.normalize(refTableName));
602 
603                     SQLCreateTableStatement refTable = tables.get(refTableName);
604                     if (refTable !is null) {
605                         edges.add(new ListDG.Edge(stmt, refTable));
606                     }
607 
608                     List!SQLCreateTableStatement referencedList = referencedTables.get(refTableName);
609                     if (referencedList is null) {
610                         referencedList = new ArrayList!SQLCreateTableStatement();
611                         referencedTables.put(refTableName, referencedList);
612                     }
613                     referencedList.add(stmt);
614                 }
615             }
616         }
617 
618         // foreach (SQLStatement stmt ; stmtList) {
619         //     if (cast(OracleCreateSynonymStatement)(stmt) !is null ) {
620         //         OracleCreateSynonymStatement createSynonym = cast(OracleCreateSynonymStatement) stmt;
621         //         SQLName object = createSynonym.getObject();
622         //         string refTableName = object.getSimpleName();
623         //         SQLCreateTableStatement refTable = tables.get(refTableName);
624         //         if (refTable !is null) {
625         //             edges.add(new ListDG.Edge(stmt, refTable));
626         //         }
627         //     }
628         // }
629 
630         ListDG dg = new ListDG(cast(List!Object)stmtList, edges);
631 
632         SQLStatement[] tops = new SQLStatement[stmtList.size()];
633         if (dg.topologicalSort(cast(Object[])tops)) {
634             for (int i = 0, size = stmtList.size(); i < size; ++i) {
635                 stmtList.set(i, tops[size - i - 1]);
636             }
637             return;
638         }
639 
640         List!SQLAlterTableStatement alterList = new ArrayList!SQLAlterTableStatement();
641 
642         for (int i = edges.size() - 1; i >= 0; --i) {
643             ListDG.Edge edge = edges.get(i);
644             SQLCreateTableStatement from = cast(SQLCreateTableStatement) edge.from;
645             string fromTableName = from.getName().getSimpleName();
646             fromTableName = toLower(SQLUtils.normalize(fromTableName));
647             if (referencedTables.containsKey(fromTableName)) {
648                 edges.removeAt(i);
649 
650                 //Arrays.fill(tops, null);@gxc
651                 tops = new SQLStatement[stmtList.size()];
652 
653                 dg = new ListDG(cast(List!Object)stmtList, edges);
654                 if (dg.topologicalSort(cast(Object[])tops)) {
655                     for (int j = 0, size = stmtList.size(); j < size; ++j) {
656                         SQLStatement stmt = tops[size - j - 1];
657                         stmtList.set(j, stmt);
658                     }
659 
660                     SQLAlterTableStatement alter = from.foreignKeyToAlterTable();
661                     alterList.add(alter);
662 
663                     stmtList.add(alter);
664                     return;
665                 }
666                 edges.add(i, edge);
667             }
668         }
669 
670         for (int i = edges.size() - 1; i >= 0; --i) {
671             ListDG.Edge edge = edges.get(i);
672             SQLCreateTableStatement from = cast(SQLCreateTableStatement) edge.from;
673             string fromTableName = from.getName().getSimpleName();
674             fromTableName = toLower(SQLUtils.normalize(fromTableName));
675             if (referencedTables.containsKey(fromTableName)) {
676                 SQLAlterTableStatement alter = from.foreignKeyToAlterTable();
677 
678                 edges.removeAt(i);
679                 if (alter !is null) {
680                     alterList.add(alter);
681                 }
682 
683                 // Arrays.fill(tops, null);@gxc
684                 tops = new SQLStatement[stmtList.size()];
685 
686                 dg = new ListDG(cast(List!Object)stmtList, edges);
687                 if (dg.topologicalSort(cast(Object[])tops)) {
688                     for (int j = 0, size = stmtList.size(); j < size; ++j) {
689                         SQLStatement stmt = tops[size - j - 1];
690                         stmtList.set(j, stmt);
691                     }
692 
693                     stmtList.addAll(cast(List!SQLStatement)alterList);
694                     return;
695                 }
696             }
697         }
698     }
699 
700     public void simplify() {
701         SQLName name = getName();
702         if (cast(SQLPropertyExpr)(name) !is null ) {
703             string tableName = (cast(SQLPropertyExpr) name).getName();
704             tableName = SQLUtils.normalize(tableName);
705 
706             string normalized = SQLUtils.normalize(tableName, dbType);
707             if (tableName != normalized) {
708                 this.setName(normalized);
709                 name = getName();
710             }
711         }
712 
713         if (cast(SQLIdentifierExpr)(name) !is null ) {
714             SQLIdentifierExpr identExpr = cast(SQLIdentifierExpr) name;
715             string tableName = identExpr.getName();
716             string normalized = SQLUtils.normalize(tableName, dbType);
717             if (normalized != tableName) {
718                 setName(normalized);
719             }
720         }
721 
722         foreach (SQLTableElement element ; this.tableElementList) {
723             if (cast(SQLColumnDefinition)(element) !is null ) {
724                 SQLColumnDefinition column = cast(SQLColumnDefinition) element;
725                 column.simplify();
726             } else if (cast(SQLConstraint)(element) !is null ) {
727                 (cast(SQLConstraint) element).simplify();
728             }
729         }
730     }
731 
732     public bool apply(SQLDropIndexStatement x) {
733         long indexNameHashCode64 = x.getIndexName().nameHashCode64();
734 
735         for (int i = tableElementList.size() - 1; i >= 0; i--) {
736             SQLTableElement e = tableElementList.get(i);
737             if (cast(SQLUniqueConstraint)(e) !is null ) {
738                 SQLUniqueConstraint unique = cast(SQLUniqueConstraint) e;
739                 if (unique.getName().nameHashCode64() == indexNameHashCode64) {
740                     tableElementList.removeAt(i);
741                     return true;
742                 }
743 
744             } else if (cast(MySqlTableIndex)(e) !is null ) {
745                 MySqlTableIndex tableIndex = cast(MySqlTableIndex) e;
746                 if (SQLUtils.nameEquals(tableIndex.getName(), x.getIndexName())) {
747                     tableElementList.removeAt(i);
748                     return true;
749                 }
750             }
751         }
752         return false;
753     }
754 
755     public bool apply(SQLCommentStatement x) {
756         SQLName on = x.getOn().getName();
757         SQLExpr comment = x.getComment();
758         if (comment is null) {
759             return false;
760         }
761 
762         SQLCommentStatement.Type type = x.getType();
763         if (type == SQLCommentStatement.Type.TABLE) {
764             if (!SQLUtils.nameEquals(getName(), on)) {
765                 return false;
766             }
767 
768             setComment(comment.clone());
769 
770             return true;
771         } else if (type == SQLCommentStatement.Type.COLUMN) {
772             SQLPropertyExpr propertyExpr = cast(SQLPropertyExpr) on;
773             if (!SQLUtils.nameEquals(getName(), cast(SQLName) propertyExpr.getOwner())) {
774                 return false;
775             }
776 
777             SQLColumnDefinition column
778                     = this.findColumn(
779                         propertyExpr.nameHashCode64());
780 
781             if (column !is null) {
782                 column.setComment(comment.clone());
783             }
784             return true;
785         }
786 
787         return false;
788     }
789 
790     public bool apply(SQLAlterTableStatement alter) {
791         if (!SQLUtils.nameEquals(alter.getName(), this.getName())) {
792             return false;
793         }
794 
795         int applyCount = 0;
796         foreach (SQLAlterTableItem item ; alter.getItems()) {
797             if (alterApply(item)) {
798                 applyCount++;
799             }
800         }
801 
802         return applyCount > 0;
803     }
804 
805     protected bool alterApply(SQLAlterTableItem item) {
806         if (cast(SQLAlterTableDropColumnItem)(item) !is null ) {
807             return apply(cast(SQLAlterTableDropColumnItem) item);
808 
809         } else if (cast(SQLAlterTableAddColumn)(item) !is null ) {
810             return apply(cast(SQLAlterTableAddColumn) item);
811 
812         } else if (cast(SQLAlterTableAddConstraint)(item) !is null ) {
813             return apply(cast(SQLAlterTableAddConstraint) item);
814 
815         } else if (cast(SQLAlterTableDropPrimaryKey)(item) !is null ) {
816             return apply(cast(SQLAlterTableDropPrimaryKey) item);
817 
818         } else if (cast(SQLAlterTableDropIndex)(item) !is null ) {
819             return apply(cast(SQLAlterTableDropIndex) item);
820 
821         } else if (cast(SQLAlterTableDropConstraint)(item) !is null ) {
822             return apply(cast(SQLAlterTableDropConstraint) item);
823 
824         } else if (cast(SQLAlterTableDropKey)(item) !is null ) {
825             return apply(cast(SQLAlterTableDropKey) item);
826 
827         } else if (cast(SQLAlterTableDropForeignKey)(item) !is null ) {
828             return apply(cast(SQLAlterTableDropForeignKey) item);
829 
830         } else if (cast(SQLAlterTableRename)(item) !is null ) {
831             return apply(cast(SQLAlterTableRename) item);
832 
833         } else if (cast(SQLAlterTableRenameColumn)(item) !is null ) {
834             return apply(cast(SQLAlterTableRenameColumn) item);
835 
836         } else if (cast(SQLAlterTableAddIndex)(item) !is null ) {
837             return apply(cast(SQLAlterTableAddIndex) item);
838         }
839 
840         return false;
841     }
842 
843     // SQLAlterTableRenameColumn
844 
845     private bool apply(SQLAlterTableRenameColumn item) {
846         int columnIndex = columnIndexOf(item.getColumn());
847         if (columnIndex == -1) {
848             return false;
849         }
850 
851         SQLColumnDefinition column = cast(SQLColumnDefinition) tableElementList.get(columnIndex);
852         column.setName(item.getTo().clone());
853 
854         return true;
855     }
856 
857     public bool renameColumn(string colummName, string newColumnName) {
858         if (colummName is null || newColumnName is null || newColumnName.length == 0) {
859             return false;
860         }
861 
862         int columnIndex = columnIndexOf(new SQLIdentifierExpr(colummName));
863         if (columnIndex == -1) {
864             return false;
865         }
866 
867         SQLColumnDefinition column = cast(SQLColumnDefinition) tableElementList.get(columnIndex);
868         column.setName(new SQLIdentifierExpr(newColumnName));
869 
870         return true;
871     }
872 
873     private bool apply(SQLAlterTableRename item) {
874         SQLName name = item.getToName();
875         if (name is null) {
876             return false;
877         }
878 
879         this.setName(name.clone());
880 
881         return true;
882     }
883 
884     private bool apply(SQLAlterTableDropForeignKey item) {
885         for (int i = tableElementList.size() - 1; i >= 0; i--) {
886             SQLTableElement e = tableElementList.get(i);
887             if (cast(SQLUniqueConstraint)(e) !is null ) {
888                 SQLForeignKeyConstraint fk = cast(SQLForeignKeyConstraint) e;
889                 if (SQLUtils.nameEquals(fk.getName(), item.getIndexName())) {
890                     tableElementList.removeAt(i);
891                     return true;
892                 }
893             }
894         }
895         return false;
896     }
897 
898     private bool apply(SQLAlterTableDropKey item) {
899         for (int i = tableElementList.size() - 1; i >= 0; i--) {
900             SQLTableElement e = tableElementList.get(i);
901             if (cast(SQLUniqueConstraint)(e) !is null ) {
902                 SQLUniqueConstraint unique = cast(SQLUniqueConstraint) e;
903                 if (SQLUtils.nameEquals(unique.getName(), item.getKeyName())) {
904                     tableElementList.removeAt(i);
905                     return true;
906                 }
907             }
908         }
909         return false;
910     }
911 
912     private bool apply(SQLAlterTableDropConstraint item) {
913         for (int i = tableElementList.size() - 1; i >= 0; i--) {
914             SQLTableElement e = tableElementList.get(i);
915             if (cast(SQLConstraint)(e) !is null ) {
916                 SQLConstraint constraint = cast(SQLConstraint) e;
917                 if (SQLUtils.nameEquals(constraint.getName(), item.getConstraintName())) {
918                     tableElementList.removeAt(i);
919                     return true;
920                 }
921             }
922         }
923         return false;
924     }
925 
926     private bool apply(SQLAlterTableDropIndex item) {
927         for (int i = tableElementList.size() - 1; i >= 0; i--) {
928             SQLTableElement e = tableElementList.get(i);
929             if (cast(SQLUniqueConstraint)(e) !is null ) {
930                 SQLUniqueConstraint unique = cast(SQLUniqueConstraint) e;
931                 if (SQLUtils.nameEquals(unique.getName(), item.getIndexName())) {
932                     tableElementList.removeAt(i);
933                     return true;
934                 }
935 
936             } else if (cast(MySqlTableIndex)(e) !is null ) {
937                 MySqlTableIndex tableIndex = cast(MySqlTableIndex) e;
938                 if (SQLUtils.nameEquals(tableIndex.getName(), item.getIndexName())) {
939                     tableElementList.removeAt(i);
940                     return true;
941                 }
942             }
943         }
944         return false;
945     }
946 
947     private bool apply(SQLAlterTableDropPrimaryKey item) {
948         for (int i = tableElementList.size() - 1; i >= 0; i--) {
949             SQLTableElement e = tableElementList.get(i);
950             if (cast(SQLPrimaryKey)(e) !is null ) {
951                 tableElementList.removeAt(i);
952                 return true;
953             }
954         }
955         return false;
956     }
957 
958     private bool apply(SQLAlterTableAddConstraint item) {
959         tableElementList.add(cast(SQLTableElement) item.getConstraint());
960         return true;
961     }
962 
963     private bool apply(SQLAlterTableDropColumnItem item) {
964         foreach (SQLName column ; item.getColumns()) {
965             string columnName = column.getSimpleName();
966             for (int i = tableElementList.size() - 1; i >= 0; --i) {
967                 SQLTableElement e = tableElementList.get(i);
968                 if (cast(SQLColumnDefinition)(e) !is null ) {
969                     if (SQLUtils.nameEquals(columnName, (cast(SQLColumnDefinition) e).getName().getSimpleName())) {
970                         tableElementList.removeAt(i);
971                     }
972                 }
973             }
974 
975             for (int i = tableElementList.size() - 1; i >= 0; --i) {
976                 SQLTableElement e = tableElementList.get(i);
977                 if (cast(SQLUnique)(e) !is null ) {
978                     SQLUnique unique = cast(SQLUnique) e;
979                     unique.applyDropColumn(column);
980                     if (unique.getColumns().size() == 0) {
981                         tableElementList.removeAt(i);
982                     }
983                 } else if (cast(MySqlTableIndex)(e) !is null ) {
984                     MySqlTableIndex index = cast(MySqlTableIndex) e;
985                     index.applyDropColumn(column);
986                     if (index.getColumns().size() == 0) {
987                         tableElementList.removeAt(i);
988                     }
989                 }
990             }
991         }
992 
993 
994 
995         return true;
996     }
997 
998     protected bool apply(SQLAlterTableAddIndex item) {
999         return false;
1000     }
1001 
1002     private bool apply(SQLAlterTableAddColumn item) {
1003         int startIndex = tableElementList.size();
1004         if (item.isFirst()) {
1005             startIndex = 0;
1006         }
1007 
1008         int afterIndex = columnIndexOf(item.getAfterColumn());
1009         if (afterIndex != -1) {
1010             startIndex = afterIndex + 1;
1011         }
1012 
1013         int beforeIndex = columnIndexOf(item.getFirstColumn());
1014         if (beforeIndex != -1) {
1015             startIndex = beforeIndex;
1016         }
1017 
1018         for (int i = 0; i < item.getColumns().size(); i++) {
1019             SQLColumnDefinition column = item.getColumns().get(i);
1020             tableElementList.add(i + startIndex, column);
1021             column.setParent(this);
1022         }
1023 
1024         return true;
1025     }
1026 
1027     protected int columnIndexOf(SQLName column) {
1028         if (column is null) {
1029             return -1;
1030         }
1031 
1032         string columnName = column.getSimpleName();
1033         for (int i = tableElementList.size() - 1; i >= 0; --i) {
1034             SQLTableElement e = tableElementList.get(i);
1035             if (cast(SQLColumnDefinition)(e) !is null ) {
1036                 if (SQLUtils.nameEquals(columnName, (cast(SQLColumnDefinition) e).getName().getSimpleName())) {
1037                     return i;
1038                 }
1039             }
1040         }
1041 
1042         return -1;
1043     }
1044 
1045     public void cloneTo(SQLCreateTableStatement x) {
1046         x.ifNotExiists = ifNotExiists;
1047         x.type = type;
1048         if (tableSource !is null) {
1049             x.setTableSource(tableSource.clone());
1050         }
1051         foreach (SQLTableElement e ; tableElementList) {
1052             SQLTableElement e2 = e.clone();
1053             e2.setParent(x);
1054             x.tableElementList.add(e2);
1055         }
1056         if (inherits !is null) {
1057             x.setInherits(inherits.clone());
1058         }
1059         if (select !is null) {
1060             x.setSelect(select.clone());
1061         }
1062         if (comment !is null) {
1063             x.setComment(comment.clone());
1064         }
1065 
1066         x.onCommitPreserveRows = onCommitPreserveRows;
1067         x.onCommitDeleteRows = onCommitDeleteRows;
1068 
1069         if (tableOptions !is null) {
1070             foreach (string k, SQLObject v; tableOptions) {
1071                 SQLObject entryVal = v.clone();
1072                 x.tableOptions.put(k, entryVal);
1073             }
1074         }
1075     }
1076 
1077     public SQLName getStoredAs() {
1078         return storedAs;
1079     }
1080 
1081     public void setStoredAs(SQLName x) {
1082         if (x !is null) {
1083             x.setParent(this);
1084         }
1085         this.storedAs = x;
1086     }
1087 
1088     override public SQLCreateTableStatement clone() {
1089         SQLCreateTableStatement x = new SQLCreateTableStatement(dbType);
1090         cloneTo(x);
1091         return x;
1092     }
1093 
1094     override public string toString() {
1095         return SQLUtils.toSQLString(this, dbType);
1096     }
1097 
1098     public bool isOnCommitPreserveRows() {
1099         return onCommitPreserveRows;
1100     }
1101 
1102     public void setOnCommitPreserveRows(bool onCommitPreserveRows) {
1103         this.onCommitPreserveRows = onCommitPreserveRows;
1104     }
1105 
1106     public List!SQLName getClusteredBy() {
1107         return clusteredBy;
1108     }
1109 
1110     public List!SQLSelectOrderByItem getSortedBy() {
1111         return sortedBy;
1112     }
1113 
1114     public void addSortedByItem(SQLSelectOrderByItem item) {
1115         item.setParent(this);
1116         this.sortedBy.add(item);
1117     }
1118 
1119     public int getBuckets() {
1120         return buckets;
1121     }
1122 
1123     public void setBuckets(int buckets) {
1124         this.buckets = buckets;
1125     }
1126 
1127     public List!SQLColumnDefinition getPartitionColumns() {
1128         return partitionColumns;
1129     }
1130 
1131     public void addPartitionColumn(SQLColumnDefinition column) {
1132         if (column !is null) {
1133             column.setParent(this);
1134         }
1135         this.partitionColumns.add(column);
1136     }
1137 
1138     public SQLExternalRecordFormat getRowFormat() {
1139         return rowFormat;
1140     }
1141 
1142     public void setRowFormat(SQLExternalRecordFormat x) {
1143         if (x !is null) {
1144             x.setParent(this);
1145         }
1146         this.rowFormat = x;
1147     }
1148 
1149     public bool isPrimaryColumn(long columnNameHash) {
1150         SQLPrimaryKey pk = this.findPrimaryKey();
1151         if (pk is null) {
1152             return false;
1153         }
1154 
1155         return pk.containsColumn(columnNameHash);
1156     }
1157 }