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