Parse foreign keys based on multiple columns

See issue #558.
This commit is contained in:
Martin Kleusberg
2016-08-18 23:17:35 +02:00
parent beff43acfc
commit 6a4d58042c
2 changed files with 76 additions and 17 deletions

View File

@@ -14,6 +14,14 @@ QString escapeIdentifier(QString id)
return '`' + id.replace('`', "``") + '`';
}
QStringList fieldVectorToFieldNames(const FieldVector& vector)
{
QStringList result;
foreach(const FieldPtr& field, vector)
result.append(escapeIdentifier(field->name()));
return result;
}
bool ForeignKeyClause::isSet() const
{
return m_override.size() || m_table.size();
@@ -99,7 +107,6 @@ void Table::clear()
m_fields.clear();
m_rowidColumn = "_rowid_";
}
Table::~Table()
{
clear();
@@ -132,17 +139,48 @@ void Table::setField(int index, FieldPtr f)
FieldPtr oldField = m_fields[index];
m_fields[index] = f;
// Update unique constraints. If an existing field is updated but was used in a unique constraint, the pointer in the
// unique constraint needs to be updated to the new field, too.
// Update unique constraints and foreign keys. If an existing field is updated but was used in a unique constraint or a foreign key, the pointer in the
// unique constraint/foreign key needs to be updated to the new field, too.
if(oldField)
{
// Unique constraints
for(int i=0;i<m_uniqueConstraints.size();++i)
{
// Loop through all the fields mentioned in each unique constraint
FieldVector& constraint = m_uniqueConstraints[i];
for(int j=0;j<constraint.size();++j)
{
if(constraint[i] == oldField)
constraint[i] = f;
// If the field that is being modified is in there update it to the new field
if(constraint[j] == oldField)
constraint[j] = f;
}
}
// Foreign keys
QMap<FieldVector, ForeignKeyClause>::iterator it = m_foreignKeyClauses.begin();
while(it != m_foreignKeyClauses.end())
{
// Loop through all fields mentioned in a foreign key
FieldVector fields = it.key();
bool modified = false;
for(int i=0;i<fields.size();++i)
{
// If the field that is being modified is in there update it to the new field and set a flag that something has changed.
// This is used below to know when to update the map key
if(fields[i] == oldField)
{
fields[i] = f;
modified = true;
}
}
if(modified)
{
// When we need to update the map key, we insert a new foreign key clause using the updated field vector and the old
// foreign key information, and delete the old one afterwards
m_foreignKeyClauses.insert(fields, it.value());
it = m_foreignKeyClauses.erase(it);
} else {
++it;
}
}
}
@@ -255,9 +293,7 @@ QString Table::sql() const
// unique constraints
foreach(FieldVector constraint, m_uniqueConstraints)
{
QStringList fieldnames;
foreach(FieldPtr field, constraint)
fieldnames.append(escapeIdentifier(field->name()));
QStringList fieldnames = fieldVectorToFieldNames(constraint);
sql += QString(",\n\tUNIQUE(%1)").arg(fieldnames.join(","));
}
@@ -267,6 +303,12 @@ QString Table::sql() const
if(f->foreignKey().isSet())
sql += QString(",\n\tFOREIGN KEY(%1) REFERENCES %2").arg(escapeIdentifier(f->name())).arg(f->foreignKey().toString());
}
QMap<FieldVector, ForeignKeyClause>::const_iterator it = m_foreignKeyClauses.constBegin();
while(it != m_foreignKeyClauses.constEnd())
{
sql += QString(",\n\tFOREIGN KEY(%1) REFERENCES %2").arg(fieldVectorToFieldNames(it.key()).join(",")).arg(it.value().toString());
++it;
}
sql += "\n)";
@@ -282,6 +324,11 @@ void Table::addUniqueConstraint(FieldVector fields)
m_uniqueConstraints.push_back(fields);
}
void Table::addForeignKey(FieldVector fields, ForeignKeyClause fk)
{
m_foreignKeyClauses.insert(fields, fk);
}
namespace
{
QString identifier(antlr::RefAST ident)
@@ -457,15 +504,21 @@ Table CreateTableWalker::table()
tc = tc->getNextSibling(); // FOREIGN
tc = tc->getNextSibling(); // KEY
tc = tc->getNextSibling(); // LPAREN
QString column_name = columnname(tc);
tc = tc->getNextSibling(); // identifier
if(tc->getType() == sqlite3TokenTypes::COMMA)
FieldVector fields;
do
{
// No support for composite foreign keys
m_bModifySupported = false;
break;
}
tc = tc->getNextSibling(); // RPAREN
QString col = columnname(tc);
FieldPtr field = tab.field(tab.findField(col));
fields.push_back(field);
tc = tc->getNextSibling();
while(tc != antlr::nullAST && tc->getType() == sqlite3TokenTypes::COMMA)
tc = tc->getNextSibling(); // skip ident and comma
} while(tc != antlr::nullAST && tc->getType() != sqlite3TokenTypes::RPAREN);
tc = tc->getNextSibling();
tc = tc->getNextSibling(); // REFERENCES
fk.setTable(identifier(tc));
@@ -489,7 +542,10 @@ Table CreateTableWalker::table()
fk.setConstraint(concatTextAST(tc, true));
tab.fields().at(tab.findField(column_name))->setForeignKey(fk);
if(fields.size() == 1)
fields[0]->setForeignKey(fk);
else
tab.addForeignKey(fields, fk);
}
break;
default:

View File

@@ -9,6 +9,7 @@
#include <QVector>
#include <QStringList>
#include <QPair>
#include <QMap>
namespace sqlb {
@@ -136,6 +137,7 @@ public:
void clear();
void addUniqueConstraint(FieldVector fields);
void addForeignKey(FieldVector fields, ForeignKeyClause fk);
/**
* @brief findField Finds a field and returns the index.
@@ -164,6 +166,7 @@ private:
FieldVector m_fields;
QString m_rowidColumn;
QVector<FieldVector> m_uniqueConstraints;
QMap<FieldVector, ForeignKeyClause> m_foreignKeyClauses;
};
/**