1 /*
2     SPDX-FileCopyrightText: 2015 Ralf Habacker <ralf.habacker@freenet.de>
3 
4     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
5 */
6 
7 // own header
8 #include "sqlimport.h"
9 
10 // app includes
11 #include "association.h"
12 #include "attribute.h"
13 #include "checkconstraint.h"
14 #include "classifier.h"
15 #include "debug_utils.h"
16 #include "enum.h"
17 #include "folder.h"
18 #include "import_utils.h"
19 #include "operation.h"
20 #include "package.h"
21 #include "uml.h"
22 #include "entity.h"
23 #include "entityattribute.h"
24 #include "foreignkeyconstraint.h"
25 #include "uniqueconstraint.h"
26 #include "umldoc.h"
27 #include "umlpackagelist.h"
28 
29 // kde includes
30 #if QT_VERSION < 0x050000
31 #include <KStandardDirs>
32 #endif
33 
34 // qt includes
35 #include <QProcess>
36 #include <QRegExp>
37 #if QT_VERSION >= 0x050000
38 #include <QStandardPaths>
39 #endif
40 #include <QStringList>
41 
42 #include <stdio.h>
43 
DEBUG_REGISTER(SQLImport)44 DEBUG_REGISTER(SQLImport)
45 
46 /**
47  * Constructor.
48  *
49  * @param thread                  thread in which the code import runs
50  */
51 SQLImport::SQLImport(CodeImpThread* thread) : NativeImportBase(QLatin1String("--"), thread)
52 {
53     setMultiLineComment(QLatin1String("/*"), QLatin1String("*/"));
54 }
55 
56 /**
57  * Destructor.
58  */
~SQLImport()59 SQLImport::~SQLImport()
60 {
61 }
62 
63 /**
64  * Implement abstract operation from NativeImportBase.
65  */
fillSource(const QString & word)66 void SQLImport::fillSource(const QString& word)
67 {
68     QString lexeme;
69     const uint len = word.length();
70     for (uint i = 0; i < len; ++i) {
71         QChar c = word[i];
72         if (c.isLetterOrNumber() || c == QLatin1Char('_')) {
73             lexeme += c;
74         } else {
75             if (!lexeme.isEmpty()) {
76                 m_source.append(lexeme);
77                 lexeme.clear();
78             }
79             m_source.append(QString(c));
80         }
81     }
82     if (!lexeme.isEmpty())
83         m_source.append(lexeme);
84 }
85 
86 /**
87  * Strip qoutes from identifier.
88  *
89  * @param token string with current token
90  * @return stripped string
91  */
stripQuotes(QString & token)92 QString &stripQuotes(QString &token)
93 {
94     if (token.contains(QLatin1String("\"")))
95         token.replace(QLatin1String("\""), QLatin1String(""));
96     else if (token.contains(QLatin1String("`")))
97         token.replace(QLatin1String("`"), QLatin1String(""));
98     else if (token.contains(QLatin1String("'")))
99         token.replace(QLatin1String("'"), QLatin1String(""));
100     return token;
101 }
102 
103 /**
104  * Parse identifier.
105  *
106  * @param token string with current token
107  * @return parsed identifier
108  */
parseIdentifier(QString & token)109 QString SQLImport::parseIdentifier(QString &token)
110 {
111     QString value;
112     if (token == QLatin1String("`")) { // mysql
113         value = advance();
114         token = advance();
115     } else
116         value = token;
117     token = advance();
118     if (token == QLatin1String(".")) {
119         // FIXME we do not support packages yet
120 #if 0
121         value += token;
122         value += advance();
123         token = advance();
124 #else
125         value = advance();
126         token = advance();
127 #endif
128     }
129 
130     return stripQuotes(value);
131 }
132 
133 /**
134  * Parse identifier list.
135  *
136  * @param token string with current token
137  * @return string list with identifiers
138  */
parseIdentifierList(QString & token)139 QStringList SQLImport::parseIdentifierList(QString &token)
140 {
141     QStringList values;
142     if (token.toLower() == QLatin1String("(")) {
143         for (token = advance(); token != QLatin1String(")");) {
144             if (token == QLatin1String(",")) {
145                 token = advance();
146                 continue;
147             }
148             QString value = parseIdentifier(token);
149             values.append(value);
150         }
151         token = advance();
152     }
153     else {
154         ;// error;
155     }
156     return values;
157 }
158 
159 /**
160  * Parse field type.
161  *
162  * @param token string with current token
163  * @return string list containing field type (index 0), size/count (index 1) and optional values (index > 2)
164  */
parseFieldType(QString & token)165 QStringList SQLImport::parseFieldType(QString &token)
166 {
167     QString type = token;
168     QString typeLength;
169 
170     // handle type extensions
171     token = advance();
172 
173     // schema.type
174     if (token == QLatin1String(".")) {
175         type += token;
176         type += advance();
177         token = advance();
178         if (token.toLower() == QLatin1String("precision")) {
179             type += token;
180             token = advance();
181         }
182     }
183 
184     if (type.toLower() == QLatin1String("enum")) {
185         QStringList values = parseIdentifierList(token);
186         return QStringList() << type << QString() << values;
187     }
188 
189     if (token.toLower() == QLatin1String("varying")) {
190         type += QLatin1String(" ") + token;
191         token = advance(); // '('
192     }
193     // (number) | (number,number)
194     if (token.toLower() == QLatin1String("(")) {
195         typeLength = advance(); // value
196         token = advance();
197         if (token == QLatin1String(",")) {
198             typeLength += token;
199             typeLength += advance();
200             token = advance();
201         }
202         token = advance();
203     } else if (token.toLower() == QLatin1String("precision")) {
204         type += QLatin1String(" ") + token;
205         token = advance();
206     }
207 
208     if (token == QLatin1String("[")) {
209         token = advance();
210         if (token == QLatin1String("]")) {
211             type += QLatin1String("[]");
212             token = advance();
213         }
214     } else if (token.toLower().startsWith(QLatin1String("with"))) {
215         type += QLatin1String(" ") + token;
216         token = advance();
217         type += QLatin1String(" ") + token;
218         token = advance();
219         type += QLatin1String(" ") + token;
220         token = advance();
221     } else if (token.toLower() == QLatin1String("unsigned")) { // mysql
222         token = advance();
223     }
224     return QStringList() << type << typeLength;
225 }
226 
227 /**
228  * Parse default expression.
229  *
230  * The expression could be in the form
231  *    (expression)\::\<type\>
232  *    function(expression)
233  *
234  * @param token string with current token
235  * @return string with default expression
236  */
parseDefaultExpression(QString & token)237 QString SQLImport::parseDefaultExpression(QString &token)
238 {
239     QString defaultValue;
240 
241     if (token == QLatin1String("(")) {
242         int index = m_srcIndex;
243         skipToClosing(QLatin1Char('('));
244         token = advance();
245         for (int i = index; i < m_srcIndex; i++)
246             defaultValue += m_source[i];
247     } else {
248         defaultValue += token;
249         token = advance();
250     }
251     if (token == (QLatin1String(":"))) {
252         defaultValue += token;
253         token = advance();
254         if (token == (QLatin1String(":"))) {
255             defaultValue += token;
256             token = advance();
257             defaultValue += parseFieldType(token).first();
258         }
259     }
260 
261     if (token == QLatin1String("(")) {
262         int index = m_srcIndex;
263         skipToClosing(QLatin1Char('('));
264         token = advance();
265         for (int i = index; i < m_srcIndex; i++)
266             defaultValue += m_source[i];
267     }
268 
269     return defaultValue;
270 }
271 
272 /**
273  * Parse column constraint.
274  *
275  *  pgsql:
276  *  [ CONSTRAINT constraint_name ]
277  *  { NOT NULL |
278  *    NULL |
279  *    CHECK ( expression ) |
280  *    COLLATE collation |
281  *    DEFAULT default_expr |
282  *    UNIQUE index_parameters |
283  *    PRIMARY KEY index_parameters |
284  *    REFERENCES reftable [ ( refcolumn ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]
285  *      [ ON DELETE action ] [ ON UPDATE action ] }
286  *  [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
287  *
288  * mysql:
289  *  [ PRIMARY KEY index_parameters |
290  *    KEY key_name ( fields )
291  *    CHARACTER SET charset_name |
292  *    COLLATE collation ]
293  *
294  * @param token string with current token
295  * @return column constraints
296  */
parseColumnConstraints(QString & token)297 SQLImport::ColumnConstraints SQLImport::parseColumnConstraints(QString &token)
298 {
299     ColumnConstraints constraints;
300 
301     while (token != QLatin1String(",") && token != QLatin1String(")") && token.toLower() != QLatin1String("comment")) {
302         const int origIndex = m_srcIndex;
303 
304         if (token.toLower() == QLatin1String("character")) { // mysql
305             token = advance(); // set
306             if (token.toLower() == QLatin1String("set")) {
307                 constraints.characterSet = advance(); // <value>
308                 token = advance();
309             }
310             else {
311                 m_srcIndex--; // take back
312                 token = m_source[m_srcIndex];
313             }
314         }
315 
316         if (token.toLower() == QLatin1String("collate")) { // mysql
317             constraints.collate = advance();
318             token = advance();
319         }
320 
321         // [ CONSTRAINT constraint_name ]
322         if (token.toLower() == QLatin1String("constraint")) {
323             constraints.constraintName = advance();
324             token = advance();
325         }
326 
327         // NOT NULL
328         if (token.toLower() == QLatin1String("not")) {
329             token = advance();
330             if (token.toLower() == QLatin1String("null")) {
331                 constraints.notNullConstraint = true;
332                 token = advance();
333             }
334         }
335 
336         // NULL
337         if (token.toLower() == QLatin1String("null")) {
338             constraints.notNullConstraint = false;
339             token = advance();
340         }
341 
342         // CHECK ( expression )
343         if (token.toLower() == QLatin1String("check")) {
344             skipStmt(QLatin1String(")"));
345             token = advance();
346         }
347 
348         // DEFAULT default_expr
349         if (token.toLower() == QLatin1String("default")) {
350             token = advance();
351             constraints.defaultValue = parseDefaultExpression(token);
352         }
353 
354         // UNIQUE index_parameters
355         if (token.toLower() == QLatin1String("unique")) {
356             constraints.uniqueKey = true;
357             token = advance();
358             // WITH ( storage_parameter [= value] [, ... ] )
359             if (token.toLower() == QLatin1String("with")) {
360                 skipStmt(QLatin1String(")"));
361                 token = advance();
362             }
363             // USING INDEX TABLESPACE tablespace
364             if (token.toLower() == QLatin1String("using")) {
365                 token = advance();
366                 token = advance();
367                 token = advance();
368                 token = advance();
369             }
370         }
371 
372         // PRIMARY KEY index_parameters
373         if (token.toLower() == QLatin1String("primary")) {
374             token = advance();
375             if (token.toLower() == QLatin1String("key")) {
376                 constraints.primaryKey = true;
377                 token = advance();
378                 // WITH ( storage_parameter [= value] [, ... ] )
379                 if (token.toLower() == QLatin1String("with")) {
380                     skipStmt(QLatin1String(")"));
381                     token = advance();
382                 }
383                 // USING INDEX TABLESPACE tablespace
384                 if (token.toLower() == QLatin1String("using")) {
385                     token = advance();  // INDEX
386                     token = advance();  // TABLESPACE
387                     token = advance();  // tablespace
388                     token = advance();
389                 }
390             }
391         }
392 
393         // REFERENCES reftable [ ( refcolumn ) ]
394         if (token.toLower() == QLatin1String("references")) {
395             token = advance();
396             token = advance();
397             if (token == QLatin1String("(")) {
398                 skipStmt(QLatin1String(")"));
399                 token = advance();
400             }
401 
402             // [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]
403             if (token.toLower() == QLatin1String("match")) {
404                 token = advance();
405                 token = advance();
406             }
407 
408             // [ ON DELETE action ]
409             if (token.toLower() == QLatin1String("on")) {
410                 token = advance();
411                 token = advance();
412                 token = advance();
413             }
414 
415             // [ ON UPDATE action ]
416             if (token.toLower() == QLatin1String("on")) {
417                 token = advance();
418                 token = advance();
419                 token = advance();
420             }
421         }
422 
423         // [ DEFERRABLE | NOT DEFERRABLE ]
424         if (token.toLower() == QLatin1String("deferrable")) {
425             token = advance();
426         }
427         else if (token.toLower() == QLatin1String("not")) {
428             token = advance();
429             token = advance();
430         }
431 
432         // [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
433         if (token.toLower() == QLatin1String("initially")) {
434             token = advance();
435             token = advance();
436         }
437 
438         if (token.toLower() == QLatin1String("auto_increment")) { // mysql
439             constraints.autoIncrement = true;
440             token = advance();
441         }
442 
443         if (m_srcIndex == origIndex) {
444             log(m_parsedFiles.first(), QLatin1String("could not parse column constraint '") + token + QLatin1String("'"));
445             token = advance();
446         }
447     }
448     if (token.toLower() == QLatin1String("comment")) {
449         while (token != QLatin1String(",") && token != QLatin1String(")")) {
450             token = advance();
451         }
452     }
453     return constraints;
454 }
455 
456 /**
457  * Parse table constraint.
458  *
459  * pgsql:
460  *
461  * [ CONSTRAINT constraint_name ]
462  * { CHECK ( expression ) |
463  *   UNIQUE ( column_name [, ... ] ) index_parameters |
464  *   PRIMARY KEY ( column_name [, ... ] ) index_parameters |
465  *   EXCLUDE [ USING index_method ] ( exclude_element WITH operator [, ... ] ) index_parameters [ WHERE ( predicate ) ] |
466  *   FOREIGN KEY ( column_name [, ... ] ) REFERENCES reftable [ ( refcolumn [, ... ] ) ]
467  *     [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ] [ ON DELETE action ] [ ON UPDATE action ] }
468  * [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
469  *
470  * mysql:
471  *   PRIMARY KEY (`uid`, `pid`) |
472  *   KEY `t3ver_oid` (`t3ver_oid`,`t3ver_wsid`)
473  *   UNIQUE KEY `entry_identifier` (`entry_namespace`,`entry_key`)
474  *
475  * @param token string with current token
476  * @return table constraints
477  */
parseTableConstraints(QString & token)478 SQLImport::TableConstraints SQLImport::parseTableConstraints(QString &token)
479 {
480     TableConstraints constraints;
481 
482     if (token.toLower() == QLatin1String("constraint")) {
483         constraints.constraintName = advance();
484         token = advance();
485     }
486 
487     // CHECK ( expression )
488     if (token.toLower() == QLatin1String("check")) {
489         token = advance();
490         if (token == QLatin1String("(")) {
491             int index = m_srcIndex;
492             skipToClosing(QLatin1Char('('));
493             token = advance();
494             constraints.checkConstraint = true;
495             for (int i = index; i < m_srcIndex; i++)
496                 constraints.checkExpression += m_source[i];
497         }
498     }
499 
500     // PRIMARY KEY (`uid`, `pid`),
501     if (token.toLower() == QLatin1String("primary")) {
502         token = advance(); // key
503         token = advance(); // (
504         constraints.primaryKey = true;
505         constraints.primaryKeyFields = parseIdentifierList(token);
506     }
507 
508     // UNIQUE KEY `entry_identifier` (`entry_namespace`,`entry_key`)
509     else if (token.toLower() == QLatin1String("unique")) {
510         token = advance();
511         token = advance();
512         constraints.uniqueKeys = true;
513         constraints.uniqueKeyName = parseIdentifier(token);
514         constraints.uniqueKeysFields = parseIdentifierList(token);
515     }
516 
517     // KEY `t3ver_oid` (`t3ver_oid`,`t3ver_wsid`) // mysql
518     else if (token.toLower() == QLatin1String("key")) {
519         if (m_source[m_srcIndex+4] == QLatin1String("(") ) {
520             token = advance();
521             constraints.uniqueKeys = true;
522             constraints.uniqueKeyName = parseIdentifier(token);
523             constraints.uniqueKeysFields = parseIdentifierList(token);
524         }
525     }
526 
527     return constraints;
528 }
529 
530 /**
531  * Parse table create definition.
532  *
533  * @param token string with current token
534  * @param entity entity to save the definition into
535  * @return true on success
536  * @return false on error
537  */
parseCreateDefinition(QString & token,UMLEntity * entity)538 bool SQLImport::parseCreateDefinition(QString &token, UMLEntity *entity)
539 {
540     if (token != QLatin1String("(")) {
541         skipStmt(QLatin1String(";"));
542         return false;
543     }
544 
545     while (m_source.count() > m_srcIndex) {
546         token = advance();
547 
548         if (token == QLatin1String(")")) {
549             break;
550         }
551 
552         TableConstraints tableConstraints = parseTableConstraints(token);
553 
554         if (tableConstraints.primaryKey) {
555             if (!addPrimaryKey(entity, tableConstraints.constraintName, tableConstraints.primaryKeyFields)) {
556                 ; // log error
557             }
558         }
559 
560         if (tableConstraints.uniqueKeys) {
561             if (!addUniqueConstraint(entity, tableConstraints.uniqueKeyName, tableConstraints.uniqueKeysFields)) {
562                 ; // log error
563             }
564         }
565 
566         if (tableConstraints.checkConstraint) {
567             if (entity) {
568                 QString name;
569                 if (!tableConstraints.constraintName.isEmpty())
570                     name = tableConstraints.constraintName;
571                 else
572                     name = entity->name() + QLatin1String("_check");
573                 UMLCheckConstraint *cc = new UMLCheckConstraint(entity, name);
574                 cc->setCheckCondition(tableConstraints.checkExpression);
575                 entity->addConstraint(cc);
576             } else {
577                 uError() << "Could not add check constraint '" << tableConstraints.constraintName << "' because of zero entity.";
578             }
579         }
580 
581         if (token == QLatin1String(","))
582             continue;
583         else if (token == QLatin1String(")"))
584             break;
585 
586         // handle field name
587         QString fieldName = parseIdentifier(token);
588 
589         // handle field type
590         QStringList fieldType = parseFieldType(token);
591         SQLImport::ColumnConstraints constraints = parseColumnConstraints(token);
592 
593         DEBUG(DBG_SRC) << "field" << fieldName << fieldType.at(0);
594         if (entity && !fieldName.isEmpty()) {
595             UMLObject *type = addDatatype(fieldType);
596             UMLEntityAttribute *a = new UMLEntityAttribute(0, fieldName,
597                     Uml::ID::None,
598                     Uml::Visibility::Public,
599                     type);
600             if (constraints.primaryKey)
601                 a->setIndexType(UMLEntityAttribute::Primary);
602             a->setNull(!constraints.notNullConstraint);
603             // convert index to value if present, see https://dev.mysql.com/doc/refman/8.0/en/enum.html
604             if (UMLApp::app()->activeLanguage() == Uml::ProgrammingLanguage::MySQL && type->isUMLEnum()) {
605                 bool ok;
606                 int index = constraints.defaultValue.toInt(&ok);
607                 if (!ok) // string (not checked if valid) or empty
608                     a->setInitialValue(constraints.defaultValue);
609                 else if (index > 0) {
610                     index--; // 0 is empty
611                     UMLEnum *_enum = type->asUMLEnum();
612                     UMLClassifierListItemList enumLiterals = _enum->getFilteredList(UMLObject::ot_EnumLiteral);
613                     if (index < enumLiterals.size())
614                         a->setInitialValue(enumLiterals.at(index)->name());
615                 }
616             } else {
617                 a->setInitialValue(constraints.defaultValue);
618             }
619             a->setValues(fieldType.at(1));
620             a->setAutoIncrement(constraints.autoIncrement);
621             if (constraints.primaryKey) {
622                 UMLUniqueConstraint *pkey = new UMLUniqueConstraint(a, a->name() + QLatin1String("_pkey"));
623                 entity->setAsPrimaryKey(pkey);
624             }
625             else if (constraints.uniqueKey) {
626                 UMLUniqueConstraint *uc = new UMLUniqueConstraint(a, a->name() + QLatin1String("_unique"));
627                 entity->addConstraint(uc);
628             }
629 
630             QStringList attributes;
631             if (!constraints.characterSet.isEmpty())
632                 attributes.append(QLatin1String("CHARACTER SET ") + constraints.characterSet);
633             if (!constraints.collate.isEmpty())
634                 attributes.append(QLatin1String("COLLATE ") + constraints.collate);
635             if (attributes.size() > 0)
636                 a->setAttributes(attributes.join(QLatin1String(" ")));
637 
638             entity->addEntityAttribute(a);
639         } else if (!entity) {
640             uError() << "Could not add field '" << fieldName << "' because of zero entity.";
641         }
642         if (token == QLatin1String(","))
643             continue;
644         else if (token == QLatin1String(")"))
645             break;
646     }
647     token = advance();
648     return true;
649 }
650 
651 /**
652  * Parse create table statement.
653  *
654  * @param token string with current token
655  * @return true on success
656  * @return false on error
657  */
parseCreateTable(QString & token)658 bool SQLImport::parseCreateTable(QString &token)
659 {
660     bool returnValue = true;
661     QString tableName = parseIdentifier(token);
662     DEBUG(DBG_SRC) << "parsing create table" << tableName;
663 
664     UMLFolder *folder = UMLApp::app()->document()->rootFolder(Uml::ModelType::EntityRelationship);
665     UMLObject *o = Import_Utils::createUMLObject(UMLObject::ot_Entity,
666                    tableName, folder, m_comment);
667     UMLEntity *entity = o->asUMLEntity();
668     m_comment.clear();
669     if (token.toLower() == QLatin1String("as")) {
670         skipStmt(QLatin1String(";"));
671         return false;
672     } else if (token == QLatin1String("(")) {
673         parseCreateDefinition(token, entity);
674     } else {
675         skipStmt(QLatin1String(";"));
676         return false;
677     }
678     if (token.toLower() == QLatin1String("inherits")) {
679         token = advance(); // (
680         const QString &baseTable = advance();
681         token = advance(); // )
682         UMLObject *b = Import_Utils::createUMLObject(UMLObject::ot_Entity,
683                        baseTable, folder, m_comment);
684         UMLAssociation *a = new UMLAssociation(Uml::AssociationType::Generalization, o, b);
685         if (entity)
686             entity->addAssocToConcepts(a);
687         else {
688             uError() << "Could not add generalization '" << baseTable << "' because of zero entity.";
689             returnValue = false;
690         }
691     }
692 
693     skipStmt(QLatin1String(";"));
694     return returnValue;
695 }
696 
697 /**
698  * Parse alter table statement.
699  *
700  * @param token string with current token
701  * @return true on success
702  * @return false on error
703  */
parseAlterTable(QString & token)704 bool SQLImport::parseAlterTable(QString &token)
705 {
706     if (token.toLower() == QLatin1String("only"))
707         token = advance();
708 
709     QString tableName = token;
710     token = advance();
711 
712     if (token == QLatin1String(".")) {
713         tableName += token;
714         token = advance();
715         if (token.contains(QLatin1String("\"")))
716             token.replace(QLatin1String("\""), QLatin1String(""));
717         tableName += token;
718         token = advance();
719     }
720 
721     if (token.toLower() == QLatin1String("add")) {
722         token = advance();
723         if (token.toLower() == QLatin1String("constraint")) {
724             const QString &constraintName = advance();
725             token = advance();
726             UMLFolder *folder = UMLApp::app()->document()->rootFolder(Uml::ModelType::EntityRelationship);
727             UMLObject *o = UMLApp::app()->document()->findUMLObject(tableName, UMLObject::ot_Entity, folder);
728             if (token.toLower() == QLatin1String("primary")) {
729                 token = advance(); // key
730                 token = advance();
731                 const QStringList &fieldNames = parseIdentifierList(token);
732                 if (!o) {
733                     // report error
734                 }
735                 UMLEntity *entity = o->asUMLEntity();
736                 if (!addPrimaryKey(entity, constraintName, fieldNames)) {
737                     ; // reporter error
738                 }
739             }
740             else if (token.toLower() == QLatin1String("unique")) {
741                 token = advance();
742                 const QStringList &fieldNames = parseIdentifierList(token);
743                 if (!o) {
744                     // report error
745                 }
746                 UMLEntity *entity = o->asUMLEntity();
747                 if (!addUniqueConstraint(entity, constraintName, fieldNames)) {
748                     ; // report error
749                 }
750             }
751             // FOREIGN KEY (<NAME>) REFERENCES <TABLE> (<FIELD>)
752             else if (token.toLower() == QLatin1String("foreign")) {
753                 token = advance(); // key
754                 token = advance();
755                 const QStringList &localFieldNames = parseIdentifierList(token);
756                 token = advance(); // references
757                 const QString &referencedTableName = parseIdentifier(token);
758                 const QStringList &referencedFieldNames = parseIdentifierList(token);
759                 // ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
760                 // use parseColumnConstraint()
761                 if (token.toLower() == QLatin1String("on")) {
762                     token = advance();
763                     token = advance(); // delete/update
764                     if (token.toLower() == QLatin1String("cascade"))
765                         token = advance();
766                 }
767                 else if (token.toLower() == QLatin1String("match")) {
768                     token = advance();
769                     token = advance(); // full
770                 }
771 
772                 if (!o) {
773                     // report error
774                 }
775                 UMLEntity *entity = o->asUMLEntity();
776                 if (!addForeignConstraint(entity, constraintName, localFieldNames, referencedTableName, referencedFieldNames)) {
777                     ; // report error
778                 }
779             }
780         }
781     } else
782         skipStmt(QLatin1String(";"));
783 
784     return true;
785 }
786 
787 /**
788  * Implement abstract operation from NativeImportBase.
789  */
parseStmt()790 bool SQLImport::parseStmt()
791 {
792     const QString& keyword = m_source[m_srcIndex];
793     if (keyword.toLower() == QLatin1String("set")) {
794         skipStmt(QLatin1String(";"));
795         return true;
796     }
797     // CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ]
798     else if (keyword.toLower() == QLatin1String("create")) {
799         QString type = advance();
800         // [ GLOBAL | LOCAL ]
801         if (type.toLower() == QLatin1String("global"))
802             type = advance();
803         else if (type.toLower() == QLatin1String("local"))
804             type = advance();
805 
806         // [ { TEMPORARY | TEMP } | UNLOGGED ]
807         if (type.toLower() == QLatin1String("temp"))
808             type = advance();
809         else if (type.toLower() == QLatin1String("temporary"))
810             type = advance();
811 
812         if (type.toLower() == QLatin1String("unlogged"))
813             type = advance();
814 
815         // TABLE
816         if (type.toLower() == QLatin1String("table")) {
817             QString token = advance();
818             // [ IF NOT EXISTS ]
819             if (token.toLower() == QLatin1String("if")) {
820                 token = advance();
821                 token = advance();
822                 token = advance();
823             }
824             return parseCreateTable(token);
825         } else if (m_source[m_srcIndex] != QLatin1String(";")) {
826             skipStmt(QLatin1String(";"));
827             return true;
828         }
829     } else if (keyword.toLower() == QLatin1String("alter")) {
830         QString type = advance();
831         if (type.toLower() == QLatin1String("table")) {
832             QString token = advance();
833             return parseAlterTable(token);
834         } else if (m_source[m_srcIndex] != QLatin1String(";")) {
835             skipStmt(QLatin1String(";"));
836             return true;
837         }
838     }
839     return true;
840 }
841 
842 /**
843  * Implement virtual method
844  * @return string with next token
845  */
advance()846 QString SQLImport::advance()
847 {
848     QString token = NativeImportBase::advance();
849     DEBUG(DBG_SRC) << m_srcIndex << token;
850     return token;
851 }
852 
addDatatype(const QStringList & type)853 UMLObject *SQLImport::addDatatype(const QStringList &type)
854 {
855     UMLObject *datatype = 0;
856     UMLPackage *parent = UMLApp::app()->document()->datatypeFolder();
857     if (type.at(0).toLower() == QLatin1String("enum")) {
858         QString name = Model_Utils::uniqObjectName(UMLObject::ot_Enum, parent, type.at(0));
859         datatype = Import_Utils::createUMLObject(UMLObject::ot_Enum, name, parent);
860         UMLEnum *enumType = datatype->asUMLEnum();
861         if (enumType == 0)
862             enumType = Import_Utils::remapUMLEnum(datatype, currentScope());
863         if (enumType) {
864             for (int i = 2; i < type.size(); i++) {
865                 Import_Utils::addEnumLiteral(enumType, type.at(i));
866             }
867         } else {
868             uError() << "Invalid dynamic cast to UMLEnum from datatype.";
869         }
870     } else {
871         datatype = Import_Utils::createUMLObject(UMLObject::ot_Datatype, type.at(0), parent);
872     }
873     return datatype;
874 }
875 
addPrimaryKey(UMLEntity * entity,const QString & _name,const QStringList & fields)876 bool SQLImport::addPrimaryKey(UMLEntity *entity, const QString &_name, const QStringList &fields)
877 {
878     if (!entity) {
879         uError() << "Could not add primary key '" << _name << "' because of zero entity.";
880         return false;
881     }
882 
883     QString name;
884     if (_name.isEmpty())
885         name = entity->name() + QLatin1String("_pkey");
886     else
887         name = _name;
888 
889     foreach(UMLObject *a, entity->getFilteredList(UMLObject::ot_EntityConstraint)) {
890         if (a->name() == name)
891             return false;
892     }
893 
894     UMLUniqueConstraint *pkey = new UMLUniqueConstraint(entity, name);
895     foreach(const QString &field, fields) {
896         foreach(UMLEntityAttribute *a, entity->getEntityAttributes()) {
897             if (a->name() == field)
898                 pkey->addEntityAttribute(a);
899         }
900     }
901     // update list view item to see 'P'
902     bool state = UMLApp::app()->document()->loading();
903     UMLApp::app()->document()->setLoading(false);
904 
905     bool result = entity->setAsPrimaryKey(pkey);
906 
907     UMLApp::app()->document()->setLoading(state);
908     return result;
909 }
910 
911 /**
912  * Add UML object for unique constraint.
913  *
914  * @param entity entity object
915  * @param _name unique constraint name
916  * @param fields field list
917  * @return true on success
918  * @return false on error
919  */
addUniqueConstraint(UMLEntity * entity,const QString & _name,const QStringList & fields)920 bool SQLImport::addUniqueConstraint(UMLEntity *entity, const QString &_name, const QStringList &fields)
921 {
922     if (!entity) {
923         uError() << "Could not add unique constraint '" << _name << "' because of zero entity.";
924         return false;
925     }
926 
927     QString name;
928     if (_name.isEmpty())
929         name = entity->name() + QLatin1String("_unique");
930     else
931         name = _name;
932 
933     foreach(UMLObject *a, entity->getFilteredList(UMLObject::ot_EntityConstraint)) {
934         if (a->name() == name)
935             return false;
936     }
937 
938     UMLUniqueConstraint *uc = new UMLUniqueConstraint(entity, name);
939     foreach(const QString &field, fields) {
940         foreach(UMLEntityAttribute *a, entity->getEntityAttributes()) {
941             if (a->name() == field)
942                 uc->addEntityAttribute(a);
943         }
944     }
945     return entity->addConstraint(uc);
946 }
947 
948 /**
949  * Add UML object foreign constraint.
950  *
951  * @param entityA entity object the foreign constraint belongs
952  * @param _name name of foreign constraint
953  * @param fieldNames list of field names
954  * @param referencedTable referenced table name
955  * @param referencedFields list of referenced field names
956  * @return true on success
957  * @return false on error
958  */
addForeignConstraint(UMLEntity * entityA,const QString & _name,const QStringList & fieldNames,const QString & referencedTable,const QStringList & referencedFields)959 bool SQLImport::addForeignConstraint(UMLEntity *entityA, const QString &_name, const QStringList &fieldNames, const QString &referencedTable, const QStringList &referencedFields)
960 {
961     if (!entityA) {
962         uError() << "Could not add foreign constraint '" << _name << "' because of zero entity.";
963         return false;
964     }
965 
966     QString name;
967     if (_name.isEmpty())
968         name = entityA->name() + QLatin1String("_foreign");
969     else
970         name = _name;
971 
972     foreach(UMLObject *a, entityA->getFilteredList(UMLObject::ot_EntityConstraint)) {
973         if (a->name() == name)
974             return false;
975     }
976 
977     UMLFolder *root = UMLApp::app()->document()->rootFolder(Uml::ModelType::EntityRelationship);
978     UMLObject *o = UMLApp::app()->document()->findUMLObject(referencedTable, UMLObject::ot_Entity, root);
979     UMLEntity *entityB = o->asUMLEntity();
980     if (!entityB)
981         return false;
982 
983     UMLForeignKeyConstraint *fc = new UMLForeignKeyConstraint(entityA, name);
984 
985     if (fieldNames.size() != referencedFields.size()) {
986         return false;
987     }
988 
989     fc->setReferencedEntity(entityB);
990 
991     for(int i = 0; i < fieldNames.size(); i++) {
992         const QString &fieldA = fieldNames.at(i);
993         const QString &fieldB = referencedFields.at(i);
994         UMLEntityAttribute *aA = 0;
995         UMLEntityAttribute *aB = 0;
996         foreach(UMLEntityAttribute *a, entityA->getEntityAttributes()) {
997             if (a->name() == fieldA) {
998                 aA = a;
999                 break;
1000             }
1001         }
1002 
1003         foreach(UMLEntityAttribute *a, entityB->getEntityAttributes()) {
1004             if (a->name() == fieldB) {
1005                 aB = a;
1006                 break;
1007             }
1008         }
1009         if (!aA || !aB)
1010             return false;
1011         fc->addEntityAttributePair(aA, aB);
1012     }
1013     return entityA->addConstraint(fc);
1014 }
1015