From 0e18e36aa9e11f1921bf2a73472ba183b35d2414 Mon Sep 17 00:00:00 2001 From: Martin Kleusberg Date: Sun, 21 Jun 2015 22:09:36 +0200 Subject: [PATCH] Parse foreign key clauses instead of just treating them as a big string This changes the SQL grammar parser so that it parses foreign key clauses instead of just reading to the end of the clause when encoutering one. This allows using the information inside the clause later in a more effective way. However, as of now this isn't used yet. This commit only attempts to imitate the old behaviour using the new approach (and might fail doing so, causing new errors...). --- src/EditTableDialog.cpp | 6 ++- src/sqlitetypes.cpp | 88 ++++++++++++++++++++++++++++++++++-- src/sqlitetypes.h | 38 ++++++++++++++-- src/tests/testsqlobjects.cpp | 10 ++-- 4 files changed, 127 insertions(+), 15 deletions(-) diff --git a/src/EditTableDialog.cpp b/src/EditTableDialog.cpp index 3dc834e5..3f83b1e8 100644 --- a/src/EditTableDialog.cpp +++ b/src/EditTableDialog.cpp @@ -115,7 +115,7 @@ void EditTableDialog::populateFields() tbitem->setText(kDefault, f->defaultValue()); tbitem->setText(kCheck, f->check()); - tbitem->setText(kForeignKey, f->foreignKey()); + tbitem->setText(kForeignKey, f->foreignKey().toString()); ui->treeWidget->addTopLevelItem(tbitem); } @@ -374,7 +374,9 @@ void EditTableDialog::itemChanged(QTreeWidgetItem *item, int column) callRenameColumn = true; break; case kForeignKey: - field->setForeignKey(item->text(column)); + sqlb::ForeignKeyClause fk; + fk.setFromString(item->text(column)); + field->setForeignKey(fk); if(!m_bNewTable) callRenameColumn = true; break; diff --git a/src/sqlitetypes.cpp b/src/sqlitetypes.cpp index 4a93f8d7..acb0fa3d 100644 --- a/src/sqlitetypes.cpp +++ b/src/sqlitetypes.cpp @@ -9,6 +9,41 @@ namespace sqlb { QStringList Field::Datatypes = QStringList() << "INTEGER" << "TEXT" << "BLOB" << "REAL" << "NUMERIC"; +bool ForeignKeyClause::isSet() const +{ + return m_override.size() || m_table.size(); +} + +QString ForeignKeyClause::toString() const +{ + if(!isSet()) + return QString(); + + if(m_override.size()) + return m_override; + + QString result = "`" + m_table + "`"; + + if(m_columns.size()) + { + result += "("; + foreach(const QString& column, m_columns) + result += "`" + column + "`,"; + result.chop(1); // Remove last comma + result += ")"; + } + + if(m_constraint.size()) + result += " " + m_constraint; + + return result; +} + +void ForeignKeyClause::setFromString(const QString& fk) +{ + m_override = fk; +} + QString Field::toString(const QString& indent, const QString& sep) const { QString str = indent + '`' + m_name + '`' + sep + m_type; @@ -194,8 +229,8 @@ QString Table::sql() const // foreign keys foreach(FieldPtr f, m_fields) { - if(!f->foreignKey().isEmpty()) - sql += QString(",\n\tFOREIGN KEY(`%1`) REFERENCES %2").arg(f->name()).arg(f->foreignKey()); + if(f->foreignKey().isSet()) + sql += QString(",\n\tFOREIGN KEY(`%1`) REFERENCES %2").arg(f->name()).arg(f->foreignKey().toString()); } sql += "\n)"; @@ -372,6 +407,8 @@ Table CreateTableWalker::table() break; case sqlite3TokenTypes::FOREIGN: { + sqlb::ForeignKeyClause fk; + tc = tc->getNextSibling(); // FOREIGN tc = tc->getNextSibling(); // KEY tc = tc->getNextSibling(); // LPAREN @@ -386,7 +423,28 @@ Table CreateTableWalker::table() tc = tc->getNextSibling(); // RPAREN tc = tc->getNextSibling(); // REFERENCES - tab.fields().at(tab.findField(column_name))->setForeignKey(concatTextAST(tc, true)); + fk.setTable(identifier(tc)); + tc = tc->getNextSibling(); // identifier + + if(tc != antlr::nullAST && tc->getType() == sqlite3TokenTypes::LPAREN) + { + tc = tc->getNextSibling(); // LPAREN + + QStringList fk_cols; + while(tc != antlr::nullAST && tc->getType() != sqlite3TokenTypes::RPAREN) + { + if(tc->getType() != sqlite3TokenTypes::COMMA) + fk_cols.push_back(identifier(tc)); + tc = tc->getNextSibling(); + } + fk.setColumns(fk_cols); + + tc = tc->getNextSibling(); // RPAREN + } + + fk.setConstraint(concatTextAST(tc, true)); + + tab.fields().at(tab.findField(column_name))->setForeignKey(fk); } break; default: @@ -423,7 +481,7 @@ void CreateTableWalker::parsecolumn(FieldPtr& f, antlr::RefAST c) bool unique = false; QString defaultvalue; QString check; - QString foreignKey; + sqlb::ForeignKeyClause foreignKey; colname = columnname(c); c = c->getNextSibling(); //type? @@ -491,7 +549,27 @@ void CreateTableWalker::parsecolumn(FieldPtr& f, antlr::RefAST c) case sqlite3TokenTypes::REFERENCES: { con = con->getNextSibling(); // REFERENCES - foreignKey = concatTextAST(con, true); + + foreignKey.setTable(identifier(con)); + con = con->getNextSibling(); // identifier + + if(con != antlr::nullAST && con->getType() == sqlite3TokenTypes::LPAREN) + { + con = con->getNextSibling(); // LPAREN + + QStringList fk_cols; + while(con != antlr::nullAST && con->getType() != sqlite3TokenTypes::RPAREN) + { + if(con->getType() != sqlite3TokenTypes::COMMA) + fk_cols.push_back(identifier(con)); + con = con->getNextSibling(); + } + foreignKey.setColumns(fk_cols); + + con = con->getNextSibling(); // RPAREN + } + + foreignKey.setConstraint(concatTextAST(con, true)); } break; default: diff --git a/src/sqlitetypes.h b/src/sqlitetypes.h index 50dbccfe..c33f443d 100644 --- a/src/sqlitetypes.h +++ b/src/sqlitetypes.h @@ -12,6 +12,38 @@ namespace sqlb { +class ForeignKeyClause +{ +public: + ForeignKeyClause(const QString& table = QString(), const QStringList& columns = QStringList(), const QString& constraint = QString()) + : m_table(table), + m_columns(columns), + m_constraint(constraint), + m_override(QString()) + { + } + + bool isSet() const; + QString toString() const; + void setFromString(const QString& fk); + + void setTable(const QString& table) { m_override = QString(); m_table = table; } + const QString& table() const { return m_table; } + + void setColumns(const QStringList& columns) { m_columns = columns; } + const QStringList& columns() const { return m_columns; } + + void setConstraint(const QString& constraint) { m_constraint = constraint; } + const QString& constraint() const { return m_constraint; } + +private: + QString m_table; + QStringList m_columns; + QString m_constraint; + + QString m_override; +}; + class Field { public: @@ -42,7 +74,7 @@ public: void setAutoIncrement(bool autoinc) { m_autoincrement = autoinc; } void setPrimaryKey(bool pk) { m_primaryKey = pk; } void setUnique(bool u) { m_unique = u; } - void setForeignKey(const QString& key) { m_foreignKey = key; } + void setForeignKey(const ForeignKeyClause& key) { m_foreignKey = key; } bool isText() const; bool isInteger() const; @@ -55,7 +87,7 @@ public: bool autoIncrement() const { return m_autoincrement; } bool primaryKey() const { return m_primaryKey; } bool unique() const { return m_unique; } - const QString& foreignKey() const { return m_foreignKey; } + const ForeignKeyClause& foreignKey() const { return m_foreignKey; } static QStringList Datatypes; private: @@ -64,7 +96,7 @@ private: bool m_notnull; QString m_check; QString m_defaultvalue; - QString m_foreignKey; // Even though this information is a table constraint easier for accessing and processing to store it here + ForeignKeyClause m_foreignKey; // Even though this information is a table constraint it's easier for accessing and processing to store it here bool m_autoincrement; //! this is stored here for simplification bool m_primaryKey; bool m_unique; diff --git a/src/tests/testsqlobjects.cpp b/src/tests/testsqlobjects.cpp index 63ecaba2..9d828764 100644 --- a/src/tests/testsqlobjects.cpp +++ b/src/tests/testsqlobjects.cpp @@ -82,12 +82,12 @@ void TestTable::foreignKeys() { Table tt("testtable"); FieldPtr f = FieldPtr(new Field("a", "integer")); - f->setForeignKey("b(c)"); + f->setForeignKey(sqlb::ForeignKeyClause("b", QStringList("c"))); tt.addField(f); QCOMPARE(tt.sql(), QString("CREATE TABLE `testtable` (\n" "\t`a`\tinteger,\n" - "\tFOREIGN KEY(`a`) REFERENCES b(c)\n" + "\tFOREIGN KEY(`a`) REFERENCES `b`(`c`)\n" ");")); } @@ -241,17 +241,17 @@ void TestTable::parseSQLEscapedQuotes() void TestTable::parseSQLForeignKeys() { - QString sql = "CREATE TABLE foreign_key_test(a int, b int, foreign key (a) references x, foreign key (b) references w(z) on delete set null);"; + QString sql = "CREATE TABLE foreign_key_test(a int, b int, foreign key (a) references x, foreign key (b) references w(y,z) on delete set null);"; Table tab = Table::parseSQL(sql).first; QCOMPARE(tab.name(), QString("foreign_key_test")); QCOMPARE(tab.fields().at(0)->name(), QString("a")); QCOMPARE(tab.fields().at(0)->type(), QString("int")); - QCOMPARE(tab.fields().at(0)->foreignKey(), QString("x")); + QCOMPARE(tab.fields().at(0)->foreignKey().table(), QString("x")); QCOMPARE(tab.fields().at(1)->name(), QString("b")); QCOMPARE(tab.fields().at(1)->type(), QString("int")); - QCOMPARE(tab.fields().at(1)->foreignKey(), QString("w ( z ) on delete set null")); + QCOMPARE(tab.fields().at(1)->foreignKey().toString(), QString("`w`(`y`,`z`) on delete set null")); } void TestTable::parseSQLCheckConstraint()