Code refactoring

This commit refactors vast parts of the sqlitetypes.h interface. Its
main goals are: less code, easier code, a more modern interface, reduced
likelihood for strange errors and more flexibility for future
extensions.

The main reason why the sqlitetypes.h functions were working so well in
DB4S was not because they were that stable but because they were
extremely interlinked with the rest of the code. This is fine because we
do not plan to ship them as a separate library. But it makes it hard to
find the obvious spot to fix an issue or to put a new function. It can
always be done in the sqlitetypes function or in the rest of the DB4S
code because it is just not clear what the interface between the two
should look like. This is supposed to be improved by this commit. One
main thing here is to make ownership of objects a bit clearer.

In theory the new code should be faster too but that difference will be
neglectable from a user POV.

This commit also fixes a hidden bug which caused all table constraints
to be removed in the Edit Table dialog when a single field was removed
from the table.

This is all still WIP and more work is needed to be done here.
This commit is contained in:
Martin Kleusberg
2018-09-04 19:46:39 +02:00
parent f3e6aec57d
commit bf505edf66
16 changed files with 685 additions and 792 deletions

View File

@@ -112,7 +112,7 @@ AddRecordDialog::AddRecordDialog(DBBrowserDB& db, const sqlb::ObjectIdentifier&
ui(new Ui::AddRecordDialog),
pdb(db),
curTable(tableName),
m_table(*(pdb.getObjectByName(curTable).dynamicCast<sqlb::Table>()))
m_table(*(pdb.getObjectByName<sqlb::Table>(curTable)))
{
// Create UI
ui->setupUi(this);
@@ -174,43 +174,42 @@ void AddRecordDialog::populateFields()
ui->treeWidget->setItemDelegateForColumn(kType, new NoEditDelegate(this));
ui->treeWidget->setItemDelegateForColumn(kValue, new EditDelegate(this));
const sqlb::FieldVector& fields = m_table.fields();
const sqlb::FieldVector& pk = m_table.primaryKey();
for(const sqlb::FieldPtr& f : fields)
const auto& fields = m_table.fields;
const QStringList& pk = m_table.primaryKey();
for(const sqlb::Field& f : fields)
{
QTreeWidgetItem *tbitem = new QTreeWidgetItem(ui->treeWidget);
tbitem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsEditable);
tbitem->setText(kName, f->name());
tbitem->setText(kType, f->type());
tbitem->setData(kType, Qt::UserRole, f->affinity());
tbitem->setText(kName, f.name());
tbitem->setText(kType, f.type());
tbitem->setData(kType, Qt::UserRole, f.affinity());
// NOT NULL fields are indicated in bold.
if (f->notnull()) {
if (f.notnull()) {
QFont font;
font.setBold(true);
tbitem->setData(kName, Qt::FontRole, font);
}
if (pk.contains(f))
if (contains(pk, f.name()))
tbitem->setIcon(kName, QIcon(":/icons/field_key"));
else
tbitem->setIcon(kName, QIcon(":/icons/field"));
QString defaultValue = f->defaultValue();
QString defaultValue = f.defaultValue();
QString toolTip;
if (f->autoIncrement())
if (f.autoIncrement())
toolTip.append(tr("Auto-increment\n"));
if (f->unique())
if (f.unique())
toolTip.append(tr("Unique constraint\n"));
if (!f->check().isEmpty())
toolTip.append(tr("Check constraint:\t %1\n").arg (f->check()));
if (!f.check().isEmpty())
toolTip.append(tr("Check constraint:\t %1\n").arg (f.check()));
QSharedPointer<sqlb::ForeignKeyClause> fk =
m_table.constraint({f}, sqlb::Constraint::ForeignKeyConstraintType).dynamicCast<sqlb::ForeignKeyClause>();
auto fk = std::dynamic_pointer_cast<sqlb::ForeignKeyClause>(m_table.constraint({f.name()}, sqlb::Constraint::ForeignKeyConstraintType));
if(fk)
toolTip.append(tr("Foreign key:\t %1\n").arg(fk->toString()));
@@ -219,7 +218,7 @@ void AddRecordDialog::populateFields()
// Display Role is used for displaying the default values.
// Only when they are changed, the User Role is updated and then used in the INSERT query.
if (!defaultValue.isEmpty()) {
tbitem->setData(kValue, Qt::DisplayRole, f->defaultValue());
tbitem->setData(kValue, Qt::DisplayRole, f.defaultValue());
toolTip.append(tr("Default value:\t %1\n").arg (defaultValue));
} else
tbitem->setData(kValue, Qt::DisplayRole, Settings::getValue("databrowser", "null_text"));

View File

@@ -326,12 +326,7 @@ void DbStructureModel::buildTree(QTreeWidgetItem* parent, const QString& schema)
{
QStringList pk_columns;
if(it->type() == sqlb::Object::Types::Table)
{
sqlb::FieldVector pk = it.dynamicCast<sqlb::Table>()->primaryKey();
for(const sqlb::FieldPtr& pk_col : pk)
pk_columns.push_back(pk_col->name());
}
pk_columns = std::dynamic_pointer_cast<sqlb::Table>(it)->primaryKey();
for(const sqlb::FieldInfo& field : fieldList)
{
QTreeWidgetItem *fldItem = new QTreeWidgetItem(item);

View File

@@ -2,10 +2,11 @@
#define DBSTRUCTUREMODEL_H
#include <QAbstractItemModel>
#include <memory>
class DBBrowserDB;
class QTreeWidgetItem;
namespace sqlb { class Object; typedef QSharedPointer<Object> ObjectPtr; }
namespace sqlb { class Object; using ObjectPtr = std::shared_ptr<Object>; }
class DbStructureModel : public QAbstractItemModel
{

View File

@@ -53,7 +53,7 @@ EditIndexDialog::EditIndexDialog(DBBrowserDB& db, const sqlb::ObjectIdentifier&
if(!newIndex)
{
// Load the current layout and fill in the dialog fields
index = *(pdb.getObjectByName(curIndex).dynamicCast<sqlb::Index>());
index = *(pdb.getObjectByName<sqlb::Index>(curIndex));
ui->editIndexName->blockSignals(true);
ui->editIndexName->setText(index.name());
@@ -77,7 +77,7 @@ EditIndexDialog::EditIndexDialog(DBBrowserDB& db, const sqlb::ObjectIdentifier&
connect(ui->tableIndexColumns, static_cast<void(QTableWidget::*)(QTableWidgetItem*)>(&QTableWidget::itemChanged),
[=](QTableWidgetItem* item)
{
index.columns().at(item->row())->setName(item->text());
index.fields[item->row()].setName(item->text());
updateSqlText();
});
@@ -96,7 +96,7 @@ void EditIndexDialog::tableChanged(const QString& new_table, bool initialLoad)
if(!initialLoad)
{
index.setTable(sqlb::ObjectIdentifier(ui->comboTableName->currentData()).name());
index.clearColumns();
index.fields.clear();
}
// Stop here if table name is empty
@@ -113,18 +113,18 @@ void EditIndexDialog::tableChanged(const QString& new_table, bool initialLoad)
void EditIndexDialog::updateColumnLists()
{
// Fill the table column list
sqlb::TablePtr table = pdb.getObjectByName(sqlb::ObjectIdentifier(ui->comboTableName->currentData())).dynamicCast<sqlb::Table>();
sqlb::TablePtr table = pdb.getObjectByName<sqlb::Table>(sqlb::ObjectIdentifier(ui->comboTableName->currentData()));
if(!table)
return;
sqlb::FieldInfoList tableFields = table->fieldInformation();
ui->tableTableColumns->setRowCount(tableFields.size());
int tableRows = 0;
for(int i=0;i<tableFields.size();++i)
for(size_t i=0;i<tableFields.size();++i)
{
// When we're doing the initial loading and this field already is in the index to edit, then don't add it to the
// list of table columns. It will be added to the list of index columns in the next step. When this is not the initial
// loading, the index column list is empty, so this check will always be true.
if(index.findColumn(tableFields.at(i).name) == -1)
if(sqlb::findField(index, tableFields.at(i).name) == index.fields.end())
{
// Put the name of the field in the first column
QTableWidgetItem* name = new QTableWidgetItem(tableFields.at(i).name);
@@ -145,15 +145,15 @@ void EditIndexDialog::updateColumnLists()
// Fill the index column list. This is done separately from the table column to include expression columns (these are not found in the original
// table) and to preserve the order of the index columns
auto indexFields = index.columns();
auto indexFields = index.fields;
ui->tableIndexColumns->blockSignals(true);
ui->tableIndexColumns->setRowCount(indexFields.size());
for(int i=0;i<indexFields.size();++i)
for(size_t i=0;i<indexFields.size();++i)
{
// Put the name of the field in the first column
QTableWidgetItem* name = new QTableWidgetItem(indexFields.at(i)->name());
QTableWidgetItem* name = new QTableWidgetItem(indexFields.at(i).name());
Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
if(indexFields.at(i)->expression())
if(indexFields.at(i).expression())
flags |= Qt::ItemIsEditable;
name->setFlags(flags);
ui->tableIndexColumns->setItem(i, 0, name);
@@ -163,15 +163,15 @@ void EditIndexDialog::updateColumnLists()
order->addItem("");
order->addItem("ASC");
order->addItem("DESC");
order->setCurrentText(indexFields.at(i)->order().toUpper());
order->setCurrentText(indexFields.at(i).order().toUpper());
ui->tableIndexColumns->setCellWidget(i, 1, order);
connect(order, static_cast<void(QComboBox::*)(const QString&)>(&QComboBox::currentTextChanged),
[=](QString new_order)
{
int colnum = index.findColumn(indexFields.at(i)->name());
if(colnum != -1)
auto colnum = sqlb::findField(index, indexFields.at(i).name());
if(colnum != index.fields.end())
{
index.column(colnum)->setOrder(new_order);
colnum->setOrder(new_order);
updateSqlText();
}
});
@@ -195,10 +195,10 @@ void EditIndexDialog::addToIndex(const QModelIndex& idx)
return;
// Add field to index
index.addColumn(sqlb::IndexedColumnPtr(new sqlb::IndexedColumn(
ui->tableTableColumns->item(row, 0)->text(), // Column name
false, // Is expression
""))); // Order
index.fields.emplace_back(
ui->tableTableColumns->item(row, 0)->text(), // Column name
false, // Is expression
""); // Order
// Update UI
updateColumnLists();
@@ -220,14 +220,14 @@ void EditIndexDialog::removeFromIndex(const QModelIndex& idx)
// If this is an expression column and the action was triggered by a double click event instead of a button click,
// we won't remove the expression column because it's too likely that this was only done by accident by the user.
// Instead just open the expression column for editing.
if(index.column(row)->expression() && sender() != ui->buttonFromIndex)
if(index.fields[row].expression() && sender() != ui->buttonFromIndex)
{
ui->tableIndexColumns->editItem(ui->tableIndexColumns->item(row, 0));
return;
}
// Remove column from index
index.removeColumn(ui->tableIndexColumns->item(row, 0)->text());
sqlb::removeField(index, ui->tableIndexColumns->item(row, 0)->text());
// Update UI
updateColumnLists();
@@ -245,7 +245,7 @@ void EditIndexDialog::checkInput()
valid = false;
// Check if index has any columns
if(index.columns().size() == 0)
if(index.fields.size() == 0)
valid = false;
// Only activate OK button if index data is valid
@@ -312,10 +312,8 @@ void EditIndexDialog::moveCurrentColumn(bool down)
if(newRow >= ui->tableIndexColumns->rowCount())
return;
// Get the column information, swap the columns, and save the new column list back in the index
auto columns = index.columns();
std::swap(columns[currentRow], columns[newRow]);
index.setColumns(columns);
// Swap the columns
std::swap(index.fields[currentRow], index.fields[newRow]);
// Update UI
updateColumnLists();
@@ -327,16 +325,17 @@ void EditIndexDialog::moveCurrentColumn(bool down)
void EditIndexDialog::addExpressionColumn()
{
// Check if there already is an empty expression column
int row = index.findColumn("");
if(row == -1)
auto field_it = sqlb::findField(index, "");
int row = std::distance(index.fields.begin(), field_it);
if(field_it == index.fields.end())
{
// There is no empty expression column yet, so add one.
// Add new expression column to the index
index.addColumn(sqlb::IndexedColumnPtr(new sqlb::IndexedColumn(
"", // Column name
true, // Is expression
""))); // Order
index.fields.emplace_back(
"", // Column name
true, // Is expression
""); // Order
// Update UI
updateColumnLists();

View File

@@ -10,6 +10,7 @@
#include <QComboBox>
#include <QDateTime>
#include <QKeyEvent>
#include <algorithm>
EditTableDialog::EditTableDialog(DBBrowserDB& db, const sqlb::ObjectIdentifier& tableName, bool createTable, QWidget* parent)
: QDialog(parent),
@@ -33,7 +34,7 @@ EditTableDialog::EditTableDialog(DBBrowserDB& db, const sqlb::ObjectIdentifier&
if(m_bNewTable == false)
{
// Existing table, so load and set the current layout
m_table = *(pdb.getObjectByName(curTable).dynamicCast<sqlb::Table>());
m_table = *(pdb.getObjectByName<sqlb::Table>(curTable));
ui->labelEditWarning->setVisible(!m_table.fullyParsed());
// Set without rowid checkbox and schema dropdown. No need to trigger any events here as we're only loading a table exactly as it is stored by SQLite, so no need
@@ -103,22 +104,22 @@ void EditTableDialog::populateFields()
this,SLOT(itemChanged(QTreeWidgetItem*,int)));
ui->treeWidget->clear();
sqlb::FieldVector fields = m_table.fields();
sqlb::FieldVector pk = m_table.primaryKey();
for(const sqlb::FieldPtr& f : fields)
const auto& fields = m_table.fields;
QStringList pk = m_table.primaryKey();
for(const sqlb::Field& f : fields)
{
QTreeWidgetItem *tbitem = new QTreeWidgetItem(ui->treeWidget);
tbitem->setFlags(tbitem->flags() | Qt::ItemIsEditable);
tbitem->setText(kName, f->name());
tbitem->setText(kName, f.name());
QComboBox* typeBox = new QComboBox(ui->treeWidget);
typeBox->setProperty("column", f->name());
typeBox->setProperty("column", f.name());
typeBox->setEditable(true);
typeBox->addItems(sqlb::Field::Datatypes);
int index = typeBox->findText(f->type(), Qt::MatchExactly);
typeBox->addItems(DBBrowserDB::Datatypes);
int index = typeBox->findText(f.type(), Qt::MatchExactly);
if(index == -1)
{
// non standard named type
typeBox->addItem(f->type());
typeBox->addItem(f.type());
index = typeBox->count() - 1;
}
typeBox->setCurrentIndex(index);
@@ -126,22 +127,22 @@ void EditTableDialog::populateFields()
connect(typeBox, SIGNAL(currentIndexChanged(int)), this, SLOT(updateTypes()));
ui->treeWidget->setItemWidget(tbitem, kType, typeBox);
tbitem->setCheckState(kNotNull, f->notnull() ? Qt::Checked : Qt::Unchecked);
tbitem->setCheckState(kPrimaryKey, pk.contains(f) ? Qt::Checked : Qt::Unchecked);
tbitem->setCheckState(kAutoIncrement, f->autoIncrement() ? Qt::Checked : Qt::Unchecked);
tbitem->setCheckState(kUnique, f->unique() ? Qt::Checked : Qt::Unchecked);
tbitem->setCheckState(kNotNull, f.notnull() ? Qt::Checked : Qt::Unchecked);
tbitem->setCheckState(kPrimaryKey, contains(pk, f.name()) ? Qt::Checked : Qt::Unchecked);
tbitem->setCheckState(kAutoIncrement, f.autoIncrement() ? Qt::Checked : Qt::Unchecked);
tbitem->setCheckState(kUnique, f.unique() ? Qt::Checked : Qt::Unchecked);
// For the default value check if it is surrounded by parentheses and if that's the case
// add a '=' character before the entire string to match the input format we're expecting
// from the user when using functions in the default value field.
if(f->defaultValue().startsWith('(') && f->defaultValue().endsWith(')'))
tbitem->setText(kDefault, "=" + f->defaultValue());
if(f.defaultValue().startsWith('(') && f.defaultValue().endsWith(')'))
tbitem->setText(kDefault, "=" + f.defaultValue());
else
tbitem->setText(kDefault, f->defaultValue());
tbitem->setText(kDefault, f.defaultValue());
tbitem->setText(kCheck, f->check());
tbitem->setText(kCheck, f.check());
QSharedPointer<sqlb::ForeignKeyClause> fk = m_table.constraint({f}, sqlb::Constraint::ForeignKeyConstraintType).dynamicCast<sqlb::ForeignKeyClause>();
auto fk = std::dynamic_pointer_cast<sqlb::ForeignKeyClause>(m_table.constraint({f.name()}, sqlb::Constraint::ForeignKeyConstraintType));
if(fk)
tbitem->setText(kForeignKey, fk->toString());
ui->treeWidget->addTopLevelItem(tbitem);
@@ -210,14 +211,14 @@ void EditTableDialog::checkInput()
m_fkEditorDelegate->updateTablesList(oldTableName);
// update fk's that refer to table itself recursively
sqlb::FieldVector fields = m_table.fields();
for(const sqlb::FieldPtr& f : fields) {
QSharedPointer<sqlb::ForeignKeyClause> fk = m_table.constraint({f}, sqlb::Constraint::ForeignKeyConstraintType).dynamicCast<sqlb::ForeignKeyClause>();
if(!fk.isNull()) {
const auto& fields = m_table.fields;
for(const sqlb::Field& f : fields) {
auto fk = std::dynamic_pointer_cast<sqlb::ForeignKeyClause>(m_table.constraint({f.name()}, sqlb::Constraint::ForeignKeyConstraintType));
if(fk) {
if (oldTableName == fk->table()) {
fk->setTable(normTableName);
if(!m_bForeignKeysEnabled)
pdb.alterTable(curTable, m_table, f->name(), f, 0);
pdb.alterTable(curTable, m_table, f.name(), std::make_shared<sqlb::Field>(f), 0);
}
}
}
@@ -237,16 +238,16 @@ void EditTableDialog::updateTypes(QObject *object)
QString type = typeBox->currentText();
QString column = typeBox->property("column").toString();
int index;
for(index=0; index < m_table.fields().size(); ++index)
size_t index;
for(index=0; index < m_table.fields.size(); ++index)
{
if(m_table.fields().at(index)->name() == column)
if(m_table.fields.at(index).name() == column)
break;
}
m_table.fields().at(index)->setType(type);
m_table.fields.at(index).setType(type);
if(!m_bNewTable)
pdb.alterTable(curTable, m_table, column, m_table.fields().at(index));
pdb.alterTable(curTable, m_table, column, std::make_shared<sqlb::Field>(m_table.fields.at(index)));
checkInput();
}
}
@@ -268,11 +269,11 @@ bool EditTableDialog::eventFilter(QObject *object, QEvent *event)
void EditTableDialog::itemChanged(QTreeWidgetItem *item, int column)
{
int index = ui->treeWidget->indexOfTopLevelItem(item);
if(index < m_table.fields().count())
if(index < static_cast<int>(m_table.fields.size()))
{
sqlb::FieldPtr field = m_table.fields().at(index);
sqlb::Field& field = m_table.fields.at(index);
bool callRenameColumn = false;
QString oldFieldName = field->name();
QString oldFieldName = field.name();
switch(column)
{
@@ -284,8 +285,8 @@ void EditTableDialog::itemChanged(QTreeWidgetItem *item, int column)
// with different case. Example: if I rename column 'COLUMN' to 'column', findField() is going to return the current field number
// because it's doing a case-independent search and it can't return another field number because SQLite prohibits duplicate field
// names (no matter the case). So when this happens we just allow the renaming because there's no harm to be expected from it.
int foundField = m_table.findField(item->text(column));
if(foundField != -1 && foundField != index)
auto foundField = sqlb::findField(m_table, item->text(column));
if(foundField != m_table.fields.end() && foundField-m_table.fields.begin() != index)
{
QMessageBox::warning(this, qApp->applicationName(), tr("There already is a field with that name. Please rename it first or choose a different "
"name for this field."));
@@ -299,16 +300,16 @@ void EditTableDialog::itemChanged(QTreeWidgetItem *item, int column)
// When editing an exiting table, check if any foreign keys would cause trouble in case this name is edited
if(!m_bNewTable)
{
sqlb::FieldVector pk = m_table.primaryKey();
QStringList pk = m_table.primaryKey();
for(const sqlb::ObjectPtr& fkobj : pdb.schemata[curTable.schema()].values("table"))
{
QList<sqlb::ConstraintPtr> fks = fkobj.dynamicCast<sqlb::Table>()->constraints(sqlb::FieldVector(), sqlb::Constraint::ForeignKeyConstraintType);
auto fks = std::dynamic_pointer_cast<sqlb::Table>(fkobj)->constraints(QStringList(), sqlb::Constraint::ForeignKeyConstraintType);
for(const sqlb::ConstraintPtr& fkptr : fks)
{
QSharedPointer<sqlb::ForeignKeyClause> fk = fkptr.dynamicCast<sqlb::ForeignKeyClause>();
auto fk = std::dynamic_pointer_cast<sqlb::ForeignKeyClause>(fkptr);
if(fk->table() == m_table.name())
{
if(fk->columns().contains(field->name()) || pk.contains(field))
if(fk->columns().contains(field.name()) || contains(pk, field.name()))
{
QMessageBox::warning(this, qApp->applicationName(), tr("This column is referenced in a foreign key in table %1 and thus "
"its name cannot be changed.")
@@ -324,7 +325,8 @@ void EditTableDialog::itemChanged(QTreeWidgetItem *item, int column)
}
}
field->setName(item->text(column));
field.setName(item->text(column));
m_table.renameKeyInAllConstraints(oldFieldName, item->text(column));
qobject_cast<QComboBox*>(ui->treeWidget->itemWidget(item, kType))->setProperty("column", item->text(column));
if(!m_bNewTable)
callRenameColumn = true;
@@ -335,28 +337,17 @@ void EditTableDialog::itemChanged(QTreeWidgetItem *item, int column)
case kPrimaryKey:
{
// Check if there already is a primary key
if(m_table.constraint(sqlb::FieldVector(), sqlb::Constraint::PrimaryKeyConstraintType))
if(m_table.constraint(QStringList(), sqlb::Constraint::PrimaryKeyConstraintType))
{
// There already is a primary key for this table. So edit that one as there always can only be one primary key anyway.
sqlb::FieldVector& pk = m_table.primaryKeyRef();
QStringList& pk = m_table.primaryKeyRef();
if(item->checkState(column) == Qt::Checked)
pk.push_back(field);
pk.push_back(field.name());
else
#if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)
pk.removeAll(field);
#else
{
int idx = pk.indexOf (field);
while ( idx != -1 )
{
pk.remove (idx);
idx = pk.indexOf (field);
}
}
#endif
pk.erase(std::remove(pk.begin(), pk.end(), field.name()), pk.end());
} else if(item->checkState(column) == Qt::Checked) {
// There is no primary key in the table yet. This means we need to add a default one.
m_table.addConstraint({field}, sqlb::ConstraintPtr(new sqlb::PrimaryKeyConstraint()));
m_table.addConstraint({field.name()}, sqlb::ConstraintPtr(new sqlb::PrimaryKeyConstraint()));
}
if(item->checkState(column) == Qt::Checked)
@@ -385,9 +376,9 @@ void EditTableDialog::itemChanged(QTreeWidgetItem *item, int column)
// to at least replace all troublesome NULL values by the default value
SqliteTableModel m(pdb, this);
m.setQuery(QString("SELECT COUNT(%1) FROM %2 WHERE %3 IS NULL;")
.arg(sqlb::escapeIdentifier(pdb.getObjectByName(curTable).dynamicCast<sqlb::Table>()->rowidColumn()))
.arg(sqlb::escapeIdentifier(pdb.getObjectByName<sqlb::Table>(curTable)->rowidColumn()))
.arg(curTable.toString())
.arg(sqlb::escapeIdentifier(field->name())));
.arg(sqlb::escapeIdentifier(field.name())));
if(!m.completeCache())
{
// If we couldn't load all data because the cancel button was clicked, just unset the checkbox again and stop.
@@ -403,7 +394,7 @@ void EditTableDialog::itemChanged(QTreeWidgetItem *item, int column)
return;
}
}
field->setNotNull(item->checkState(column) == Qt::Checked);
field.setNotNull(item->checkState(column) == Qt::Checked);
if(!m_bNewTable)
callRenameColumn = true;
}
@@ -419,8 +410,8 @@ void EditTableDialog::itemChanged(QTreeWidgetItem *item, int column)
SqliteTableModel m(pdb, this);
m.setQuery(QString("SELECT COUNT(*) FROM %1 WHERE %2 <> CAST(%3 AS INTEGER);")
.arg(curTable.toString())
.arg(sqlb::escapeIdentifier(field->name()))
.arg(sqlb::escapeIdentifier(field->name())));
.arg(sqlb::escapeIdentifier(field.name()))
.arg(sqlb::escapeIdentifier(field.name())));
if(!m.completeCache())
{
// If we couldn't load all data because the cancel button was clicked, just unset the checkbox again and stop.
@@ -454,7 +445,7 @@ void EditTableDialog::itemChanged(QTreeWidgetItem *item, int column)
}
}
}
field->setAutoIncrement(ischecked);
field.setAutoIncrement(ischecked);
if(!m_bNewTable)
callRenameColumn = true;
@@ -467,7 +458,7 @@ void EditTableDialog::itemChanged(QTreeWidgetItem *item, int column)
{
// Because our renameColumn() function fails when setting a column to unique when it already contains the same values
SqliteTableModel m(pdb, this);
m.setQuery(QString("SELECT COUNT(%2) FROM %1;").arg(curTable.toString()).arg(sqlb::escapeIdentifier(field->name())));
m.setQuery(QString("SELECT COUNT(%2) FROM %1;").arg(curTable.toString()).arg(sqlb::escapeIdentifier(field.name())));
if(!m.completeCache())
{
// If we couldn't load all data because the cancel button was clicked, just unset the checkbox again and stop.
@@ -475,7 +466,7 @@ void EditTableDialog::itemChanged(QTreeWidgetItem *item, int column)
return;
}
int rowcount = m.data(m.index(0, 0)).toInt();
m.setQuery(QString("SELECT COUNT(DISTINCT %2) FROM %1;").arg(curTable.toString()).arg(sqlb::escapeIdentifier(field->name())));
m.setQuery(QString("SELECT COUNT(DISTINCT %2) FROM %1;").arg(curTable.toString()).arg(sqlb::escapeIdentifier(field.name())));
if(!m.completeCache())
{
// If we couldn't load all data because the cancel button was clicked, just unset the checkbox again and stop.
@@ -486,13 +477,13 @@ void EditTableDialog::itemChanged(QTreeWidgetItem *item, int column)
if(rowcount != uniquecount)
{
// There is a NULL value, so print an error message, uncheck the combobox, and return here
QMessageBox::information(this, qApp->applicationName(), tr("Column '%1' has no unique data.\n").arg(field->name())
QMessageBox::information(this, qApp->applicationName(), tr("Column '%1' has no unique data.\n").arg(field.name())
+ tr("This makes it impossible to set this flag. Please change the table data first."));
item->setCheckState(column, Qt::Unchecked);
return;
}
}
field->setUnique(item->checkState(column) == Qt::Checked);
field.setUnique(item->checkState(column) == Qt::Checked);
if(!m_bNewTable)
callRenameColumn = true;
@@ -525,13 +516,13 @@ void EditTableDialog::itemChanged(QTreeWidgetItem *item, int column)
}
}
}
field->setDefaultValue(new_value);
field.setDefaultValue(new_value);
if(!m_bNewTable)
callRenameColumn = true;
}
break;
case kCheck:
field->setCheck(item->text(column));
field.setCheck(item->text(column));
if(!m_bNewTable)
callRenameColumn = true;
break;
@@ -545,7 +536,7 @@ void EditTableDialog::itemChanged(QTreeWidgetItem *item, int column)
if(callRenameColumn)
{
if(!pdb.alterTable(curTable, m_table, oldFieldName, field))
if(!pdb.alterTable(curTable, m_table, oldFieldName, std::make_shared<sqlb::Field>(field)))
QMessageBox::warning(this, qApp->applicationName(), tr("Modifying this column failed. Error returned from database:\n%1").arg(pdb.lastError()));
}
}
@@ -567,18 +558,18 @@ void EditTableDialog::addField()
{
field_name = "Field" + QString::number(field_number);
field_number++;
} while(m_table.findField(field_name) != -1);
} while(sqlb::findField(m_table, field_name) != m_table.fields.end());
tbitem->setText(kName, field_name);
}
QComboBox* typeBox = new QComboBox(ui->treeWidget);
typeBox->setProperty("column", tbitem->text(kName));
typeBox->setEditable(true);
typeBox->addItems(sqlb::Field::Datatypes);
typeBox->addItems(DBBrowserDB::Datatypes);
int defaultFieldTypeIndex = Settings::getValue("db", "defaultfieldtype").toInt();
if (defaultFieldTypeIndex < sqlb::Field::Datatypes.count())
if (defaultFieldTypeIndex < DBBrowserDB::Datatypes.count())
{
typeBox->setCurrentIndex(defaultFieldTypeIndex);
}
@@ -597,15 +588,11 @@ void EditTableDialog::addField()
ui->treeWidget->editItem(tbitem, 0);
// add field to table object
sqlb::FieldPtr f(new sqlb::Field(
tbitem->text(kName),
typeBox->currentText()
));
m_table.addField(f);
m_table.fields.emplace_back(tbitem->text(kName), typeBox->currentText());
// Actually add the new column to the table if we're editing an existing table
if(!m_bNewTable)
pdb.addColumn(curTable, f);
pdb.addColumn(curTable, m_table.fields.back());
checkInput();
}
@@ -622,9 +609,8 @@ void EditTableDialog::removeField()
// Creating a new one
// Just delete that item. At this point there is no DB table to edit or data to be lost anyway
sqlb::FieldVector fields = m_table.fields();
fields.remove(ui->treeWidget->indexOfTopLevelItem(ui->treeWidget->currentItem()));
m_table.setFields(fields);
m_table.fields.erase(m_table.fields.begin() + ui->treeWidget->indexOfTopLevelItem(ui->treeWidget->currentItem()));
m_table.removeKeyFromAllConstraints(ui->treeWidget->currentItem()->text(kName));
delete ui->treeWidget->currentItem();
} else {
// Editing an old one
@@ -638,7 +624,7 @@ void EditTableDialog::removeField()
QMessageBox::warning(nullptr, QApplication::applicationName(), pdb.lastError());
} else {
//relayout
m_table = *(pdb.getObjectByName(curTable).dynamicCast<sqlb::Table>());
m_table = *(pdb.getObjectByName<sqlb::Table>(curTable));
populateFields();
}
}
@@ -704,9 +690,7 @@ void EditTableDialog::moveCurrentField(bool down)
ui->treeWidget->setCurrentIndex(ui->treeWidget->currentIndex().sibling(newRow, 0));
// Finally update the table SQL
sqlb::FieldVector fields = m_table.fields();
std::swap(fields[newRow], fields[currentRow]);
m_table.setFields(fields);
std::swap(m_table.fields[newRow], m_table.fields[currentRow]);
} else {
// Editing an old one
@@ -715,14 +699,14 @@ void EditTableDialog::moveCurrentField(bool down)
curTable,
m_table,
ui->treeWidget->currentItem()->text(0),
m_table.fields().at(ui->treeWidget->indexOfTopLevelItem(ui->treeWidget->currentItem())),
std::make_shared<sqlb::Field>(m_table.fields.at(ui->treeWidget->indexOfTopLevelItem(ui->treeWidget->currentItem()))),
(down ? 1 : -1)
))
{
QMessageBox::warning(nullptr, QApplication::applicationName(), pdb.lastError());
} else {
// Reload table SQL
m_table = *(pdb.getObjectByName(curTable).dynamicCast<sqlb::Table>());
m_table = *(pdb.getObjectByName<sqlb::Table>(curTable));
populateFields();
// Select old item at new position
@@ -739,8 +723,8 @@ void EditTableDialog::setWithoutRowid(bool without_rowid)
if(without_rowid)
{
// Before setting the without rowid flag, first perform a check to see if the table meets all the required criteria for without rowid tables
int pk = m_table.findPk();
if(pk == -1 || m_table.fields().at(pk)->autoIncrement())
auto pk = m_table.findPk();
if(pk == m_table.fields.end() || pk->autoIncrement())
{
QMessageBox::information(this, QApplication::applicationName(),
tr("Please add a field which meets the following criteria before setting the without rowid flag:\n"
@@ -756,7 +740,7 @@ void EditTableDialog::setWithoutRowid(bool without_rowid)
}
// If it does, override the the rowid column name of the table object with the name of the primary key.
m_table.setRowidColumn(m_table.fields().at(pk)->name());
m_table.setRowidColumn(pk->name());
} else {
// If the without rowid flag is unset no further checks are required. Just set the rowid column name back to "_rowid_"
m_table.setRowidColumn("_rowid_");

View File

@@ -88,7 +88,7 @@ ForeignKeyEditorDelegate::ForeignKeyEditorDelegate(const DBBrowserDB& db, sqlb::
if((*jt)->type() == sqlb::Object::Types::Table)
{
QString tableName = (*jt)->name();
m_tablesIds.insert(tableName, (*jt).dynamicCast<sqlb::Table>()->fieldNames());
m_tablesIds.insert(tableName, std::dynamic_pointer_cast<sqlb::Table>(*jt)->fieldNames());
}
}
}
@@ -123,9 +123,9 @@ void ForeignKeyEditorDelegate::setEditorData(QWidget* editor, const QModelIndex&
ForeignKeyEditor* fkEditor = static_cast<ForeignKeyEditor*>(editor);
int column = index.row(); // weird? I know right
sqlb::FieldPtr field = m_table.fields().at(column);
QSharedPointer<sqlb::ForeignKeyClause> fk = m_table.constraint({field}, sqlb::Constraint::ForeignKeyConstraintType).dynamicCast<sqlb::ForeignKeyClause>();
if (!fk.isNull()) {
const sqlb::Field& field = m_table.fields.at(column);
auto fk = std::dynamic_pointer_cast<sqlb::ForeignKeyClause>(m_table.constraint({field.name()}, sqlb::Constraint::ForeignKeyConstraintType));
if (fk) {
fkEditor->tablesComboBox->setCurrentText(fk->table());
fkEditor->clauseEdit->setText(fk->constraint());
if (!fk->columns().isEmpty())
@@ -141,10 +141,10 @@ void ForeignKeyEditorDelegate::setModelData(QWidget* editor, QAbstractItemModel*
QString sql = fkEditor->getSql();
int column = index.row();
sqlb::FieldPtr field = m_table.fields().at(column);
const sqlb::Field& field = m_table.fields.at(column);
if (sql.isEmpty()) {
// Remove the foreign key
m_table.removeConstraints({field}, sqlb::Constraint::ConstraintTypes::ForeignKeyConstraintType);
m_table.removeConstraints({field.name()}, sqlb::Constraint::ConstraintTypes::ForeignKeyConstraintType);
} else {
// Set the foreign key
sqlb::ForeignKeyClause* fk = new sqlb::ForeignKeyClause;
@@ -162,7 +162,7 @@ void ForeignKeyEditorDelegate::setModelData(QWidget* editor, QAbstractItemModel*
fk->setConstraint(clause);
}
m_table.setConstraint({field}, sqlb::ConstraintPtr(fk));
m_table.setConstraint({field.name()}, sqlb::ConstraintPtr(fk));
}
model->setData(index, sql);

View File

@@ -227,7 +227,7 @@ void ImportCsvDialog::updatePreview()
ui->tablePreview->setRowCount(0);
// Analyse CSV file
sqlb::FieldVector fieldList = generateFieldList(selectedFile);
sqlb::FieldPtrVector fieldList = generateFieldList(selectedFile);
// Reset preview widget
ui->tablePreview->clear();
@@ -343,7 +343,7 @@ void ImportCsvDialog::matchSimilar()
auto item = ui->filePicker->item(i);
auto header = generateFieldList(item->data(Qt::DisplayRole).toString());
if (selectedHeader.count() == header.count())
if (selectedHeader.size() == header.size())
{
bool matchingHeader = std::equal(selectedHeader.begin(), selectedHeader.end(), header.begin(),
[](const sqlb::FieldPtr& item1, const sqlb::FieldPtr& item2) -> bool {
@@ -382,9 +382,9 @@ CSVParser::ParserResult ImportCsvDialog::parseCSV(const QString &fileName, std::
return csv.parse(rowFunction, tstream, count);
}
sqlb::FieldVector ImportCsvDialog::generateFieldList(const QString& filename)
sqlb::FieldPtrVector ImportCsvDialog::generateFieldList(const QString& filename)
{
sqlb::FieldVector fieldList; // List of fields in the file
sqlb::FieldPtrVector fieldList; // List of fields in the file
// Parse the first couple of records of the CSV file and only analyse them
parseCSV(filename, [this, &fieldList](size_t rowNum, const CSVRow& data) -> bool {
@@ -487,7 +487,7 @@ bool ImportCsvDialog::importCsv(const QString& fileName, const QString& name)
}
// Analyse CSV file
sqlb::FieldVector fieldList = generateFieldList(fileName);
sqlb::FieldPtrVector fieldList = generateFieldList(fileName);
if(fieldList.size() == 0)
return true;
@@ -496,7 +496,7 @@ bool ImportCsvDialog::importCsv(const QString& fileName, const QString& name)
const sqlb::ObjectPtr obj = pdb->getObjectByName(sqlb::ObjectIdentifier("main", tableName));
if(obj && obj->type() == sqlb::Object::Types::Table)
{
if(obj.dynamicCast<sqlb::Table>()->fields().size() != fieldList.size())
if(std::dynamic_pointer_cast<sqlb::Table>(obj)->fields.size() != fieldList.size())
{
QMessageBox::warning(this, QApplication::applicationName(),
tr("There is already a table named '%1' and an import into an existing table is only possible if the number of columns match.").arg(tableName));
@@ -558,10 +558,10 @@ bool ImportCsvDialog::importCsv(const QString& fileName, const QString& name)
// Prepare the values for each table column that are to be inserted if the field in the CSV file is empty. Depending on the data type
// and the constraints of a field, we need to handle this case differently.
sqlb::TablePtr tbl = pdb->getObjectByName(sqlb::ObjectIdentifier("main", tableName)).dynamicCast<sqlb::Table>();
sqlb::TablePtr tbl = pdb->getObjectByName<sqlb::Table>(sqlb::ObjectIdentifier("main", tableName));
if(tbl)
{
for(const sqlb::FieldPtr& f : tbl->fields())
for(const sqlb::Field& f : tbl->fields)
{
// For determining the value for empty fields we follow a set of rules
@@ -571,17 +571,17 @@ bool ImportCsvDialog::importCsv(const QString& fileName, const QString& name)
// If a field has a default value, that gets priority over everything else.
// Exception: if the user wants to ignore default values we never use them.
if(!ignoreDefaults && !f->defaultValue().isNull())
if(!ignoreDefaults && !f.defaultValue().isNull())
{
nullValues << f->defaultValue().toUtf8();
nullValues << f.defaultValue().toUtf8();
} else {
// If it has no default value, check if the field is NOT NULL
if(f->notnull())
if(f.notnull())
{
// The field is NOT NULL
// If this is an integer column insert 0. Otherwise insert an empty string.
if(f->isInteger())
if(f.isInteger())
nullValues << "0";
else
nullValues << "";
@@ -602,7 +602,7 @@ bool ImportCsvDialog::importCsv(const QString& fileName, const QString& name)
// Prepare the INSERT statement. The prepared statement can then be reused for each row to insert
QString sQuery = QString("INSERT INTO %1 VALUES(").arg(sqlb::escapeIdentifier(tableName));
for(int i=1;i<=fieldList.size();i++)
for(size_t i=1;i<=fieldList.size();i++)
sQuery.append(QString("?%1,").arg(i));
sQuery.chop(1); // Remove last comma
sQuery.append(")");

View File

@@ -42,7 +42,7 @@ private:
QStringList dontAskForExistingTableAgain;
CSVParser::ParserResult parseCSV(const QString& fileName, std::function<bool(size_t, CSVRow)> rowFunction, size_t count = 0);
sqlb::FieldVector generateFieldList(const QString& filename);
sqlb::FieldPtrVector generateFieldList(const QString& filename);
bool importCsv(const QString& f, const QString& n = QString());

View File

@@ -51,11 +51,34 @@
#include <QShortcut>
#include <QTextCodec>
#include <QUrlQuery>
#include <QDataStream> // This include seems to only be necessary for the Windows build
#ifdef Q_OS_MACX //Needed only on macOS
#include <QOpenGLWidget>
#endif
// These are needed for reading and writing object files
QDataStream& operator<<(QDataStream& ds, const sqlb::ObjectIdentifier& objid)
{
ds << objid.toVariant();
return ds;
}
QDataStream& operator>>(QDataStream& ds, sqlb::ObjectIdentifier& objid)
{
// Read in the item
QVariant v;
ds >> v;
// If it is a string list, we can treat it as an object identifier. If it isn't, we assume it's just a
// single string and use interpret it as the table name in the main schema. This is done for backwards
// compatability with old project file formats.
if(v.toStringList().isEmpty())
objid = sqlb::ObjectIdentifier("main", v.toString());
else
objid = sqlb::ObjectIdentifier(v);
return ds;
}
MainWindow::MainWindow(QWidget* parent)
: QMainWindow(parent),
ui(new Ui::MainWindow),
@@ -587,7 +610,7 @@ void MainWindow::populateTable()
QVector<QString> v;
bool only_defaults = true;
const sqlb::FieldInfoList& tablefields = db.getObjectByName(tablename)->fieldInformation();
for(int i=0; i<tablefields.size(); ++i)
for(size_t i=0; i<tablefields.size(); ++i)
{
QString format = storedData.displayFormats[i+1];
if(format.size())
@@ -616,7 +639,7 @@ void MainWindow::populateTable()
if(db.getObjectByName(currentlyBrowsedTableName())->type() == sqlb::Object::Table)
{
// Table
sqlb::TablePtr table = db.getObjectByName(currentlyBrowsedTableName()).dynamicCast<sqlb::Table>();
sqlb::TablePtr table = db.getObjectByName<sqlb::Table>(currentlyBrowsedTableName());
ui->actionUnlockViewEditing->setVisible(false);
ui->actionShowRowidColumn->setVisible(!table->isWithoutRowidTable());
} else {
@@ -2819,17 +2842,17 @@ void MainWindow::copyCurrentCreateStatement()
void MainWindow::jumpToRow(const sqlb::ObjectIdentifier& table, QString column, const QByteArray& value)
{
// First check if table exists
sqlb::TablePtr obj = db.getObjectByName(table).dynamicCast<sqlb::Table>();
sqlb::TablePtr obj = db.getObjectByName<sqlb::Table>(table);
if(!obj)
return;
// If no column name is set, assume the primary key is meant
if(!column.size())
column = obj->fields().at(obj->findPk())->name();
column = obj->findPk()->name();
// If column doesn't exist don't do anything
int column_index = obj->findField(column);
if(column_index == -1)
auto column_index = sqlb::findField(obj, column);
if(column_index == obj->fields.end())
return;
// Jump to table
@@ -2837,7 +2860,7 @@ void MainWindow::jumpToRow(const sqlb::ObjectIdentifier& table, QString column,
populateTable();
// Set filter
ui->dataTable->filterHeader()->setFilter(column_index+1, "=" + value);
ui->dataTable->filterHeader()->setFilter(column_index-obj->fields.begin()+1, "=" + value);
}
void MainWindow::showDataColumnPopupMenu(const QPoint& pos)
@@ -2909,9 +2932,9 @@ void MainWindow::editDataColumnDisplayFormat()
int field_number = sender()->property("clicked_column").toInt();
QString field_name;
if (db.getObjectByName(current_table)->type() == sqlb::Object::Table)
field_name = db.getObjectByName(current_table).dynamicCast<sqlb::Table>()->fields().at(field_number-1)->name();
field_name = db.getObjectByName<sqlb::Table>(current_table)->fields.at(field_number-1).name();
else
field_name = db.getObjectByName(current_table).dynamicCast<sqlb::View>()->fieldNames().at(field_number-1);
field_name = db.getObjectByName<sqlb::View>(current_table)->fieldNames().at(field_number-1);
// Get the current display format of the field
QString current_displayformat = browseTableSettings[current_table].displayFormats[field_number];
@@ -3044,7 +3067,7 @@ void MainWindow::unlockViewEditing(bool unlock, QString pk)
if(unlock != settings.unlockViewPk.isEmpty() && settings.unlockViewPk == pk)
return;
sqlb::ViewPtr obj = db.getObjectByName(currentTable).dynamicCast<sqlb::View>();
sqlb::ViewPtr obj = db.getObjectByName<sqlb::View>(currentTable);
// If the view gets unlocked for editing and we don't have a 'primary key' for this view yet, then ask for one
if(unlock && pk.isEmpty())

View File

@@ -73,11 +73,11 @@ void PreferencesDialog::loadSettings()
ui->spinPrefetchSize->setValue(Settings::getValue("db", "prefetchsize").toInt());
ui->editDatabaseDefaultSqlText->setText(Settings::getValue("db", "defaultsqltext").toString());
ui->defaultFieldTypeComboBox->addItems(sqlb::Field::Datatypes);
ui->defaultFieldTypeComboBox->addItems(DBBrowserDB::Datatypes);
int defaultFieldTypeIndex = Settings::getValue("db", "defaultfieldtype").toInt();
if (defaultFieldTypeIndex < sqlb::Field::Datatypes.count())
if (defaultFieldTypeIndex < DBBrowserDB::Datatypes.count())
{
ui->defaultFieldTypeComboBox->setCurrentIndex(defaultFieldTypeIndex);
}

View File

@@ -17,6 +17,9 @@
#include <QDebug>
#include <functional>
#include <atomic>
#include <algorithm>
QStringList DBBrowserDB::Datatypes = QStringList() << "INTEGER" << "TEXT" << "BLOB" << "REAL" << "NUMERIC";
// Helper template to allow turning member functions into a C-style function pointer
// See https://stackoverflow.com/questions/19808054/convert-c-function-pointer-to-c-function-pointer/19809787
@@ -711,7 +714,7 @@ bool DBBrowserDB::dump(const QString& filePath,
for(auto it : tables)
{
// get columns
QStringList cols(it.dynamicCast<sqlb::Table>()->fieldNames());
QStringList cols(std::dynamic_pointer_cast<sqlb::Table>(it)->fieldNames());
QString sQuery = QString("SELECT * FROM %1;").arg(sqlb::escapeIdentifier(it->name()));
QByteArray utf8Query = sQuery.toUtf8();
@@ -1027,7 +1030,7 @@ bool DBBrowserDB::getRow(const sqlb::ObjectIdentifier& table, const QString& row
QString sQuery = QString("SELECT * FROM %1 WHERE %2='%3';")
.arg(table.toString())
.arg(sqlb::escapeIdentifier(getObjectByName(table).dynamicCast<sqlb::Table>()->rowidColumn()))
.arg(sqlb::escapeIdentifier(getObjectByName<sqlb::Table>(table)->rowidColumn()))
.arg(rowid);
QByteArray utf8Query = sQuery.toUtf8();
@@ -1059,9 +1062,9 @@ bool DBBrowserDB::getRow(const sqlb::ObjectIdentifier& table, const QString& row
return ret;
}
QString DBBrowserDB::max(const sqlb::ObjectIdentifier& tableName, sqlb::FieldPtr field) const
QString DBBrowserDB::max(const sqlb::ObjectIdentifier& tableName, const sqlb::Field& field) const
{
QString sQuery = QString("SELECT MAX(CAST(%2 AS INTEGER)) FROM %1;").arg(tableName.toString()).arg(sqlb::escapeIdentifier(field->name()));
QString sQuery = QString("SELECT MAX(CAST(%2 AS INTEGER)) FROM %1;").arg(tableName.toString()).arg(sqlb::escapeIdentifier(field.name()));
QByteArray utf8Query = sQuery.toUtf8();
sqlite3_stmt *stmt;
QString ret = "0";
@@ -1086,18 +1089,18 @@ QString DBBrowserDB::emptyInsertStmt(const QString& schemaName, const sqlb::Tabl
QStringList vals;
QStringList fields;
for(const sqlb::FieldPtr& f : t.fields())
for(const sqlb::Field& f : t.fields)
{
sqlb::ConstraintPtr pk = t.constraint({f}, sqlb::Constraint::PrimaryKeyConstraintType);
sqlb::ConstraintPtr pk = t.constraint({f.name()}, sqlb::Constraint::PrimaryKeyConstraintType);
if(pk)
{
fields << f->name();
fields << f.name();
if(!pk_value.isNull())
{
vals << pk_value;
} else {
if(f->notnull())
if(f.notnull())
{
QString maxval = this->max(sqlb::ObjectIdentifier(schemaName, t.name()), f);
vals << QString::number(maxval.toLongLong() + 1);
@@ -1105,19 +1108,19 @@ QString DBBrowserDB::emptyInsertStmt(const QString& schemaName, const sqlb::Tabl
vals << "NULL";
}
}
} else if(f->notnull() && f->defaultValue().length() == 0) {
fields << f->name();
} else if(f.notnull() && f.defaultValue().length() == 0) {
fields << f.name();
if(f->isInteger())
if(f.isInteger())
vals << "0";
else
vals << "''";
} else {
// don't insert into fields with a default value
// or we will never see it.
if(f->defaultValue().length() == 0)
if(f.defaultValue().length() == 0)
{
fields << f->name();
fields << f.name();
vals << "NULL";
}
}
@@ -1145,7 +1148,7 @@ QString DBBrowserDB::addRecord(const sqlb::ObjectIdentifier& tablename)
if(!_db)
return QString();
sqlb::TablePtr table = getObjectByName(tablename).dynamicCast<sqlb::Table>();
sqlb::TablePtr table = getObjectByName<sqlb::Table>(tablename);
if(!table)
return QString();
@@ -1155,7 +1158,7 @@ QString DBBrowserDB::addRecord(const sqlb::ObjectIdentifier& tablename)
QString pk_value;
if(table->isWithoutRowidTable())
{
pk_value = QString::number(max(tablename, table->fields().at(table->findField(table->rowidColumn()))).toLongLong() + 1);
pk_value = QString::number(max(tablename, *sqlb::findField(table, table->rowidColumn())).toLongLong() + 1);
sInsertstmt = emptyInsertStmt(tablename.schema(), *table, pk_value);
} else {
sInsertstmt = emptyInsertStmt(tablename.schema(), *table);
@@ -1268,7 +1271,7 @@ QString DBBrowserDB::primaryKeyForEditing(const sqlb::ObjectIdentifier& table, c
if(pseudo_pk.isEmpty())
{
sqlb::TablePtr tbl = getObjectByName(table).dynamicCast<sqlb::Table>();
sqlb::TablePtr tbl = getObjectByName<sqlb::Table>(table);
if(tbl)
return tbl->rowidColumn();
} else {
@@ -1278,20 +1281,20 @@ QString DBBrowserDB::primaryKeyForEditing(const sqlb::ObjectIdentifier& table, c
return QString();
}
bool DBBrowserDB::createTable(const sqlb::ObjectIdentifier& name, const sqlb::FieldVector& structure)
bool DBBrowserDB::createTable(const sqlb::ObjectIdentifier& name, const sqlb::FieldPtrVector& structure)
{
// Build SQL statement
sqlb::Table table(name.name());
for(int i=0;i<structure.size();i++)
table.addField(structure.at(i));
for(size_t i=0;i<structure.size();i++)
table.fields.push_back(*structure.at(i));
// Execute it and update the schema
return executeSQL(table.sql(name.schema()));
}
bool DBBrowserDB::addColumn(const sqlb::ObjectIdentifier& tablename, const sqlb::FieldPtr& field)
bool DBBrowserDB::addColumn(const sqlb::ObjectIdentifier& tablename, const sqlb::Field& field)
{
QString sql = QString("ALTER TABLE %1 ADD COLUMN %2").arg(tablename.toString()).arg(field->toString());
QString sql = QString("ALTER TABLE %1 ADD COLUMN %2").arg(tablename.toString()).arg(field.toString());
// Execute it and update the schema
return executeSQL(sql);
@@ -1336,10 +1339,10 @@ bool DBBrowserDB::alterTable(const sqlb::ObjectIdentifier& tablename, const sqlb
}
// Create table schema
const sqlb::TablePtr oldSchema = getObjectByName(tablename).dynamicCast<sqlb::Table>();
const sqlb::TablePtr oldSchema = getObjectByName<sqlb::Table>(tablename);
// Check if field actually exists
if(!name.isNull() && oldSchema->findField(name) == -1)
if(!name.isNull() && sqlb::findField(oldSchema, name) == oldSchema->fields.end())
{
lastErrorMessage = tr("renameColumn: cannot find column %1.").arg(name);
return false;
@@ -1362,36 +1365,33 @@ bool DBBrowserDB::alterTable(const sqlb::ObjectIdentifier& tablename, const sqlb
newSchema.setConstraints(table.allConstraints());
newSchema.setRowidColumn(table.rowidColumn());
QString select_cols;
if(to.isNull())
if(!to)
{
// We want drop the column - so just remove the field. If the name is set to null, skip this step. This effectively leaves all fields as they are,
// thus only changing the table constraints.
if(!name.isNull())
newSchema.removeField(name);
sqlb::removeField(newSchema, name);
for(int i=0;i<newSchema.fields().count();++i)
select_cols.append(sqlb::escapeIdentifier(newSchema.fields().at(i)->name()) + ',');
for(size_t i=0;i<newSchema.fields.size();++i)
select_cols.append(sqlb::escapeIdentifier(newSchema.fields.at(i).name()) + ',');
select_cols.chop(1); // remove last comma
} else {
// We want to modify it
int index = newSchema.findField(name);
auto index = sqlb::findField(newSchema, name);
// Move field
if(move)
{
sqlb::FieldPtr temp = newSchema.fields().at(index);
newSchema.setField(index, newSchema.fields().at(index + move));
newSchema.setField(index + move, temp);
}
std::iter_swap(index, index + move);
// Get names of fields to select from old table now - after the field has been moved and before it might be renamed
for(int i=0;i<newSchema.fields().count();++i)
select_cols.append(sqlb::escapeIdentifier(newSchema.fields().at(i)->name()) + ',');
for(size_t i=0;i<newSchema.fields.size();++i)
select_cols.append(sqlb::escapeIdentifier(newSchema.fields.at(i).name()) + ',');
select_cols.chop(1); // remove last comma
// Modify field
newSchema.setField(index + move, to);
newSchema.renameKeyInAllConstraints((index + move)->name(), to->name());
*(index + move) = *to;
}
// Create the new table
@@ -1428,26 +1428,26 @@ bool DBBrowserDB::alterTable(const sqlb::ObjectIdentifier& tablename, const sqlb
// error later on when we try to recreate it.
if(it->type() == sqlb::Object::Types::Index)
{
sqlb::IndexPtr idx = it.dynamicCast<sqlb::Index>();
sqlb::IndexPtr idx = std::dynamic_pointer_cast<sqlb::Index>(it);
// Are we updating a field name or are we removing a field entirely?
if(to)
{
// We're updating a field name. So search for it in the index and replace it whereever it is found
for(int i=0;i<idx->columns().size();i++)
for(size_t i=0;i<idx->fields.size();i++)
{
if(idx->column(i)->name() == name)
idx->column(i)->setName(to->name());
if(idx->fields[i].name() == name)
idx->fields[i].setName(to->name());
}
} else {
// We're removing a field. So remove it from any indices, too.
while(idx->removeColumn(name))
while(sqlb::removeField(idx, name))
;
}
// Only try to add the index later if it has any columns remaining. Also use the new schema name here, too, to basically move
// any index that references the table to the same new schema as the table.
if(idx->columns().size())
if(idx->fields.size())
otherObjectsSql << idx->sql(newSchemaName);
} else {
// If it's a view or a trigger we don't have any chance to corrections yet. Just store the statement as is and
@@ -1564,16 +1564,6 @@ objectMap DBBrowserDB::getBrowsableObjects(const QString& schema) const
return res;
}
const sqlb::ObjectPtr DBBrowserDB::getObjectByName(const sqlb::ObjectIdentifier& name) const
{
for(auto it : schemata[name.schema()])
{
if(it->name() == name.name())
return it;
}
return sqlb::ObjectPtr(nullptr);
}
void DBBrowserDB::logSQL(QString statement, int msgtype)
{
// Remove any leading and trailing spaces, tabs, or line breaks first
@@ -1645,44 +1635,42 @@ void DBBrowserDB::updateSchema()
QString val_tblname = QString::fromUtf8((const char*)sqlite3_column_text(vm, 3));
val_sql.replace("\r", "");
sqlb::Object::Types type;
if(val_type == "table")
type = sqlb::Object::Types::Table;
else if(val_type == "index")
type = sqlb::Object::Types::Index;
else if(val_type == "trigger")
type = sqlb::Object::Types::Trigger;
else if(val_type == "view")
type = sqlb::Object::Types::View;
else
continue;
if(!val_sql.isEmpty())
{
sqlb::ObjectPtr object = sqlb::Object::parseSQL(type, val_sql);
sqlb::ObjectPtr object;
if(val_type == "table")
object = sqlb::Table::parseSQL(val_sql);
else if(val_type == "index")
object = sqlb::Index::parseSQL(val_sql);
else if(val_type == "trigger")
object = sqlb::Trigger::parseSQL(val_sql);
else if(val_type == "view")
object = sqlb::View::parseSQL(val_sql);
else
continue;
// If parsing wasn't successful set the object name manually, so that at least the name is going to be correct
if(!object->fullyParsed())
object->setName(val_name);
// For virtual tables and views query the column list using the SQLite pragma because for both we can't yet rely on our grammar parser
if((type == sqlb::Object::Types::Table && object.dynamicCast<sqlb::Table>()->isVirtual()) || type == sqlb::Object::Types::View)
if((object->type() == sqlb::Object::Types::Table && std::dynamic_pointer_cast<sqlb::Table>(object)->isVirtual()) || object->type() == sqlb::Object::Types::View)
{
auto columns = queryColumnInformation(schema_name, val_name);
if(type == sqlb::Object::Types::Table)
if(object->type() == sqlb::Object::Types::Table)
{
sqlb::TablePtr tab = object.dynamicCast<sqlb::Table>();
sqlb::TablePtr tab = std::dynamic_pointer_cast<sqlb::Table>(object);
for(const auto& column : columns)
tab->addField(sqlb::FieldPtr(new sqlb::Field(column.first, column.second)));
tab->fields.emplace_back(column.first, column.second);
} else {
sqlb::ViewPtr view = object.dynamicCast<sqlb::View>();
sqlb::ViewPtr view = std::dynamic_pointer_cast<sqlb::View>(object);
for(const auto& column : columns)
view->addField(sqlb::FieldPtr(new sqlb::Field(column.first, column.second)));
view->fields.emplace_back(column.first, column.second);
}
} else if(type == sqlb::Object::Types::Trigger) {
} else if(object->type() == sqlb::Object::Types::Trigger) {
// For triggers set the name of the table the trigger operates on here because we don't have a parser for trigger statements yet.
sqlb::TriggerPtr trg = object.dynamicCast<sqlb::Trigger>();
sqlb::TriggerPtr trg = std::dynamic_pointer_cast<sqlb::Trigger>(object);
trg->setTable(val_tblname);
}

View File

@@ -118,7 +118,7 @@ private:
* @param field Field to get the max value
* @return the max value of the field or 0 on error
*/
QString max(const sqlb::ObjectIdentifier& tableName, sqlb::FieldPtr field) const;
QString max(const sqlb::ObjectIdentifier& tableName, const sqlb::Field& field) const;
public:
void updateSchema();
@@ -137,9 +137,9 @@ public:
bool deleteRecords(const sqlb::ObjectIdentifier& table, const QStringList& rowids, const QString& pseudo_pk = QString());
bool updateRecord(const sqlb::ObjectIdentifier& table, const QString& column, const QString& rowid, const QByteArray& value, bool itsBlob, const QString& pseudo_pk = QString());
bool createTable(const sqlb::ObjectIdentifier& name, const sqlb::FieldVector& structure);
bool createTable(const sqlb::ObjectIdentifier& name, const sqlb::FieldPtrVector& structure);
bool renameTable(const QString& schema, const QString& from_table, const QString& to_table);
bool addColumn(const sqlb::ObjectIdentifier& tablename, const sqlb::FieldPtr& field);
bool addColumn(const sqlb::ObjectIdentifier& tablename, const sqlb::Field& field);
/**
* @brief alterTable Can be used to rename, modify or drop an existing column of a given table
@@ -155,7 +155,18 @@ public:
bool alterTable(const sqlb::ObjectIdentifier& tablename, const sqlb::Table& table, const QString& name, sqlb::FieldPtr to, int move = 0, QString newSchemaName = QString());
objectMap getBrowsableObjects(const QString& schema) const;
const sqlb::ObjectPtr getObjectByName(const sqlb::ObjectIdentifier& name) const;
template<typename T = sqlb::Object>
const std::shared_ptr<T> getObjectByName(const sqlb::ObjectIdentifier& name) const
{
for(auto& it : schemata[name.schema()])
{
if(it->name() == name.name())
return std::dynamic_pointer_cast<T>(it);
}
return std::shared_ptr<T>();
}
bool isOpen() const;
bool encrypted() const { return isEncrypted; }
bool readOnly() const { return isReadOnly; }
@@ -173,6 +184,8 @@ public:
bool loadExtension(const QString& filename);
void loadExtensionsFromSettings();
static QStringList Datatypes;
private:
QVector<QPair<QString, QString>> queryColumnInformation(const QString& schema_name, const QString& object_name);

View File

@@ -132,8 +132,8 @@ void SqliteTableModel::setTable(const sqlb::ObjectIdentifier& table, int sortCol
bool allOk = false;
if(m_db.getObjectByName(table)->type() == sqlb::Object::Types::Table)
{
sqlb::TablePtr t = m_db.getObjectByName(table).dynamicCast<sqlb::Table>();
if(t && t->fields().size()) // parsing was OK
sqlb::TablePtr t = m_db.getObjectByName<sqlb::Table>(table);
if(t && t->fields.size()) // parsing was OK
{
m_sRowidColumn = t->rowidColumn();
m_headers.push_back(m_sRowidColumn);
@@ -145,9 +145,9 @@ void SqliteTableModel::setTable(const sqlb::ObjectIdentifier& table, int sortCol
<< "REAL"
<< "TEXT"
<< "BLOB";
for(const sqlb::FieldPtr& fld : t->fields())
for(const sqlb::Field& fld : t->fields)
{
QString name(fld->type().toUpper());
QString name(fld.type().toUpper());
int colType = dataTypes.indexOf(name);
colType = (colType == -1) ? SQLITE_TEXT : colType + 1;
m_vDataTypes.push_back(colType);
@@ -326,15 +326,15 @@ sqlb::ForeignKeyClause SqliteTableModel::getForeignKeyClause(int column) const
return empty_foreign_key_clause;
// Convert object to a table and check if the column number is in the valid range
sqlb::TablePtr tbl = obj.dynamicCast<sqlb::Table>();
if(tbl && tbl->name().size() && (column >= 0 && column < tbl->fields().count()))
sqlb::TablePtr tbl = std::dynamic_pointer_cast<sqlb::Table>(obj);
if(tbl && tbl->name().size() && (column >= 0 && column < static_cast<int>(tbl->fields.size())))
{
// Note that the rowid column has number -1 here, it can safely be excluded since there will never be a
// foreign key on that column.
sqlb::ConstraintPtr ptr = tbl->constraint({tbl->fields().at(column)}, sqlb::Constraint::ForeignKeyConstraintType);
sqlb::ConstraintPtr ptr = tbl->constraint({tbl->fields.at(column).name()}, sqlb::Constraint::ForeignKeyConstraintType);
if(ptr)
return *(ptr.dynamicCast<sqlb::ForeignKeyClause>());
return *(std::dynamic_pointer_cast<sqlb::ForeignKeyClause>(ptr));
}
return empty_foreign_key_clause;
@@ -371,11 +371,11 @@ bool SqliteTableModel::setTypedData(const QModelIndex& index, bool isBlob, const
// used in a primary key. Otherwise SQLite will always output an 'datatype mismatch' error.
if(newValue == "" && !newValue.isNull())
{
sqlb::TablePtr table = m_db.getObjectByName(m_sTable).dynamicCast<sqlb::Table>();
sqlb::TablePtr table = m_db.getObjectByName<sqlb::Table>(m_sTable);
if(table)
{
sqlb::FieldPtr field = table->field(table->findField(m_headers.at(index.column())));
if(table->primaryKey().contains(field) && field->isInteger())
auto field = sqlb::findField(table, m_headers.at(index.column()));
if(contains(table->primaryKey(), field->name()) && field->isInteger())
newValue = "0";
}
}
@@ -542,11 +542,11 @@ QModelIndex SqliteTableModel::dittoRecord(int old_row)
int firstEditedColumn = 0;
int new_row = rowCount() - 1;
sqlb::TablePtr t = m_db.getObjectByName(m_sTable).dynamicCast<sqlb::Table>();
sqlb::TablePtr t = m_db.getObjectByName<sqlb::Table>(m_sTable);
sqlb::FieldVector pk = t->primaryKey();
for (int col = 0; col < t->fields().size(); ++col) {
if(!pk.contains(t->fields().at(col))) {
QStringList pk = t->primaryKey();
for (size_t col = 0; col < t->fields.size(); ++col) {
if(!contains(pk, t->fields.at(col).name())) {
if (!firstEditedColumn)
firstEditedColumn = col + 1;

View File

@@ -3,14 +3,11 @@
#include "grammar/Sqlite3Parser.hpp"
#include <sstream>
#include <QDebug>
#include <QDataStream> // This include seems to only be necessary for the Windows build
#include <iostream>
#include <clocale> // This include seems to only be necessary for the Windows build
namespace sqlb {
QStringList Field::Datatypes = QStringList() << "INTEGER" << "TEXT" << "BLOB" << "REAL" << "NUMERIC";
static escapeQuoting customQuoting = DoubleQuotes;
void setIdentifierQuoting(escapeQuoting toQuoting)
@@ -37,34 +34,12 @@ QString escapeIdentifier(QString id)
}
}
QStringList fieldVectorToFieldNames(const FieldVector& vector)
QStringList escapeIdentifier(const QStringList& ids)
{
QStringList result;
for(const FieldPtr& field : vector)
result.append(escapeIdentifier(field->name()));
return result;
}
QDataStream& operator<<(QDataStream& ds, const ObjectIdentifier& objid)
{
ds << objid.toVariant();
return ds;
}
QDataStream& operator>>(QDataStream& ds, ObjectIdentifier& objid)
{
// Read in the item
QVariant v;
ds >> v;
// If it is a string list, we can treat it as an object identifier. If it isn't, we assume it's just a
// single string and use interpret it as the table name in the main schema. This is done for backwards
// compatability with old project file formats.
if(v.toStringList().isEmpty())
objid = sqlb::ObjectIdentifier("main", v.toString());
else
objid = sqlb::ObjectIdentifier(v);
return ds;
QStringList ret;
for(const QString& id : ids)
ret.push_back(escapeIdentifier(id));
return ret;
}
/**
@@ -140,33 +115,6 @@ private:
antlr::RefAST m_root;
};
ObjectPtr Object::parseSQL(Object::Types type, const QString& sSQL)
{
// Parse SQL statement according to type
ObjectPtr result;
switch(type)
{
case Object::Types::Table:
result = Table::parseSQL(sSQL);
break;
case Object::Types::Index:
result = Index::parseSQL(sSQL);
break;
case Object::Types::View:
result = View::parseSQL(sSQL);
break;
case Object::Types::Trigger:
result = Trigger::parseSQL(sSQL);
break;
default:
return ObjectPtr(nullptr);
}
// Strore the original SQL statement and return the result
result->setOriginalSql(sSQL);
return result;
}
QString Object::typeToString(Types type)
{
switch(type)
@@ -214,32 +162,32 @@ void ForeignKeyClause::setFromString(const QString& fk)
m_override = fk;
}
QString ForeignKeyClause::toSql(const FieldVector& applyOn) const
QString ForeignKeyClause::toSql(const QStringList& applyOn) const
{
QString result;
if(!m_name.isNull())
result += QString("CONSTRAINT %1 ").arg(escapeIdentifier(m_name));
result += QString("FOREIGN KEY(%1) REFERENCES %2").arg(fieldVectorToFieldNames(applyOn).join(",")).arg(this->toString());
result += QString("FOREIGN KEY(%1) REFERENCES %2").arg(escapeIdentifier(applyOn).join(",")).arg(this->toString());
return result;
}
QString UniqueConstraint::toSql(const FieldVector& applyOn) const
QString UniqueConstraint::toSql(const QStringList& applyOn) const
{
QString result;
if(!m_name.isNull())
result += QString("CONSTRAINT %1 ").arg(escapeIdentifier(m_name));
result += QString("UNIQUE(%1)").arg(fieldVectorToFieldNames(applyOn).join(","));
result += QString("UNIQUE(%1)").arg(escapeIdentifier(applyOn).join(","));
return result;
}
QString PrimaryKeyConstraint::toSql(const FieldVector& applyOn) const
QString PrimaryKeyConstraint::toSql(const QStringList& applyOn) const
{
QString result;
if(!m_name.isNull())
result += QString("CONSTRAINT %1 ").arg(escapeIdentifier(m_name));
result += QString("PRIMARY KEY(%1)").arg(fieldVectorToFieldNames(applyOn).join(","));
result += QString("PRIMARY KEY(%1)").arg(escapeIdentifier(applyOn).join(","));
if(!m_conflictAction.isEmpty())
result += " ON CONFLICT " + m_conflictAction;
@@ -247,7 +195,7 @@ QString PrimaryKeyConstraint::toSql(const FieldVector& applyOn) const
return result;
}
QString CheckConstraint::toSql(const FieldVector&) const
QString CheckConstraint::toSql(const QStringList&) const
{
QString result;
if(!m_name.isNull())
@@ -346,16 +294,8 @@ QString Field::affinity() const
return "NUMERIC";
}
void Table::clear()
{
m_rowidColumn = "_rowid_";
m_fields.clear();
m_constraints.clear();
m_virtual = QString();
}
Table::~Table()
{
clear();
}
Table& Table::operator=(const Table& rhs)
@@ -368,122 +308,35 @@ Table& Table::operator=(const Table& rhs)
m_virtual = rhs.m_virtual;
// Clear the fields and the constraints first in order to avoid duplicates and/or old data in the next step
m_fields.clear();
fields.clear();
m_constraints.clear();
// Make copies of the fields and the constraints. This is necessary in order to avoid any unwanted changes to the application's main database
// schema representation just modifying a reference to the fields or constraints and thinking it operates on a copy.
for(const FieldPtr& f : rhs.m_fields)
addField(FieldPtr(new Field(*f)));
for(auto it=rhs.m_constraints.constBegin();it!=rhs.m_constraints.constEnd();++it) // TODO This is so ugly, it should be replaced really by anything else
{
FieldVector key;
ConstraintPtr constraint;
for(const FieldPtr& f : it.key())
key.push_back(m_fields.at(findField(f->name())));
if(it.value()->type() == Constraint::ConstraintTypes::PrimaryKeyConstraintType)
constraint = ConstraintPtr(new PrimaryKeyConstraint(*(it.value().dynamicCast<PrimaryKeyConstraint>())));
else if(it.value()->type() == Constraint::ConstraintTypes::UniqueConstraintType)
constraint = ConstraintPtr(new UniqueConstraint(*(it.value().dynamicCast<UniqueConstraint>())));
else if(it.value()->type() == Constraint::ConstraintTypes::ForeignKeyConstraintType)
constraint = ConstraintPtr(new ForeignKeyClause(*(it.value().dynamicCast<ForeignKeyClause>())));
else if(it.value()->type() == Constraint::ConstraintTypes::CheckConstraintType)
constraint = ConstraintPtr(new CheckConstraint(*(it.value().dynamicCast<CheckConstraint>())));
else
qWarning() << "Unknown constraint type";
addConstraint(key, constraint);
}
// schema representation just by modifying a reference to the fields or constraints and thinking it operates on a copy.
for(const Field& f : rhs.fields)
fields.push_back(f);
m_constraints = rhs.m_constraints;
return *this;
}
void Table::addField(const FieldPtr& f)
{
m_fields.append(FieldPtr(f));
}
bool Table::removeField(const QString& sFieldName)
{
int index = findField(sFieldName);
if( index != -1)
{
m_fields.remove(index);
return true;
}
return false;
}
void Table::setFields(const FieldVector &fields)
{
clear();
m_fields = fields;
}
void Table::setField(int index, FieldPtr f)
{
FieldPtr oldField = m_fields[index];
m_fields[index] = f;
// Update all constraints. If an existing field is updated but was used in a constraint, the pointers in the constraint key needs to be updated
// to the new field, too.
if(oldField)
{
ConstraintMap::iterator it = m_constraints.begin();
while(it != m_constraints.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 constraint using the updated field vector and the old
// constraint information, and delete the old one afterwards
m_constraints.insert(fields, it.value());
it = m_constraints.erase(it);
} else {
++it;
}
}
}
}
int Table::findField(const QString &sname) const
{
for(int i = 0; i < m_fields.count(); ++i)
{
if(m_fields.at(i)->name().compare(sname, Qt::CaseInsensitive) == 0)
return i;
}
return -1;
}
int Table::findPk() const
Table::field_iterator Table::findPk()
{
// TODO This is a stupid function (and always was) which should be fixed/improved
FieldVector pk = primaryKey();
QStringList pk = primaryKey();
if(pk.empty())
return -1;
return fields.end();
else
return findField(pk.at(0)->name());
return findField(this, pk.at(0));
}
QStringList Table::fieldList() const
{
QStringList sl;
for(const FieldPtr& f : m_fields)
sl << f->toString();
for(const Field& f : fields)
sl << f.toString();
return sl;
}
@@ -492,8 +345,8 @@ QStringList Table::fieldNames() const
{
QStringList sl;
for(const FieldPtr& f : m_fields)
sl << f->name();
for(const Field& f : fields)
sl << f.name();
return sl;
}
@@ -501,21 +354,21 @@ QStringList Table::fieldNames() const
FieldInfoList Table::fieldInformation() const
{
FieldInfoList result;
for(const FieldPtr& f : m_fields)
result.append({f->name(), f->type(), f->toString(" ", " ")});
for(const Field& f : fields)
result.emplace_back(f.name(), f.type(), f.toString(" ", " "));
return result;
}
bool Table::hasAutoIncrement() const
{
for(const FieldPtr& f : m_fields) {
if(f->autoIncrement())
for(const Field& f : fields) {
if(f.autoIncrement())
return true;
}
return false;
}
ObjectPtr Table::parseSQL(const QString &sSQL)
TablePtr Table::parseSQL(const QString& sSQL)
{
SetLocaleToC locale;
@@ -534,15 +387,17 @@ ObjectPtr Table::parseSQL(const QString &sSQL)
parser.createtable();
CreateTableWalker ctw(parser.getAST());
return ctw.table();
auto t = ctw.table();
t->setOriginalSql(sSQL);
return t;
}
catch(antlr::ANTLRException& ex)
{
qCritical() << "Sqlite parse error: " << QString::fromStdString(ex.toString()) << "(" << sSQL << ")";
std::cerr << "Sqlite parse error: " << ex.toString() << "(" << sSQL.toStdString() << ")" << std::endl;
}
catch(...)
{
qCritical() << "Sqlite parse error: " << sSQL; //TODO
std::cerr << "Sqlite parse error: " << sSQL.toStdString() << std::endl; //TODO
}
return TablePtr(new Table(""));
@@ -562,18 +417,18 @@ QString Table::sql(const QString& schema, bool ifNotExists) const
sql += fieldList().join(",\n");
// Constraints
ConstraintMap::const_iterator it = m_constraints.constBegin();
ConstraintMap::const_iterator it = m_constraints.cbegin();
bool autoincrement = hasAutoIncrement();
while(it != m_constraints.constEnd())
while(it != m_constraints.cend())
{
// Ignore auto increment primary key constraint
if((!autoincrement || it.value()->type() != Constraint::PrimaryKeyConstraintType))
if((!autoincrement || it->second->type() != Constraint::PrimaryKeyConstraintType))
{
// Ignore all constraints without any fields, except for check constraints which don't rely on a field vector
if(!(it.key().isEmpty() && it.value()->type() != Constraint::CheckConstraintType))
if(!(it->first.empty() && it->second->type() != Constraint::CheckConstraintType))
{
sql += QString(",\n\t");
sql += it.value()->toSql(it.key());
sql += it->second->toSql(it->first);
}
}
++it;
@@ -588,12 +443,12 @@ QString Table::sql(const QString& schema, bool ifNotExists) const
return sql + ";";
}
void Table::addConstraint(FieldVector fields, ConstraintPtr constraint)
void Table::addConstraint(QStringList fields, ConstraintPtr constraint)
{
m_constraints.insert(fields, constraint);
m_constraints.insert({fields, constraint});
}
void Table::setConstraint(FieldVector fields, ConstraintPtr constraint)
void Table::setConstraint(QStringList fields, ConstraintPtr constraint)
{
// Delete any old constraints of this type for these fields
removeConstraints(fields, constraint->type());
@@ -602,37 +457,45 @@ void Table::setConstraint(FieldVector fields, ConstraintPtr constraint)
addConstraint(fields, constraint);
}
void Table::removeConstraints(FieldVector fields, Constraint::ConstraintTypes type)
void Table::removeConstraints(QStringList fields, Constraint::ConstraintTypes type)
{
QList<ConstraintPtr> list = constraints(fields, type);
for (ConstraintPtr c : list) {
m_constraints.remove(fields, c);
for(auto it = m_constraints.begin();it!=m_constraints.end();)
{
if(it->first == fields && it->second->type() == type)
m_constraints.erase(it++);
else
++it;
}
}
ConstraintPtr Table::constraint(FieldVector fields, Constraint::ConstraintTypes type) const
ConstraintPtr Table::constraint(QStringList fields, Constraint::ConstraintTypes type) const
{
QList<ConstraintPtr> list = constraints(fields, type);
auto list = constraints(fields, type);
if(list.size())
return list.at(0);
else
return ConstraintPtr(nullptr);
}
QList<ConstraintPtr> Table::constraints(FieldVector fields, Constraint::ConstraintTypes type) const
std::vector<ConstraintPtr> Table::constraints(QStringList fields, Constraint::ConstraintTypes type) const
{
QList<ConstraintPtr> clist;
if(fields.isEmpty())
clist = m_constraints.values();
else
clist = m_constraints.values(fields);
ConstraintMap::const_iterator begin, end;
if(fields.empty())
{
begin = m_constraints.begin();
end = m_constraints.end();
} else {
std::tie(begin, end) = m_constraints.equal_range(fields);
}
std::vector<ConstraintPtr> clist;
std::transform(begin, end, std::back_inserter(clist), [](std::pair<QStringList, ConstraintPtr> elem){return elem.second;});
if(type == Constraint::NoType)
{
return clist;
} else {
QList<ConstraintPtr> clist_typed;
std::vector<ConstraintPtr> clist_typed;
for(const ConstraintPtr& ptr : clist)
{
if(ptr->type() == type)
@@ -647,25 +510,62 @@ void Table::setConstraints(const ConstraintMap& constraints)
m_constraints = constraints;
}
FieldVector& Table::primaryKeyRef()
QStringList& Table::primaryKeyRef()
{
return const_cast<FieldVector&>(static_cast<const Table*>(this)->primaryKey());
return const_cast<QStringList&>(static_cast<const Table*>(this)->primaryKey());
}
const FieldVector& Table::primaryKey() const
const QStringList& Table::primaryKey() const
{
auto it = m_constraints.constBegin();
while(it != m_constraints.constEnd())
auto it = m_constraints.cbegin();
while(it != m_constraints.cend())
{
if(it.value()->type() == Constraint::PrimaryKeyConstraintType)
return it.key();
if(it->second->type() == Constraint::PrimaryKeyConstraintType)
return it->first;
++it;
}
static FieldVector emptyFieldVector;
static QStringList emptyFieldVector;
return emptyFieldVector;
}
void Table::removeKeyFromAllConstraints(const QString& key)
{
// First remove all constraints with exactly that one key
m_constraints.erase({key});
// Then delete all occurrences of the key in compound columns
for(auto it=m_constraints.begin();it!=m_constraints.end();)
{
if(it->first.contains(key))
{
QStringList k = it->first;
k.removeAll(key);
m_constraints.insert({k, it->second});
it = m_constraints.erase(it);
} else {
++it;
}
}
}
void Table::renameKeyInAllConstraints(const QString& key, const QString& to)
{
// Find all occurrences of the key and change it to the new one
for(auto it=m_constraints.begin();it!=m_constraints.end();)
{
if(it->first.contains(key))
{
QStringList k = it->first;
k.replaceInStrings(QRegExp("^" + key + "$"), to);
m_constraints.insert({k, it->second});
it = m_constraints.erase(it);
} else {
++it;
}
}
}
namespace
{
QString identifier(antlr::RefAST ident)
@@ -784,7 +684,7 @@ TablePtr CreateTableWalker::table()
antlr::RefAST s = m_root->getFirstChild();
// If the primary tree isn't filled, this isn't a normal CREATE TABLE statement. Switch to the next alternative tree.
if(s == 0)
if(s == nullptr)
s = m_root->getNextSibling();
// Skip to table name
@@ -861,14 +761,13 @@ TablePtr CreateTableWalker::table()
tc = tc->getNextSibling()->getNextSibling(); // skip primary and key
tc = tc->getNextSibling(); // skip LPAREN
FieldVector fields;
QStringList fields;
do
{
antlr::RefAST indexed_column = tc->getFirstChild();
QString col = columnname(indexed_column);
FieldPtr field = tab->field(tab->findField(col));
fields.push_back(field);
fields.push_back(col);
indexed_column = indexed_column->getNextSibling();
if(indexed_column != antlr::nullAST
@@ -890,6 +789,7 @@ TablePtr CreateTableWalker::table()
if(indexed_column != antlr::nullAST && indexed_column->getType() == sqlite3TokenTypes::AUTOINCREMENT)
{
auto field = findField(tab, col);
field->setAutoIncrement(true);
indexed_column = indexed_column->getNextSibling();
}
@@ -916,14 +816,14 @@ TablePtr CreateTableWalker::table()
tc = tc->getNextSibling(); // skip UNIQUE
tc = tc->getNextSibling(); // skip LPAREN
FieldVector fields;
QStringList fields;
do
{
antlr::RefAST indexed_column = tc->getFirstChild();
QString col = columnname(indexed_column);
FieldPtr field = tab->field(tab->findField(col));
fields.push_back(field);
auto field = findField(tab, col);
fields.push_back(field->name());
indexed_column = indexed_column->getNextSibling();
if(indexed_column != antlr::nullAST
@@ -953,7 +853,7 @@ TablePtr CreateTableWalker::table()
if(fields.size() == 1 && constraint_name.isEmpty())
{
fields[0]->setUnique(true);
findField(tab, fields[0])->setUnique(true);
delete unique;
} else {
tab->addConstraint(fields, ConstraintPtr(unique));
@@ -969,12 +869,11 @@ TablePtr CreateTableWalker::table()
tc = tc->getNextSibling(); // KEY
tc = tc->getNextSibling(); // LPAREN
FieldVector fields;
QStringList fields;
do
{
QString col = columnname(tc);
FieldPtr field = tab->field(tab->findField(col));
fields.push_back(field);
fields.push_back(findField(tab, col)->name());
tc = tc->getNextSibling();
@@ -1017,12 +916,12 @@ TablePtr CreateTableWalker::table()
tc = tc->getNextSibling(); // skip LPAREN
check->setExpression(concatExprAST(tc));
tab->addConstraint(FieldVector(), ConstraintPtr(check));
tab->addConstraint(QStringList(), ConstraintPtr(check));
}
break;
default:
{
qWarning() << "unknown table constraint in " << tab->name();
std::cout << "unknown table constraint in " << tab->name().toStdString() << std::endl;
tab->setFullyParsed(false);
}
break;
@@ -1037,7 +936,7 @@ TablePtr CreateTableWalker::table()
s = s->getNextSibling(); // WITHOUT
s = s->getNextSibling(); // ROWID
tab->setRowidColumn(tab->fields().at(tab->findPk())->name());
tab->setRowidColumn(tab->findPk()->name());
}
}
}
@@ -1056,7 +955,7 @@ void CreateTableWalker::parsecolumn(Table* table, antlr::RefAST c)
QString check;
QString collation;
sqlb::PrimaryKeyConstraint* primaryKey = nullptr;
QVector<sqlb::ForeignKeyClause*> foreignKeys;
std::vector<sqlb::ForeignKeyClause*> foreignKeys;
colname = columnname(c);
c = c->getNextSibling(); //type?
@@ -1211,7 +1110,7 @@ void CreateTableWalker::parsecolumn(Table* table, antlr::RefAST c)
break;
default:
{
qWarning() << "unknown column constraint in " << table->name() << "." << colname;
std::cout << "unknown column constraint in " << table->name().toStdString() << "." << colname.toStdString() << std::endl;
table->setFullyParsed(false);
}
break;
@@ -1221,22 +1120,22 @@ void CreateTableWalker::parsecolumn(Table* table, antlr::RefAST c)
FieldPtr f = FieldPtr(new Field(colname, type, notnull, defaultvalue, check, unique, collation));
f->setAutoIncrement(autoincrement);
table->addField(f);
table->fields.push_back(*f);
for(sqlb::ForeignKeyClause* fk : foreignKeys)
table->addConstraint({f}, ConstraintPtr(fk));
table->addConstraint({f->name()}, ConstraintPtr(fk));
if(primaryKey)
{
FieldVector v;
QStringList v;
if(table->constraint(v, Constraint::PrimaryKeyConstraintType))
{
table->primaryKeyRef().push_back(f);
table->primaryKeyRef().push_back(f->name());
// Delete useless primary key constraint. There already is a primary key object for this table, we
// don't need another one.
delete primaryKey;
} else {
table->addConstraint({f}, ConstraintPtr(primaryKey));
table->addConstraint({f->name()}, ConstraintPtr(primaryKey));
}
}
}
@@ -1267,7 +1166,6 @@ QString IndexedColumn::toString(const QString& indent, const QString& sep) const
Index::~Index()
{
clear();
}
Index& Index::operator=(const Index& rhs)
@@ -1281,53 +1179,18 @@ Index& Index::operator=(const Index& rhs)
m_whereExpr = rhs.m_whereExpr;
// Make copies of the column
for(const IndexedColumnPtr& c : rhs.m_columns)
addColumn(IndexedColumnPtr(new IndexedColumn(*c)));
for(const IndexedColumn& c : rhs.fields)
fields.push_back(c);
return *this;
}
void Index::clear()
{
m_name.clear();
m_unique = false;
m_table.clear();
m_whereExpr.clear();
m_columns.clear();
}
bool Index::removeColumn(const QString& name)
{
int index = findColumn(name);
if(index != -1)
{
m_columns.remove(index);
return true;
}
return false;
}
void Index::setColumns(const IndexedColumnVector& columns)
{
m_columns = columns;
}
int Index::findColumn(const QString& name) const
{
for(int i=0;i<m_columns.count();++i)
{
if(m_columns.at(i)->name().compare(name, Qt::CaseInsensitive) == 0)
return i;
}
return -1;
}
QStringList Index::columnSqlList() const
{
QStringList sl;
for(const IndexedColumnPtr& c : m_columns)
sl << c->toString();
for(const IndexedColumn& c : fields)
sl << c.toString();
return sl;
}
@@ -1355,12 +1218,12 @@ QString Index::sql(const QString& schema, bool ifNotExists) const
FieldInfoList Index::fieldInformation() const
{
FieldInfoList result;
for(const IndexedColumnPtr& c : m_columns)
result.append({c->name(), c->order(), c->toString(" ", " ")});
for(const IndexedColumn& c : fields)
result.emplace_back(c.name(), c.order(), c.toString(" ", " "));
return result;
}
ObjectPtr Index::parseSQL(const QString& sSQL)
IndexPtr Index::parseSQL(const QString& sSQL)
{
SetLocaleToC locale;
@@ -1379,15 +1242,17 @@ ObjectPtr Index::parseSQL(const QString& sSQL)
parser.createindex();
CreateIndexWalker ctw(parser.getAST());
return ctw.index();
auto i = ctw.index();
i->setOriginalSql(sSQL);
return i;
}
catch(antlr::ANTLRException& ex)
{
qCritical() << "Sqlite parse error: " << QString::fromStdString(ex.toString()) << "(" << sSQL << ")";
std::cerr << "Sqlite parse error: " << ex.toString() << "(" << sSQL.toStdString() << ")" << std::endl;
}
catch(...)
{
qCritical() << "Sqlite parse error: " << sSQL; //TODO
std::cerr << "Sqlite parse error: " << sSQL.toStdString() << std::endl; //TODO
}
return IndexPtr(new Index(""));
@@ -1510,45 +1375,30 @@ void CreateIndexWalker::parsecolumn(Index* index, antlr::RefAST c)
c = c->getNextSibling();
}
index->addColumn(IndexedColumnPtr(new IndexedColumn(name, isExpression, order)));
index->fields.emplace_back(name, isExpression, order);
}
View::~View()
{
clear();
}
ObjectPtr View::parseSQL(const QString& /*sSQL*/)
ViewPtr View::parseSQL(const QString& sSQL)
{
// TODO
return ViewPtr(new View(""));
}
void View::clear()
{
m_fields.clear();
}
void View::addField(const FieldPtr& f)
{
m_fields.append(FieldPtr(f));
}
void View::setFields(const FieldVector& fields)
{
clear();
m_fields = fields;
auto v = ViewPtr(new View(""));
v->setOriginalSql(sSQL);
return v;
}
QStringList View::fieldNames() const
{
QStringList sl;
for(const FieldPtr& f : m_fields)
sl << f->name();
for(const Field& f : fields)
sl << f.name();
return sl;
}
@@ -1556,18 +1406,19 @@ QStringList View::fieldNames() const
FieldInfoList View::fieldInformation() const
{
FieldInfoList result;
for(const FieldPtr& f : m_fields)
result.append({f->name(), f->type(), f->toString(" ", " ")});
for(const Field& f : fields)
result.emplace_back(f.name(), f.type(), f.toString(" ", " "));
return result;
}
ObjectPtr Trigger::parseSQL(const QString& /*sSQL*/)
TriggerPtr Trigger::parseSQL(const QString& sSQL)
{
// TODO
return TriggerPtr(new Trigger(""));
auto t = TriggerPtr(new Trigger(""));
t->setOriginalSql(sSQL);
return t;
}
} //namespace sqlb

View File

@@ -2,13 +2,18 @@
#ifndef SQLITETYPES_H
#define SQLITETYPES_H
#include <vector>
#include <unordered_map>
#include <memory>
#include <QString>
#include <QSharedPointer>
#include <QVector>
#include <QStringList>
#include <QMultiHash>
#include <QVariant>
template<typename C, typename E>
bool contains(const C& container, E element)
{
return std::find(container.begin(), container.end(), element) != container.end();
}
namespace sqlb {
#if QT_VERSION < QT_VERSION_CHECK(5, 6, 0)
@@ -31,7 +36,7 @@ inline uint qHashRange(InputIterator first, InputIterator last, uint seed = 0)
#endif
template<typename T>
uint qHash(const QVector<T>& key, uint seed = 0)
uint qHash(const QList<T>& key, uint seed = 0)
Q_DECL_NOEXCEPT_EXPR(noexcept(qHashRange(key.cbegin(), key.cend(), seed)))
{
return qHashRange(key.cbegin(), key.cend(), seed);
@@ -128,8 +133,13 @@ private:
QString m_name;
};
QDataStream& operator<<(QDataStream& ds, const ObjectIdentifier& objid);
QDataStream& operator>>(QDataStream& ds, ObjectIdentifier& objid);
struct StringListHash
{
size_t operator()(const QStringList& key) const
{
return qHash(key);
}
};
class Object;
class Table;
@@ -140,23 +150,25 @@ class Field;
class Constraint;
class IndexedColumn;
struct FieldInfo;
typedef QSharedPointer<Object> ObjectPtr;
typedef QSharedPointer<Table> TablePtr;
typedef QSharedPointer<Index> IndexPtr;
typedef QSharedPointer<View> ViewPtr;
typedef QSharedPointer<Trigger> TriggerPtr;
typedef QSharedPointer<Field> FieldPtr;
typedef QSharedPointer<Constraint> ConstraintPtr;
typedef QVector<FieldPtr> FieldVector;
typedef QSharedPointer<IndexedColumn> IndexedColumnPtr;
typedef QVector<IndexedColumnPtr> IndexedColumnVector;
typedef QMultiHash<FieldVector, ConstraintPtr> ConstraintMap;
typedef QList<FieldInfo> FieldInfoList;
QStringList fieldVectorToFieldNames(const sqlb::FieldVector& vector);
using ObjectPtr = std::shared_ptr<Object>;
using TablePtr = std::shared_ptr<Table>;
using IndexPtr = std::shared_ptr<Index>;
using ViewPtr = std::shared_ptr<View>;
using TriggerPtr = std::shared_ptr<Trigger>;
using FieldPtr = std::shared_ptr<Field>;
using ConstraintPtr = std::shared_ptr<Constraint>;
using FieldVector = std::vector<Field>;
using FieldPtrVector = std::vector<FieldPtr>;
using IndexedColumnVector = std::vector<IndexedColumn>;
using ConstraintMap = std::unordered_multimap<QStringList, ConstraintPtr, StringListHash>;
using FieldInfoList = std::vector<FieldInfo>;
struct FieldInfo
{
FieldInfo(const QString& name_, const QString& type_, const QString& sql_)
: name(name_), type(type_), sql(sql_)
{}
QString name;
QString type;
QString sql;
@@ -192,8 +204,6 @@ public:
virtual FieldInfoList fieldInformation() const { return FieldInfoList(); }
virtual void clear() {}
/**
* @brief Returns the CREATE statement for this object
* @param schema The schema name of the object
@@ -202,14 +212,6 @@ public:
*/
virtual QString sql(const QString& schema = QString("main"), bool ifNotExists = false) const = 0;
/**
* @brief parseSQL Parses the CREATE statement in sSQL.
* @param type The type of the object.
* @param sSQL The create statement.
* @return The parsed database object. The object may be empty if parsing failed.
*/
static ObjectPtr parseSQL(Object::Types type, const QString& sSQL);
protected:
QString m_name;
QString m_originalSql;
@@ -239,7 +241,7 @@ public:
void setName(const QString& name) { m_name = name; }
const QString& name() const { return m_name; }
virtual QString toSql(const FieldVector& applyOn) const = 0;
virtual QString toSql(const QStringList& applyOn) const = 0;
protected:
QString m_name;
@@ -269,7 +271,7 @@ public:
void setConstraint(const QString& constraint) { m_constraint = constraint; }
const QString& constraint() const { return m_constraint; }
virtual QString toSql(const FieldVector& applyOn) const;
virtual QString toSql(const QStringList& applyOn) const;
virtual ConstraintTypes type() const { return ForeignKeyConstraintType; }
@@ -286,7 +288,7 @@ class UniqueConstraint : public Constraint
public:
UniqueConstraint() {}
virtual QString toSql(const FieldVector& applyOn) const;
virtual QString toSql(const QStringList& applyOn) const;
virtual ConstraintTypes type() const { return UniqueConstraintType; }
};
@@ -299,7 +301,7 @@ public:
void setConflictAction(const QString& conflict) { m_conflictAction = conflict; }
const QString& conflictAction() const { return m_conflictAction; }
virtual QString toSql(const FieldVector& applyOn) const;
virtual QString toSql(const QStringList& applyOn) const;
virtual ConstraintTypes type() const { return PrimaryKeyConstraintType; }
@@ -318,7 +320,7 @@ public:
void setExpression(const QString& expr) { m_expression = expr; }
QString expression() const { return m_expression; }
virtual QString toSql(const FieldVector& applyOn) const;
virtual QString toSql(const QStringList& applyOn) const;
virtual ConstraintTypes type() const { return CheckConstraintType; }
@@ -329,6 +331,12 @@ private:
class Field
{
public:
Field()
: m_notnull(false),
m_autoincrement(false),
m_unique(false)
{}
Field(const QString& name,
const QString& type,
bool notnull = false,
@@ -346,6 +354,11 @@ public:
, m_collation(collation)
{}
bool operator==(const Field& rhs) const
{
return m_name == rhs.name();
}
QString toString(const QString& indent = "\t", const QString& sep = "\t") const;
void setName(const QString& name) { m_name = name; }
@@ -375,7 +388,6 @@ public:
bool unique() const { return m_unique; }
const QString& collation() const { return m_collation; }
static QStringList Datatypes;
private:
QString m_name;
QString m_type;
@@ -396,7 +408,9 @@ public:
virtual Types type() const { return Object::Table; }
const FieldVector& fields() const { return m_fields; }
FieldVector fields;
using field_type = Field;
using field_iterator = FieldVector::iterator;
/**
* @brief Returns the CREATE TABLE statement for this table object
@@ -404,11 +418,6 @@ public:
*/
QString sql(const QString& schema = QString("main"), bool ifNotExists = false) const;
void addField(const FieldPtr& f);
bool removeField(const QString& sFieldName);
void setFields(const FieldVector& fields);
void setField(int index, FieldPtr f);
const FieldPtr& field(int index) const { return m_fields[index]; }
QStringList fieldNames() const;
void setRowidColumn(const QString& rowid) { m_rowidColumn = rowid; }
@@ -419,42 +428,33 @@ public:
QString virtualUsing() const { return m_virtual; }
bool isVirtual() const { return !m_virtual.isEmpty(); }
void clear();
virtual FieldInfoList fieldInformation() const;
void addConstraint(FieldVector fields, ConstraintPtr constraint);
void setConstraint(FieldVector fields, ConstraintPtr constraint);
void removeConstraints(FieldVector fields = FieldVector(), Constraint::ConstraintTypes type = Constraint::NoType); //! Only removes the first constraint, if any
ConstraintPtr constraint(FieldVector fields = FieldVector(), Constraint::ConstraintTypes type = Constraint::NoType) const; //! Only returns the first constraint, if any
QList<ConstraintPtr> constraints(FieldVector fields = FieldVector(), Constraint::ConstraintTypes type = Constraint::NoType) const;
void addConstraint(QStringList fields, ConstraintPtr constraint);
void setConstraint(QStringList fields, ConstraintPtr constraint);
void removeConstraints(QStringList fields = QStringList(), Constraint::ConstraintTypes type = Constraint::NoType); //! Only removes the first constraint, if any
ConstraintPtr constraint(QStringList fields = QStringList(), Constraint::ConstraintTypes type = Constraint::NoType) const; //! Only returns the first constraint, if any
std::vector<ConstraintPtr> constraints(QStringList fields = QStringList(), Constraint::ConstraintTypes type = Constraint::NoType) const;
ConstraintMap allConstraints() const { return m_constraints; }
void setConstraints(const ConstraintMap& constraints);
FieldVector& primaryKeyRef();
const FieldVector& primaryKey() const;
QStringList& primaryKeyRef();
const QStringList& primaryKey() const;
void removeKeyFromAllConstraints(const QString& key);
void renameKeyInAllConstraints(const QString& key, const QString& to);
/**
* @brief findField Finds a field and returns the index.
* @param sname
* @return The field index if the field was found.
* -1 if field couldn't be found.
*/
int findField(const QString& sname) const;
int findPk() const;
field_iterator findPk();
/**
* @brief parseSQL Parses the create Table statement in sSQL.
* @param sSQL The create table statement.
* @return The table object. The table object may be empty if parsing failed.
*/
static ObjectPtr parseSQL(const QString& sSQL);
static TablePtr parseSQL(const QString& sSQL);
private:
QStringList fieldList() const;
bool hasAutoIncrement() const;
private:
FieldVector m_fields;
QString m_rowidColumn;
ConstraintMap m_constraints;
QString m_virtual;
@@ -496,6 +496,10 @@ public:
virtual Types type() const { return Object::Index; }
IndexedColumnVector fields;
using field_type = IndexedColumn;
using field_iterator = IndexedColumnVector::iterator;
virtual QString baseTable() const { return m_table; }
void setUnique(bool unique) { m_unique = unique; }
@@ -507,17 +511,6 @@ public:
void setWhereExpr(const QString& expr) { m_whereExpr = expr; }
const QString& whereExpr() const { return m_whereExpr; }
void setColumns(const IndexedColumnVector& columns);
const IndexedColumnVector& columns() const { return m_columns; }
void clearColumns() { m_columns.clear(); }
void addColumn(const IndexedColumnPtr& c) { m_columns.append(c); }
bool removeColumn(const QString& name);
void setColumn(int index, IndexedColumnPtr c) { m_columns[index] = c; }
IndexedColumnPtr& column(int index) { return m_columns[index]; }
int findColumn(const QString& name) const;
void clear();
/**
* @brief Returns the CREATE INDEX statement for this index object
* @return A QString with the CREATE INDEX object.
@@ -529,7 +522,7 @@ public:
* @param sSQL The create index statement.
* @return The index object. The index object may be empty if the parsing failed.
*/
static ObjectPtr parseSQL(const QString& sSQL);
static IndexPtr parseSQL(const QString& sSQL);
virtual FieldInfoList fieldInformation() const;
@@ -539,7 +532,6 @@ private:
bool m_unique;
QString m_table;
QString m_whereExpr;
IndexedColumnVector m_columns;
};
class View : public Object
@@ -550,21 +542,15 @@ public:
virtual Types type() const { return Object::View; }
FieldVector fields;
QString sql(const QString& schema = QString("main"), bool ifNotExists = false) const { /* TODO */ Q_UNUSED(schema); Q_UNUSED(ifNotExists); return m_originalSql; }
static ObjectPtr parseSQL(const QString& sSQL);
static ViewPtr parseSQL(const QString& sSQL);
void clear();
void addField(const FieldPtr& f);
void setFields(const FieldVector& fields);
const FieldPtr& field(int index) const { return m_fields[index]; }
QStringList fieldNames() const;
virtual FieldInfoList fieldInformation() const;
private:
FieldVector m_fields;
};
class Trigger : public Object
@@ -577,7 +563,7 @@ public:
QString sql(const QString& schema = QString("main"), bool ifNotExists = false) const { /* TODO */ Q_UNUSED(schema); Q_UNUSED(ifNotExists); return m_originalSql; }
static ObjectPtr parseSQL(const QString& sSQL);
static TriggerPtr parseSQL(const QString& sSQL);
virtual QString baseTable() const { return m_table; }
@@ -588,6 +574,60 @@ private:
QString m_table;
};
/**
* @brief findField Finds a field in the database object and returns an iterator to it.
* @param object
* @param name
* @return The iterator pointing to the field in the field container of the object if the field was found.
* object.fields.end() if the field couldn't be found.
*/
template<typename T>
typename T::field_iterator findField(T* object, const QString& name)
{
return std::find_if(object->fields.begin(), object->fields.end(), [&name](const typename T::field_type& f) {return f.name().compare(name, Qt::CaseInsensitive) == 0;});
}
template<typename T>
typename T::field_iterator findField(std::shared_ptr<T> object, const QString& name)
{
return findField(object.get(), name);
}
template<typename T>
typename T::field_iterator findField(T& object, const QString& name)
{
return findField(&object, name);
}
template<typename T> struct is_shared_ptr : std::false_type {};
template<typename T> struct is_shared_ptr<std::shared_ptr<T>> : std::true_type {};
/**
* @brief removeField Finds and removes a field in the database object
* @param object
* @param name
* @return true if sucessful, otherwise false
*/
template<typename T>
bool removeField(T* object, const QString& name)
{
auto index = findField(object, name);
if(index != object->fields.end())
{
object->fields.erase(index);
return true;
}
return false;
}
template<typename T, typename = typename std::enable_if<is_shared_ptr<T>::value>::type>
bool removeField(T object, const QString& name)
{
return removeField(object.get(), name);
}
template<typename T, typename = typename std::enable_if<!is_shared_ptr<T>::value>::type>
bool removeField(T& object, const QString& name)
{
return removeField(&object, name);
}
} //namespace sqlb
#endif

View File

@@ -25,10 +25,10 @@ void TestTable::sqlOutput()
Table tt("testtable");
FieldPtr f = FieldPtr(new Field("id", "integer"));
FieldPtr fkm = FieldPtr(new Field("km", "integer", false, "", "km > 1000"));
tt.addField(f);
tt.addField(FieldPtr(new Field("car", "text")));
tt.addField(fkm);
tt.addConstraint({f, fkm}, ConstraintPtr(new PrimaryKeyConstraint()));
tt.fields.push_back(*f);
tt.fields.emplace_back("car", "text");
tt.fields.push_back(*fkm);
tt.addConstraint({f->name(), fkm->name()}, ConstraintPtr(new PrimaryKeyConstraint()));
QCOMPARE(tt.sql(), QString("CREATE TABLE \"testtable\" (\n"
"\t\"id\"\tinteger,\n"
@@ -43,10 +43,10 @@ void TestTable::sqlGraveAccentOutput()
Table tt("testtable");
FieldPtr f = FieldPtr(new Field("id", "integer"));
FieldPtr fkm = FieldPtr(new Field("km", "integer", false, "", "km > 1000"));
tt.addField(f);
tt.addField(FieldPtr(new Field("car", "text")));
tt.addField(fkm);
tt.addConstraint({f, fkm}, ConstraintPtr(new PrimaryKeyConstraint()));
tt.fields.push_back(*f);
tt.fields.emplace_back("car", "text");
tt.fields.push_back(*fkm);
tt.addConstraint({f->name(), fkm->name()}, ConstraintPtr(new PrimaryKeyConstraint()));
sqlb::setIdentifierQuoting(sqlb::GraveAccents);
QCOMPARE(tt.sql(), QString("CREATE TABLE `testtable` (\n"
@@ -65,10 +65,10 @@ void TestTable::sqlSquareBracketsOutput()
Table tt("testtable");
FieldPtr f = FieldPtr(new Field("id", "integer"));
FieldPtr fkm = FieldPtr(new Field("km", "integer", false, "", "km > 1000"));
tt.addField(f);
tt.addField(FieldPtr(new Field("car", "text")));
tt.addField(fkm);
tt.addConstraint({f, fkm}, ConstraintPtr(new PrimaryKeyConstraint()));
tt.fields.push_back(*f);
tt.fields.emplace_back("car", "text");
tt.fields.push_back(*fkm);
tt.addConstraint({f->name(), fkm->name()}, ConstraintPtr(new PrimaryKeyConstraint()));
sqlb::setIdentifierQuoting(sqlb::SquareBrackets);
QCOMPARE(tt.sql(), QString("CREATE TABLE [testtable] (\n"
@@ -87,10 +87,10 @@ void TestTable::autoincrement()
FieldPtr f = FieldPtr(new Field("id", "integer"));
f->setAutoIncrement(true);
FieldPtr fkm = FieldPtr(new Field("km", "integer"));
tt.addField(f);
tt.addField(FieldPtr(new Field("car", "text")));
tt.addField(fkm);
tt.addConstraint({f}, ConstraintPtr(new PrimaryKeyConstraint()));
tt.fields.push_back(*f);
tt.fields.emplace_back("car", "text");
tt.fields.push_back(*fkm);
tt.addConstraint({f->name()}, ConstraintPtr(new PrimaryKeyConstraint()));
QCOMPARE(tt.sql(), QString("CREATE TABLE \"testtable\" (\n"
"\t\"id\"\tinteger PRIMARY KEY AUTOINCREMENT,\n"
@@ -105,10 +105,10 @@ void TestTable::notnull()
FieldPtr f = FieldPtr(new Field("id", "integer"));
f->setAutoIncrement(true);
FieldPtr fkm = FieldPtr(new Field("km", "integer"));
tt.addField(f);
tt.addField(FieldPtr(new Field("car", "text", true)));
tt.addField(fkm);
tt.addConstraint({f}, ConstraintPtr(new PrimaryKeyConstraint()));
tt.fields.push_back(*f);
tt.fields.emplace_back("car", "text", true);
tt.fields.push_back(*fkm);
tt.addConstraint({f->name()}, ConstraintPtr(new PrimaryKeyConstraint()));
QCOMPARE(tt.sql(), QString("CREATE TABLE \"testtable\" (\n"
"\t\"id\"\tinteger PRIMARY KEY AUTOINCREMENT,\n"
@@ -122,10 +122,10 @@ void TestTable::withoutRowid()
Table tt("testtable");
FieldPtr f = FieldPtr(new Field("a", "integer"));
f->setAutoIncrement(true);
tt.addField(f);
tt.addField(FieldPtr(new Field("b", "integer")));
tt.fields.push_back(*f);
tt.fields.emplace_back("b", "integer");
tt.setRowidColumn("a");
tt.addConstraint({f}, ConstraintPtr(new PrimaryKeyConstraint()));
tt.addConstraint({f->name()}, ConstraintPtr(new PrimaryKeyConstraint()));
QCOMPARE(tt.sql(), QString("CREATE TABLE \"testtable\" (\n"
"\t\"a\"\tinteger PRIMARY KEY AUTOINCREMENT,\n"
@@ -137,8 +137,8 @@ void TestTable::foreignKeys()
{
Table tt("testtable");
FieldPtr f = FieldPtr(new Field("a", "integer"));
tt.addField(f);
tt.addConstraint({f}, sqlb::ConstraintPtr(new sqlb::ForeignKeyClause("b", QStringList("c"))));
tt.fields.push_back(*f);
tt.addConstraint({f->name()}, sqlb::ConstraintPtr(new sqlb::ForeignKeyClause("b", QStringList("c"))));
QCOMPARE(tt.sql(), QString("CREATE TABLE \"testtable\" (\n"
"\t\"a\"\tinteger,\n"
@@ -153,10 +153,10 @@ void TestTable::uniqueConstraint()
FieldPtr f2 = FieldPtr(new Field("b", "integer"));
FieldPtr f3 = FieldPtr(new Field("c", "integer"));
f1->setUnique(true);
tt.addField(f1);
tt.addField(f2);
tt.addField(f3);
tt.addConstraint({f2, f3}, sqlb::ConstraintPtr(new sqlb::UniqueConstraint()));
tt.fields.push_back(*f1);
tt.fields.push_back(*f2);
tt.fields.push_back(*f3);
tt.addConstraint({f2->name(), f3->name()}, sqlb::ConstraintPtr(new sqlb::UniqueConstraint()));
QCOMPARE(tt.sql(), QString("CREATE TABLE \"testtable\" (\n"
"\t\"a\"\tinteger UNIQUE,\n"
@@ -174,26 +174,26 @@ void TestTable::parseSQL()
"\tinfo VARCHAR(255) CHECK (info == 'x')\n"
");";
Table tab = *(Table::parseSQL(sSQL).dynamicCast<sqlb::Table>());
Table tab = *(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sSQL)));
QCOMPARE(tab.name(), "hero");
QCOMPARE(tab.rowidColumn(), "_rowid_");
QCOMPARE(tab.fields().at(0)->name(), "id");
QCOMPARE(tab.fields().at(1)->name(), "name");
QCOMPARE(tab.fields().at(2)->name(), "info");
QCOMPARE(tab.fields.at(0).name(), "id");
QCOMPARE(tab.fields.at(1).name(), "name");
QCOMPARE(tab.fields.at(2).name(), "info");
QCOMPARE(tab.fields().at(0)->type(), "integer");
QCOMPARE(tab.fields().at(1)->type(), "text");
QCOMPARE(tab.fields().at(2)->type(), QString("VARCHAR(255)"));
QCOMPARE(tab.fields.at(0).type(), "integer");
QCOMPARE(tab.fields.at(1).type(), "text");
QCOMPARE(tab.fields.at(2).type(), QString("VARCHAR(255)"));
FieldVector pk = tab.primaryKey();
QVERIFY(tab.fields().at(0)->autoIncrement());
QStringList pk = tab.primaryKey();
QVERIFY(tab.fields.at(0).autoIncrement());
QCOMPARE(pk.size(), 1);
QCOMPARE(pk.at(0), tab.fields().at(0));
QVERIFY(tab.fields().at(1)->notnull());
QCOMPARE(tab.fields().at(1)->defaultValue(), QString("'xxxx'"));
QCOMPARE(tab.fields().at(1)->check(), QString(""));
QCOMPARE(tab.fields().at(2)->check(), QString("info=='x'"));
QCOMPARE(pk.at(0), tab.fields.at(0).name());
QVERIFY(tab.fields.at(1).notnull());
QCOMPARE(tab.fields.at(1).defaultValue(), QString("'xxxx'"));
QCOMPARE(tab.fields.at(1).check(), QString(""));
QCOMPARE(tab.fields.at(2).check(), QString("info=='x'"));
}
void TestTable::parseSQLdefaultexpr()
@@ -204,29 +204,29 @@ void TestTable::parseSQLdefaultexpr()
"date datetime default CURRENT_TIMESTAMP,"
"zoi integer)";
Table tab = *(Table::parseSQL(sSQL).dynamicCast<sqlb::Table>());
Table tab = *(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sSQL)));
QCOMPARE(tab.name(), QString("chtest"));
QCOMPARE(tab.fields().at(0)->name(), QString("id"));
QCOMPARE(tab.fields().at(1)->name(), QString("dumpytext"));
QCOMPARE(tab.fields().at(2)->name(), QString("date"));
QCOMPARE(tab.fields().at(3)->name(), QString("zoi"));
QCOMPARE(tab.fields.at(0).name(), QString("id"));
QCOMPARE(tab.fields.at(1).name(), QString("dumpytext"));
QCOMPARE(tab.fields.at(2).name(), QString("date"));
QCOMPARE(tab.fields.at(3).name(), QString("zoi"));
QCOMPARE(tab.fields().at(0)->type(), QString("integer"));
QCOMPARE(tab.fields().at(1)->type(), QString("text"));
QCOMPARE(tab.fields().at(2)->type(), QString("datetime"));
QCOMPARE(tab.fields().at(3)->type(), QString("integer"));
QCOMPARE(tab.fields.at(0).type(), QString("integer"));
QCOMPARE(tab.fields.at(1).type(), QString("text"));
QCOMPARE(tab.fields.at(2).type(), QString("datetime"));
QCOMPARE(tab.fields.at(3).type(), QString("integer"));
QCOMPARE(tab.fields().at(1)->defaultValue(), QString("('axa')"));
QCOMPARE(tab.fields().at(1)->check(), QString("dumpytext==\"aa\""));
QCOMPARE(tab.fields().at(2)->defaultValue(), QString("CURRENT_TIMESTAMP"));
QCOMPARE(tab.fields().at(2)->check(), QString(""));
QCOMPARE(tab.fields().at(3)->defaultValue(), QString(""));
QCOMPARE(tab.fields().at(3)->check(), QString(""));
QCOMPARE(tab.fields.at(1).defaultValue(), QString("('axa')"));
QCOMPARE(tab.fields.at(1).check(), QString("dumpytext==\"aa\""));
QCOMPARE(tab.fields.at(2).defaultValue(), QString("CURRENT_TIMESTAMP"));
QCOMPARE(tab.fields.at(2).check(), QString(""));
QCOMPARE(tab.fields.at(3).defaultValue(), QString(""));
QCOMPARE(tab.fields.at(3).check(), QString(""));
sqlb::FieldVector pk = tab.primaryKey();
QStringList pk = tab.primaryKey();
QCOMPARE(pk.size(), 1);
QCOMPARE(pk.at(0), tab.fields().at(0));
QCOMPARE(pk.at(0), tab.fields.at(0).name());
}
void TestTable::parseSQLMultiPk()
@@ -238,52 +238,52 @@ void TestTable::parseSQLMultiPk()
"PRIMARY KEY(\"id1\",\"id2\")\n"
");";
Table tab = *(Table::parseSQL(sSQL).dynamicCast<sqlb::Table>());
Table tab = *(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sSQL)));
QCOMPARE(tab.name(), "hero");
QCOMPARE(tab.fields().at(0)->name(), "id1");
QCOMPARE(tab.fields().at(1)->name(), "id2");
QCOMPARE(tab.fields.at(0).name(), "id1");
QCOMPARE(tab.fields.at(1).name(), "id2");
QCOMPARE(tab.fields().at(0)->type(), "integer");
QCOMPARE(tab.fields().at(1)->type(), "integer");
QCOMPARE(tab.fields.at(0).type(), "integer");
QCOMPARE(tab.fields.at(1).type(), "integer");
sqlb::FieldVector pk = tab.primaryKey();
QStringList pk = tab.primaryKey();
QCOMPARE(pk.size(), 2);
QCOMPARE(pk.at(0), tab.fields().at(0));
QCOMPARE(pk.at(1), tab.fields().at(1));
QCOMPARE(pk.at(0), tab.fields.at(0).name());
QCOMPARE(pk.at(1), tab.fields.at(1).name());
}
void TestTable::parseSQLForeignKey()
{
QString sSQL = "CREATE TABLE grammar_test(id, test, FOREIGN KEY(test) REFERENCES other_table);";
Table tab = *(Table::parseSQL(sSQL).dynamicCast<sqlb::Table>());
Table tab = *(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sSQL)));
QCOMPARE(tab.name(), "grammar_test");
QCOMPARE(tab.fields().at(0)->name(), "id");
QCOMPARE(tab.fields().at(1)->name(), "test");
QCOMPARE(tab.fields.at(0).name(), "id");
QCOMPARE(tab.fields.at(1).name(), "test");
}
void TestTable::parseSQLSingleQuotes()
{
QString sSQL = "CREATE TABLE 'test'('id','test');";
Table tab = *(Table::parseSQL(sSQL).dynamicCast<sqlb::Table>());
Table tab = *(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sSQL)));
QCOMPARE(tab.name(), "test");
QCOMPARE(tab.fields().at(0)->name(), "id");
QCOMPARE(tab.fields().at(1)->name(), "test");
QCOMPARE(tab.fields.at(0).name(), "id");
QCOMPARE(tab.fields.at(1).name(), "test");
}
void TestTable::parseSQLSquareBrackets()
{
QString sSQL = "CREATE TABLE [test]([id],[test]);";
Table tab = *(Table::parseSQL(sSQL).dynamicCast<sqlb::Table>());
Table tab = *(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sSQL)));
QCOMPARE(tab.name(), "test");
QCOMPARE(tab.fields().at(0)->name(), "id");
QCOMPARE(tab.fields().at(1)->name(), "test");
QCOMPARE(tab.fields.at(0).name(), "id");
QCOMPARE(tab.fields.at(1).name(), "test");
}
@@ -291,11 +291,11 @@ void TestTable::parseSQLKeywordInIdentifier()
{
QString sSQL = "CREATE TABLE deffered(key integer primary key, if text);";
Table tab = *(Table::parseSQL(sSQL).dynamicCast<sqlb::Table>());
Table tab = *(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sSQL)));
QCOMPARE(tab.name(), "deffered");
QCOMPARE(tab.fields().at(0)->name(), "key");
QCOMPARE(tab.fields().at(1)->name(), "if");
QCOMPARE(tab.fields.at(0).name(), "key");
QCOMPARE(tab.fields.at(1).name(), "if");
}
@@ -305,20 +305,20 @@ void TestTable::parseSQLSomeKeywordsInIdentifier()
"`Area of Work` TEXT,"
"`Average Number of Volunteers` INTEGER);";
Table tab = *(Table::parseSQL(sSQL).dynamicCast<sqlb::Table>());
Table tab = *(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sSQL)));
QCOMPARE(tab.name(), "Average Number of Volunteers by Area of Work");
QCOMPARE(tab.fields().at(0)->name(), "Area of Work");
QCOMPARE(tab.fields().at(1)->name(), "Average Number of Volunteers");
QCOMPARE(tab.fields.at(0).name(), "Area of Work");
QCOMPARE(tab.fields.at(1).name(), "Average Number of Volunteers");
}
void TestTable::parseSQLWithoutRowid()
{
QString sSQL = "CREATE TABLE test(a integer primary key, b integer) WITHOUT ROWID;";
Table tab = *(Table::parseSQL(sSQL).dynamicCast<sqlb::Table>());
Table tab = *(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sSQL)));
QCOMPARE(tab.fields().at(tab.findPk())->name(), "a");
QCOMPARE(tab.findPk()->name(), "a");
QCOMPARE(tab.rowidColumn(), "a");
}
@@ -329,10 +329,10 @@ void TestTable::parseNonASCIIChars()
"PRIMARY KEY(`Fieldöäüß`)"
");";
Table tab = *(Table::parseSQL(sSQL).dynamicCast<sqlb::Table>());
Table tab = *(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sSQL)));
QCOMPARE(tab.name(), "lösung");
QCOMPARE(tab.fields().at(0)->name(), "Fieldöäüß");
QCOMPARE(tab.fields.at(0).name(), "Fieldöäüß");
}
void TestTable::parseNonASCIICharsEs()
@@ -342,69 +342,69 @@ void TestTable::parseNonASCIICharsEs()
"PRIMARY KEY(\"Field áéíóúÁÉÍÓÚñÑçÇ\")"
");";
Table tab = *(Table::parseSQL(sSQL).dynamicCast<sqlb::Table>());
Table tab = *(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sSQL)));
QCOMPARE(tab.name(), "Cigüeñas de Alcalá");
QCOMPARE(tab.fields().at(0)->name(), "Field áéíóúÁÉÍÓÚñÑçÇ");
QCOMPARE(tab.fields.at(0).name(), "Field áéíóúÁÉÍÓÚñÑçÇ");
}
void TestTable::parseSQLEscapedQuotes()
{
QString sSql = "CREATE TABLE double_quotes(a text default 'a''a');";
Table tab = *(Table::parseSQL(sSql).dynamicCast<sqlb::Table>());
Table tab = *(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sSql)));
QCOMPARE(tab.name(), QString("double_quotes"));
QCOMPARE(tab.fields().at(0)->name(), QString("a"));
QCOMPARE(tab.fields().at(0)->defaultValue(), QString("'a''a'"));
QCOMPARE(tab.fields.at(0).name(), QString("a"));
QCOMPARE(tab.fields.at(0).defaultValue(), QString("'a''a'"));
}
void TestTable::parseSQLForeignKeys()
{
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).dynamicCast<sqlb::Table>());
Table tab = *(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sql)));
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.constraint({tab.fields().at(0)}, sqlb::Constraint::ForeignKeyConstraintType).dynamicCast<sqlb::ForeignKeyClause>()->table(), QString("x"));
QCOMPARE(tab.fields().at(1)->name(), QString("b"));
QCOMPARE(tab.fields().at(1)->type(), QString("int"));
QCOMPARE(tab.constraint({tab.fields().at(1)}, sqlb::Constraint::ForeignKeyConstraintType).dynamicCast<sqlb::ForeignKeyClause>()->toString(), QString("\"w\"(\"y\",\"z\") on delete set null"));
QCOMPARE(tab.fields.at(0).name(), QString("a"));
QCOMPARE(tab.fields.at(0).type(), QString("int"));
QCOMPARE(std::dynamic_pointer_cast<sqlb::ForeignKeyClause>(tab.constraint({tab.fields.at(0).name()}, sqlb::Constraint::ForeignKeyConstraintType))->table(), QString("x"));
QCOMPARE(tab.fields.at(1).name(), QString("b"));
QCOMPARE(tab.fields.at(1).type(), QString("int"));
QCOMPARE(std::dynamic_pointer_cast<sqlb::ForeignKeyClause>(tab.constraint({tab.fields.at(1).name()}, sqlb::Constraint::ForeignKeyConstraintType))->toString(), QString("\"w\"(\"y\",\"z\") on delete set null"));
}
void TestTable::parseSQLCheckConstraint()
{
QString sql = "CREATE TABLE a (\"b\" text CHECK(\"b\"='A' or \"b\"='B'));";
Table tab = *(Table::parseSQL(sql).dynamicCast<sqlb::Table>());
Table tab = *(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sql)));
QCOMPARE(tab.name(), QString("a"));
QCOMPARE(tab.fields().at(0)->name(), QString("b"));
QCOMPARE(tab.fields().at(0)->type(), QString("text"));
QCOMPARE(tab.fields().at(0)->check(), QString("\"b\"='A' or \"b\"='B'"));
QCOMPARE(tab.fields.at(0).name(), QString("b"));
QCOMPARE(tab.fields.at(0).type(), QString("text"));
QCOMPARE(tab.fields.at(0).check(), QString("\"b\"='A' or \"b\"='B'"));
}
void TestTable::parseDefaultValues()
{
QString sql = "CREATE TABLE test(a int DEFAULT 0, b int DEFAULT -1, c text DEFAULT 'hello', d text DEFAULT '0');";
Table tab = *(Table::parseSQL(sql).dynamicCast<sqlb::Table>());
Table tab = *(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sql)));
QCOMPARE(tab.name(), QString("test"));
QCOMPARE(tab.fields().at(0)->name(), QString("a"));
QCOMPARE(tab.fields().at(0)->type(), QString("int"));
QCOMPARE(tab.fields().at(0)->defaultValue(), QString("0"));
QCOMPARE(tab.fields().at(1)->name(), QString("b"));
QCOMPARE(tab.fields().at(1)->type(), QString("int"));
QCOMPARE(tab.fields().at(1)->defaultValue(), QString("-1"));
QCOMPARE(tab.fields().at(2)->name(), QString("c"));
QCOMPARE(tab.fields().at(2)->type(), QString("text"));
QCOMPARE(tab.fields().at(2)->defaultValue(), QString("'hello'"));
QCOMPARE(tab.fields().at(3)->name(), QString("d"));
QCOMPARE(tab.fields().at(3)->type(), QString("text"));
QCOMPARE(tab.fields().at(3)->defaultValue(), QString("'0'"));
QCOMPARE(tab.fields.at(0).name(), QString("a"));
QCOMPARE(tab.fields.at(0).type(), QString("int"));
QCOMPARE(tab.fields.at(0).defaultValue(), QString("0"));
QCOMPARE(tab.fields.at(1).name(), QString("b"));
QCOMPARE(tab.fields.at(1).type(), QString("int"));
QCOMPARE(tab.fields.at(1).defaultValue(), QString("-1"));
QCOMPARE(tab.fields.at(2).name(), QString("c"));
QCOMPARE(tab.fields.at(2).type(), QString("text"));
QCOMPARE(tab.fields.at(2).defaultValue(), QString("'hello'"));
QCOMPARE(tab.fields.at(3).name(), QString("d"));
QCOMPARE(tab.fields.at(3).type(), QString("text"));
QCOMPARE(tab.fields.at(3).defaultValue(), QString("'0'"));
}
void TestTable::createTableWithIn()
@@ -414,10 +414,10 @@ void TestTable::createTableWithIn()
"value NVARCHAR(5) CHECK (value IN ('a', 'b', 'c'))"
");";
Table tab = *(Table::parseSQL(sSQL).dynamicCast<sqlb::Table>());
Table tab = *(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sSQL)));
QCOMPARE(tab.name(), "not_working");
QCOMPARE(tab.fields().at(1)->check(), "value IN ('a','b','c')");
QCOMPARE(tab.fields.at(1).check(), "value IN ('a','b','c')");
}
void TestTable::createTableWithNotLikeConstraint()
@@ -432,16 +432,16 @@ void TestTable::createTableWithNotLikeConstraint()
"value7 INTEGER CONSTRAINT 'value' CHECK(NOT EXISTS (1))\n"
");";
Table tab = *(Table::parseSQL(sSQL).dynamicCast<sqlb::Table>());
Table tab = *(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sSQL)));
QCOMPARE(tab.name(), "hopefully_working");
QCOMPARE(tab.fields().at(0)->check(), "value NOT LIKE 'prefix%'");
QCOMPARE(tab.fields().at(1)->check(), "value2 NOT MATCH 'prefix%'");
QCOMPARE(tab.fields().at(2)->check(), "value3 NOT REGEXP 'prefix%'");
QCOMPARE(tab.fields().at(3)->check(), "value4 NOT GLOB 'prefix%'");
QCOMPARE(tab.fields().at(4)->check(), "value5 BETWEEN 1+4 AND 100 OR 200");
QCOMPARE(tab.fields().at(5)->check(), "value6 NOT BETWEEN 1 AND 100");
QCOMPARE(tab.fields().at(6)->check(), "NOT EXISTS (1)");
QCOMPARE(tab.fields.at(0).check(), "value NOT LIKE 'prefix%'");
QCOMPARE(tab.fields.at(1).check(), "value2 NOT MATCH 'prefix%'");
QCOMPARE(tab.fields.at(2).check(), "value3 NOT REGEXP 'prefix%'");
QCOMPARE(tab.fields.at(3).check(), "value4 NOT GLOB 'prefix%'");
QCOMPARE(tab.fields.at(4).check(), "value5 BETWEEN 1+4 AND 100 OR 200");
QCOMPARE(tab.fields.at(5).check(), "value6 NOT BETWEEN 1 AND 100");
QCOMPARE(tab.fields.at(6).check(), "NOT EXISTS (1)");
}
void TestTable::rowValues()
@@ -452,10 +452,10 @@ void TestTable::rowValues()
"CHECK((a, b) = (1, 2))\n"
");";
Table tab = *(Table::parseSQL(sql).dynamicCast<sqlb::Table>());
Table tab = *(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sql)));
QCOMPARE(tab.name(), "test");
QCOMPARE(tab.constraint({}, sqlb::Constraint::CheckConstraintType).dynamicCast<sqlb::CheckConstraint>()->expression(), QString("(a,b)=(1,2)"));
QCOMPARE(std::dynamic_pointer_cast<sqlb::CheckConstraint>(tab.constraint({}, sqlb::Constraint::CheckConstraintType))->expression(), QString("(a,b)=(1,2)"));
}
void TestTable::complexExpressions()
@@ -467,11 +467,11 @@ void TestTable::complexExpressions()
"d INTEGER CHECK((((d > 0))))\n"
");";
Table tab = *(Table::parseSQL(sql).dynamicCast<sqlb::Table>());
Table tab = *(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sql)));
QCOMPARE(tab.name(), "test");
QCOMPARE(tab.fields().at(0)->check(), "(a>0)");
QCOMPARE(tab.fields().at(1)->check(), "(b>0 and b>1)");
QCOMPARE(tab.fields().at(2)->check(), "(c=-1) or (c>0 and c>1) or (c=0)");
QCOMPARE(tab.fields().at(3)->check(), "(((d>0)))");
QCOMPARE(tab.fields.at(0).check(), "(a>0)");
QCOMPARE(tab.fields.at(1).check(), "(b>0 and b>1)");
QCOMPARE(tab.fields.at(2).check(), "(c=-1) or (c>0 and c>1) or (c=0)");
QCOMPARE(tab.fields.at(3).check(), "(((d>0)))");
}