Code refactoring

This changes the class structure in the sqlb namespace as well as the
DBBrowserObject class. The rest of the commit are changes that are
required by the modifications in sqlb and DBBrowserObject.

The idea behind this refactoring is this: we currently have the
DBBrowserObject class which holds some basic information about the
database object (name, type, SQL string, etc.). It also contains a
sqlb::Table and a sqlb::Index object. Those are used if the type of
the object is table or index and they contain a whole lot more
information on the object than the DBBrowserObject class, including the
name, the type, the SQL string, etc.

So we have a duplication here. There are two class structures for
storing the same information. This has historic reasons but other than
that there is no point in keeping it this way. With this commit I start
the work of consolidating the sqlb classes in order to get rid of the
DBBrowserObject class entirely.

This commit only starts this task, it doesn't finish it. This is why it
is a little messy here and there, but then again the old structure was a
little messy, too. We will need at least a very basic trigger and view
parser before finishing this is even possible. When this is done, I hope
the ode will be much easier to read and understand. But even in the
current state there already is some progress: we save a little bit of
memory, don't copy big objects all the time anymore, and replace a lot
of unnecessary string comparisons with integer comparisons.
This commit is contained in:
Martin Kleusberg
2017-01-20 17:31:54 +01:00
parent e3ef13a916
commit e5a79ec0fa
12 changed files with 252 additions and 164 deletions

View File

@@ -178,24 +178,32 @@ void DbStructureModel::reloadData()
for(auto it=dbobjs.constBegin();it!=dbobjs.constEnd();++it)
{
// Object node
QTreeWidgetItem* item = addNode(typeToParentItem.value((*it).gettype()), *it);
QString type;
switch((*it).gettype())
{
case sqlb::Object::ObjectTypes::Table: type = "table"; break;
case sqlb::Object::ObjectTypes::Index: type = "index"; break;
case sqlb::Object::ObjectTypes::Trigger: type = "trigger"; break;
case sqlb::Object::ObjectTypes::View: type = "view"; break;
}
QTreeWidgetItem* item = addNode(typeToParentItem.value(type), *it);
// If it is a table or view add the field nodes
if((*it).gettype() == "table" || (*it).gettype() == "view")
if((*it).gettype() == sqlb::Object::ObjectTypes::Table || (*it).gettype() == sqlb::Object::ObjectTypes::View)
{
// Add extra node for browsable section
addNode(typeToParentItem.value("browsable"), *it);
// Add field nodes
sqlb::FieldVector pk = (*it).table.primaryKey();
for(int i=0; i < (*it).table.fields().size(); ++i)
sqlb::FieldVector pk = (*it).object.dynamicCast<sqlb::Table>()->primaryKey();
for(int i=0; i < (*it).object.dynamicCast<sqlb::Table>()->fields().size(); ++i)
{
QTreeWidgetItem *fldItem = new QTreeWidgetItem(item);
fldItem->setText(0, (*it).table.fields().at(i)->name());
fldItem->setText(0, (*it).object.dynamicCast<sqlb::Table>()->fields().at(i)->name());
fldItem->setText(1, "field");
fldItem->setText(2, (*it).table.fields().at(i)->type());
fldItem->setText(3, (*it).table.fields().at(i)->toString(" ", " "));
if(pk.contains((*it).table.fields().at(i)))
fldItem->setText(2, (*it).object.dynamicCast<sqlb::Table>()->fields().at(i)->type());
fldItem->setText(3, (*it).object.dynamicCast<sqlb::Table>()->fields().at(i)->toString(" ", " "));
if(pk.contains((*it).object.dynamicCast<sqlb::Table>()->fields().at(i)))
fldItem->setIcon(0, QIcon(":/icons/field_key"));
else
fldItem->setIcon(0, QIcon(":/icons/field"));
@@ -279,10 +287,19 @@ bool DbStructureModel::dropMimeData(const QMimeData* data, Qt::DropAction action
QTreeWidgetItem* DbStructureModel::addNode(QTreeWidgetItem* parent, const DBBrowserObject& object)
{
QString type;
switch(object.gettype())
{
case sqlb::Object::ObjectTypes::Table: type = "table"; break;
case sqlb::Object::ObjectTypes::Index: type = "index"; break;
case sqlb::Object::ObjectTypes::Trigger: type = "trigger"; break;
case sqlb::Object::ObjectTypes::View: type = "view"; break;
}
QTreeWidgetItem *item = new QTreeWidgetItem(parent);
item->setIcon(0, QIcon(QString(":/icons/%1").arg(object.gettype())));
item->setIcon(0, QIcon(QString(":/icons/%1").arg(type)));
item->setText(0, object.getname());
item->setText(1, object.gettype());
item->setText(1, type);
item->setText(3, object.getsql());
return item;

View File

@@ -33,7 +33,7 @@ EditIndexDialog::EditIndexDialog(DBBrowserDB& db, const QString& indexName, bool
if(!newIndex)
{
// Load the current layour and fill in the dialog fields
index = pdb.getObjectByName(curIndex).index;
index = *(pdb.getObjectByName(curIndex).dynamicCast<sqlb::Index>());
ui->editIndexName->blockSignals(true);
ui->editIndexName->setText(index.name());
@@ -69,7 +69,7 @@ void EditIndexDialog::tableChanged(const QString& new_table, bool initialLoad)
}
// And fill the table again
QStringList fields = pdb.getObjectByName(new_table).table.fieldNames();
QStringList fields = pdb.getObjectByName(new_table).dynamicCast<sqlb::Table>()->fieldNames();
ui->tableIndexColumns->setRowCount(fields.size());
for(int i=0; i < fields.size(); ++i)
{

View File

@@ -33,11 +33,11 @@ EditTableDialog::EditTableDialog(DBBrowserDB& db, const QString& tableName, bool
if(m_bNewTable == false)
{
// Existing table, so load and set the current layout
DBBrowserObject obj = pdb.getObjectByName(curTable);
QString sTablesql = obj.getsql();
QPair<sqlb::Table, bool> parse_result = sqlb::Table::parseSQL(sTablesql);
m_table = parse_result.first;
m_table.setTemporary(obj.isTemporary());
sqlb::TablePtr obj = pdb.getObjectByName(curTable).dynamicCast<sqlb::Table>();
QString sTablesql = obj->originalSql();
QPair<sqlb::ObjectPtr, bool> parse_result = sqlb::Table::parseSQL(sTablesql);
m_table = *(parse_result.first.dynamicCast<sqlb::Table>());
m_table.setTemporary(obj->isTemporary());
ui->labelEditWarning->setVisible(!parse_result.second);
// Set without rowid and temporary checkboxex. No need to trigger any events here as we're only loading a table exactly as it is stored by SQLite, so no need
@@ -278,7 +278,7 @@ void EditTableDialog::itemChanged(QTreeWidgetItem *item, int column)
sqlb::FieldVector pk = m_table.primaryKey();
foreach(const DBBrowserObject& fkobj, pdb.objMap.values("table"))
{
QList<sqlb::ConstraintPtr> fks = fkobj.table.constraints(sqlb::FieldVector(), sqlb::Constraint::ForeignKeyConstraintType);
QList<sqlb::ConstraintPtr> fks = fkobj.object.dynamicCast<sqlb::Table>()->constraints(sqlb::FieldVector(), sqlb::Constraint::ForeignKeyConstraintType);
foreach(sqlb::ConstraintPtr fkptr, fks)
{
QSharedPointer<sqlb::ForeignKeyClause> fk = fkptr.dynamicCast<sqlb::ForeignKeyClause>();
@@ -350,7 +350,7 @@ 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).table.rowidColumn()))
.arg(sqlb::escapeIdentifier(pdb.getObjectByName(curTable).dynamicCast<sqlb::Table>()->rowidColumn()))
.arg(sqlb::escapeIdentifier(curTable))
.arg(sqlb::escapeIdentifier(field->name())));
if(m.data(m.index(0, 0)).toInt() > 0)
@@ -578,8 +578,7 @@ void EditTableDialog::removeField()
QMessageBox::warning(0, QApplication::applicationName(), pdb.lastError());
} else {
//relayout
QString sTablesql = pdb.getObjectByName(curTable).getsql();
m_table = sqlb::Table::parseSQL(sTablesql).first;
m_table = *(pdb.getObjectByName(curTable).dynamicCast<sqlb::Table>());
populateFields();
}
}
@@ -662,8 +661,7 @@ void EditTableDialog::moveCurrentField(bool down)
QMessageBox::warning(0, QApplication::applicationName(), pdb.lastError());
} else {
// Reload table SQL
QString sTablesql = pdb.getObjectByName(curTable).getsql();
m_table = sqlb::Table::parseSQL(sTablesql).first;
m_table = *(pdb.getObjectByName(curTable).dynamicCast<sqlb::Table>());
populateFields();
// Select old item at new position

View File

@@ -81,9 +81,9 @@ ForeignKeyEditorDelegate::ForeignKeyEditorDelegate(const DBBrowserDB& db, sqlb::
{
const auto objects = m_db.getBrowsableObjects();
for (auto& obj : objects) {
if ("table" == obj.gettype()) {
QString tableName = obj.table.name();
m_tablesIds.insert(tableName, obj.table.fieldNames());
if (obj.gettype() == sqlb::Object::ObjectTypes::Table) {
QString tableName = obj.object->name();
m_tablesIds.insert(tableName, obj.object.dynamicCast<sqlb::Table>()->fieldNames());
}
}
}

View File

@@ -186,9 +186,9 @@ void ImportCsvDialog::accept()
objectMap objects = pdb->getBrowsableObjects();
for(auto it=objects.constBegin();it!=objects.constEnd();++it)
{
if(it.value().gettype() == "table" && it.value().getname() == ui->editName->text())
if(it.value().gettype() == sqlb::Object::ObjectTypes::Table && it.value().getname() == ui->editName->text())
{
if((size_t)it.value().table.fields().size() != csv.columns())
if((size_t)it.value().object.dynamicCast<sqlb::Table>()->fields().size() != csv.columns())
{
QMessageBox::warning(this, QApplication::applicationName(),
tr("There is already a table of that name and an import into an existing table is only possible if the number of columns match."));

View File

@@ -366,9 +366,9 @@ void MainWindow::populateStructure()
{
QString objectname = it.value().getname();
for(int i=0; i < (*it).table.fields().size(); ++i)
for(int i=0; i < (*it).object.dynamicCast<sqlb::Table>()->fields().size(); ++i)
{
QString fieldname = (*it).table.fields().at(i)->name();
QString fieldname = (*it).object.dynamicCast<sqlb::Table>()->fields().at(i)->name();
tablesToColumnsMap[objectname].append(fieldname);
}
}
@@ -429,7 +429,7 @@ void MainWindow::populateTable()
} else {
QVector<QString> v;
bool only_defaults = true;
const sqlb::FieldVector& tablefields = db.getObjectByName(tablename).table.fields();
const sqlb::FieldVector& tablefields = db.getObjectByName(tablename).dynamicCast<sqlb::Table>()->fields();
for(int i=0; i<tablefields.size(); ++i)
{
QString format = tableIt.value().displayFormats[i+1];
@@ -498,7 +498,7 @@ void MainWindow::populateTable()
}
// Activate the add and delete record buttons and editing only if a table has been selected
bool editable = db.getObjectByName(tablename).gettype() == "table" && !db.readOnly();
bool editable = db.getObjectByName(tablename)->type() == sqlb::Object::ObjectTypes::Table && !db.readOnly();
ui->buttonNewRecord->setEnabled(editable);
ui->buttonDeleteRecord->setEnabled(editable);
ui->dataTable->setEditTriggers(editable ? QAbstractItemView::SelectedClicked | QAbstractItemView::AnyKeyPressed | QAbstractItemView::EditKeyPressed : QAbstractItemView::NoEditTriggers);
@@ -816,7 +816,7 @@ void MainWindow::doubleClickTable(const QModelIndex& index)
// * Don't allow editing of other objects than tables (on the browse table) *
bool isEditingAllowed = (m_currentTabTableModel == m_browseTableModel) &&
(db.getObjectByName(ui->comboBrowseTable->currentText()).gettype() == "table");
(db.getObjectByName(ui->comboBrowseTable->currentText())->type() == sqlb::Object::ObjectTypes::Table);
// Enable or disable the Apply, Null, & Import buttons in the Edit Cell
// dock depending on the value of the "isEditingAllowed" bool above
@@ -840,7 +840,7 @@ void MainWindow::dataTableSelectionChanged(const QModelIndex& index)
}
bool editingAllowed = (m_currentTabTableModel == m_browseTableModel) &&
(db.getObjectByName(ui->comboBrowseTable->currentText()).gettype() == "table");
(db.getObjectByName(ui->comboBrowseTable->currentText())->type() == sqlb::Object::ObjectTypes::Table);
// Don't allow editing of other objects than tables
editDock->setReadOnly(!editingAllowed);
@@ -2195,16 +2195,16 @@ void MainWindow::copyCurrentCreateStatement()
void MainWindow::jumpToRow(const QString& table, QString column, const QByteArray& value)
{
// First check if table exists
DBBrowserObject obj = db.getObjectByName(table);
if(obj.getname().size() == 0)
sqlb::TablePtr obj = db.getObjectByName(table).dynamicCast<sqlb::Table>();
if(!obj)
return;
// If no column name is set, assume the primary key is meant
if(!column.size())
column = obj.table.fields().at(obj.table.findPk())->name();
column = obj->fields().at(obj->findPk())->name();
// If column doesn't exist don't do anything
int column_index = obj.table.findField(column);
int column_index = obj->findField(column);
if(column_index == -1)
return;
@@ -2232,7 +2232,7 @@ void MainWindow::showDataColumnPopupMenu(const QPoint& pos)
void MainWindow::showRecordPopupMenu(const QPoint& pos)
{
const QString sCurrentTable = ui->comboBrowseTable->currentText();
if (!(db.getObjectByName(sCurrentTable).gettype() == "table" && !db.readOnly()))
if(!(db.getObjectByName(sCurrentTable)->type() == sqlb::Object::ObjectTypes::Table && !db.readOnly()))
return;
int row = ui->dataTable->verticalHeader()->logicalIndexAt(pos);
@@ -2257,7 +2257,7 @@ void MainWindow::editDataColumnDisplayFormat()
// column is always the rowid column. Ultimately, get the column name from the column object
QString current_table = ui->comboBrowseTable->currentText();
int field_number = sender()->property("clicked_column").toInt();
QString field_name = db.getObjectByName(current_table).table.fields().at(field_number-1)->name();
QString field_name = db.getObjectByName(current_table).dynamicCast<sqlb::Table>()->fields().at(field_number-1)->name();
// Get the current display format of the field
QString current_displayformat = browseTableSettings[current_table].displayFormats[field_number];

View File

@@ -96,14 +96,12 @@ void SqlExecutionArea::saveAsView()
{
name = QInputDialog::getText(this, qApp->applicationName(), tr("Please specify the view name")).trimmed();
if(name.isEmpty())
break;
if(!db.getObjectByName(name).getname().isEmpty())
return;
if(db.getObjectByName(name) != nullptr)
QMessageBox::warning(this, qApp->applicationName(), tr("There is already an object with that name. Please choose a different name."));
else
break;
}
if(name.isEmpty())
return;
// Create the view
if(db.executeSQL(QString("CREATE VIEW %1 AS %2;").arg(sqlb::escapeIdentifier(name)).arg(model->query())))

View File

@@ -503,7 +503,7 @@ bool DBBrowserDB::dump(const QString& filename,
continue;
// get columns
QStringList cols(it->table.fieldNames());
QStringList cols(it->object.dynamicCast<sqlb::Table>()->fieldNames());
QString sQuery = QString("SELECT * FROM %1;").arg(sqlb::escapeIdentifier(it->getTableName()));
QByteArray utf8Query = sQuery.toUtf8();
@@ -522,7 +522,7 @@ bool DBBrowserDB::dump(const QString& filename,
if (!insertNewSyntx || !counter)
{
stream << "INSERT INTO " << sqlb::escapeIdentifier(it->getTableName());
stream << "INSERT INTO " << sqlb::escapeIdentifier(it->getname());
if (insertColNames)
stream << " (" << cols.join(",") << ")";
stream << " VALUES (";
@@ -594,7 +594,7 @@ bool DBBrowserDB::dump(const QString& filename,
for(auto it=objMap.constBegin();it!=objMap.constEnd();++it)
{
// Make sure it's not a table again
if(it.value().gettype() == "table")
if(it.value().gettype() == sqlb::Object::ObjectTypes::Table)
continue;
// Write the SQL string used to create this object to the output file
@@ -732,7 +732,7 @@ bool DBBrowserDB::getRow(const QString& sTableName, const QString& rowid, QList<
{
QString sQuery = QString("SELECT * FROM %1 WHERE %2='%3';")
.arg(sqlb::escapeIdentifier(sTableName))
.arg(sqlb::escapeIdentifier(getObjectByName(sTableName).table.rowidColumn()))
.arg(sqlb::escapeIdentifier(getObjectByName(sTableName).dynamicCast<sqlb::Table>()->rowidColumn()))
.arg(rowid);
QByteArray utf8Query = sQuery.toUtf8();
@@ -853,18 +853,18 @@ QString DBBrowserDB::addRecord(const QString& sTableName)
{
if (!isOpen()) return QString();
sqlb::Table table = getObjectByName(sTableName).table;
sqlb::TablePtr table = getObjectByName(sTableName).dynamicCast<sqlb::Table>();
// For tables without rowid we have to set the primary key by ourselves. We do so by querying for the largest value in the PK column
// and adding one to it.
QString sInsertstmt;
QString pk_value;
if(table.isWithoutRowidTable())
if(table->isWithoutRowidTable())
{
pk_value = QString::number(max(table, table.fields().at(table.findField(table.rowidColumn()))).toLongLong() + 1);
sInsertstmt = emptyInsertStmt(table, pk_value);
pk_value = QString::number(max(*table, table->fields().at(table->findField(table->rowidColumn()))).toLongLong() + 1);
sInsertstmt = emptyInsertStmt(*table, pk_value);
} else {
sInsertstmt = emptyInsertStmt(table);
sInsertstmt = emptyInsertStmt(*table);
}
if(!executeSQL(sInsertstmt))
@@ -872,7 +872,7 @@ QString DBBrowserDB::addRecord(const QString& sTableName)
qWarning() << "addRecord: " << lastErrorMessage;
return QString();
} else {
if(table.isWithoutRowidTable())
if(table->isWithoutRowidTable())
return pk_value;
else
return QString::number(sqlite3_last_insert_rowid(_db));
@@ -891,7 +891,7 @@ bool DBBrowserDB::deleteRecords(const QString& table, const QStringList& rowids)
}
QString statement = QString("DELETE FROM %1 WHERE %2 IN (%3);")
.arg(sqlb::escapeIdentifier(table))
.arg(sqlb::escapeIdentifier(getObjectByName(table).table.rowidColumn()))
.arg(sqlb::escapeIdentifier(getObjectByName(table).dynamicCast<sqlb::Table>()->rowidColumn()))
.arg(quoted_rowids.join(", "));
if(executeSQL(statement))
ok = true;
@@ -908,7 +908,7 @@ bool DBBrowserDB::updateRecord(const QString& table, const QString& column, cons
QString sql = QString("UPDATE %1 SET %2=? WHERE %3='%4';")
.arg(sqlb::escapeIdentifier(table))
.arg(sqlb::escapeIdentifier(column))
.arg(sqlb::escapeIdentifier(getObjectByName(table).table.rowidColumn()))
.arg(sqlb::escapeIdentifier(getObjectByName(table).dynamicCast<sqlb::Table>()->rowidColumn()))
.arg(rowid);
logSQL(sql, kLogMsg_App);
@@ -994,19 +994,11 @@ bool DBBrowserDB::renameColumn(const QString& tablename, const sqlb::Table& tabl
// 2) Include the addColumn() use case in here, so the calling side doesn't need to know anything about how this class handles table modifications.
// 3) Maybe rename this function to alterTable() or something
// Collect information on the current DB layout
QString tableSql = getObjectByName(tablename).getsql();
if(tableSql.isEmpty())
{
lastErrorMessage = tr("renameColumn: cannot find table %1.").arg(tablename);
return false;
}
// Create table schema
sqlb::Table oldSchema = sqlb::Table::parseSQL(tableSql).first;
const sqlb::TablePtr oldSchema = getObjectByName(tablename).dynamicCast<sqlb::Table>();
// Check if field actually exists
if(!name.isNull() && oldSchema.findField(name) == -1)
if(!name.isNull() && oldSchema->findField(name) == -1)
{
lastErrorMessage = tr("renameColumn: cannot find column %1.").arg(name);
return false;
@@ -1022,7 +1014,7 @@ bool DBBrowserDB::renameColumn(const QString& tablename, const sqlb::Table& tabl
// Create a new table with a name that hopefully doesn't exist yet.
// Its layout is exactly the same as the one of the table to change - except for the column to change
// of course, and the table constraints which are copied from the table parameter.
sqlb::Table newSchema = oldSchema;
sqlb::Table newSchema = *oldSchema;
newSchema.setName("sqlitebrowser_rename_column_new_table");
newSchema.setConstraints(table.allConstraints());
newSchema.setRowidColumn(table.rowidColumn());
@@ -1084,19 +1076,19 @@ bool DBBrowserDB::renameColumn(const QString& tablename, const sqlb::Table& tabl
for(auto it=objMap.constBegin();it!=objMap.constEnd();++it)
{
// If this object references the table and it's not the table itself save it's SQL string
if((*it).getTableName() == tablename && (*it).gettype() != "table")
if((*it).getTableName() == tablename && (*it).gettype() != sqlb::Object::ObjectTypes::Table)
{
// If this is an index, update the fields first. This highly increases the chance that the SQL statement won't throw an
// error later on when we try to recreate it.
if((*it).gettype() == "index")
if((*it).gettype() == sqlb::Object::ObjectTypes::Index)
{
sqlb::Index idx = (*it).index;
for(int i=0;i<idx.columns().size();i++)
sqlb::IndexPtr idx = (*it).object.dynamicCast<sqlb::Index>();
for(int i=0;i<idx->columns().size();i++)
{
if(idx.column(i)->name() == name)
idx.column(i)->setName(to->name());
if(idx->column(i)->name() == name)
idx->column(i)->setName(to->name());
}
otherObjectsSql << idx.sql();
otherObjectsSql << idx->sql();
} 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
// hope for the best.
@@ -1187,14 +1179,14 @@ objectMap DBBrowserDB::getBrowsableObjects() const
return res;
}
DBBrowserObject DBBrowserDB::getObjectByName(const QString& name) const
sqlb::ObjectPtr DBBrowserDB::getObjectByName(const QString& name) const
{
for(auto it=objMap.constBegin();it!=objMap.constEnd();++it)
{
if((*it).getname() == name)
return *it;
return it->object;
}
return DBBrowserObject();
return sqlb::ObjectPtr(nullptr);
}
void DBBrowserDB::logSQL(QString statement, int msgtype)
@@ -1237,6 +1229,7 @@ void DBBrowserDB::updateSchema( )
QByteArray utf8Statement = statement.toUtf8();
err=sqlite3_prepare_v2(_db, utf8Statement, utf8Statement.length(),
&vm, &tail);
QVector<QString> temporary_objects;
if (err == SQLITE_OK){
logSQL(statement, kLogMsg_App);
while ( sqlite3_step(vm) == SQLITE_ROW ){
@@ -1247,32 +1240,45 @@ void DBBrowserDB::updateSchema( )
QString val_temp = QString::fromUtf8((const char*)sqlite3_column_text(vm, 4));
val_sql.replace("\r", "");
if(val_type == "table" || val_type == "index" || val_type == "view" || val_type == "trigger")
objMap.insert(val_type, DBBrowserObject(val_name, val_sql, val_type, val_tblname, (val_temp == "1")));
sqlb::Object::ObjectTypes type;
if(val_type == "table")
type = sqlb::Object::ObjectTypes::Table;
else if(val_type == "index")
type = sqlb::Object::ObjectTypes::Index;
else if(val_type == "trigger")
type = sqlb::Object::ObjectTypes::Trigger;
else if(val_type == "view")
type = sqlb::Object::ObjectTypes::View;
else
qWarning() << tr("unknown object type %1").arg(val_type);
continue;
DBBrowserObject obj(val_name, val_sql, type, val_tblname);
if((type == sqlb::Object::ObjectTypes::Table || type == sqlb::Object::ObjectTypes::Index) && !val_sql.isEmpty())
{
obj.object = sqlb::Object::parseSQL(type, val_sql).first;
if(val_temp == "1")
obj.object->setTemporary(true);
}
objMap.insert(val_type, obj);
}
sqlite3_finalize(vm);
}else{
qWarning() << tr("could not get list of db objects: %1, %2").arg(err).arg(sqlite3_errmsg(_db));
}
//now get the field list for each table
//now get the field list for views
objectMap::Iterator it;
for ( it = objMap.begin(); it != objMap.end(); ++it )
{
// Use our SQL parser to generate the field list for tables. For views we currently have to fall back to the
// pragma SQLite offers.
if((*it).gettype() == "table")
if((*it).gettype() == sqlb::Object::ObjectTypes::View)
{
(*it).table = sqlb::Table::parseSQL((*it).getsql()).first;
} else if((*it).gettype() == "index" && !(*it).getsql().isEmpty()) {
(*it).index = sqlb::Index::parseSQL((*it).getsql()).first;
} else if((*it).gettype() == "view") {
statement = QString("PRAGMA TABLE_INFO(%1);").arg(sqlb::escapeIdentifier((*it).getname()));
logSQL(statement, kLogMsg_App);
err=sqlite3_prepare_v2(_db,statement.toUtf8(),statement.length(),
&vm, &tail);
sqlb::Table* view_dummy = new sqlb::Table("");
if (err == SQLITE_OK){
while ( sqlite3_step(vm) == SQLITE_ROW ){
if (sqlite3_column_count(vm)==6)
@@ -1281,13 +1287,14 @@ void DBBrowserDB::updateSchema( )
QString val_type = QString::fromUtf8((const char *)sqlite3_column_text(vm, 2));
sqlb::FieldPtr f(new sqlb::Field(val_name, val_type));
(*it).table.addField(f);
view_dummy->addField(f);
}
}
sqlite3_finalize(vm);
} else{
lastErrorMessage = tr("could not get types");
}
(*it).object = sqlb::TablePtr(view_dummy);
}
}

View File

@@ -21,25 +21,22 @@ typedef QMultiMap<QString, class DBBrowserObject> objectMap;
class DBBrowserObject
{
public:
DBBrowserObject() : table(""), index(""), name( "" ) { }
DBBrowserObject(const QString& wname, const QString& wsql, const QString& wtype, const QString& tbl_name, bool temp)
: table(wname), index(wname), name( wname), sql( wsql ), type(wtype), table_name(tbl_name), temporary(temp)
DBBrowserObject() : name( "" ) { }
DBBrowserObject(const QString& wname, const QString& wsql, sqlb::Object::ObjectTypes wtype, const QString& tbl_name)
: name( wname), sql( wsql ), type(wtype), table_name(tbl_name)
{ }
QString getname() const { return name; }
QString getsql() const { return sql; }
QString gettype() const { return type; }
sqlb::Object::ObjectTypes gettype() const { return type; }
QString getTableName() const { return table_name; }
bool isTemporary() const { return temporary; }
sqlb::Table table;
sqlb::Index index;
sqlb::ObjectPtr object;
private:
QString name;
QString sql;
QString type;
sqlb::Object::ObjectTypes type;
QString table_name; // The name of the table this object references, interesting for views, triggers and indices
bool temporary;
};
class DBBrowserDB : public QObject
@@ -109,7 +106,7 @@ public:
bool renameColumn(const QString& tablename, const sqlb::Table& table, const QString& name, sqlb::FieldPtr to, int move = 0);
objectMap getBrowsableObjects() const;
DBBrowserObject getObjectByName(const QString& name) const;
sqlb::ObjectPtr getObjectByName(const QString& name) const;
bool isOpen() const;
bool encrypted() const { return isEncrypted; }
bool readOnly() const { return isReadOnly; }

View File

@@ -47,13 +47,13 @@ void SqliteTableModel::setTable(const QString& table, const QVector<QString>& di
m_vDataTypes.push_back(SQLITE_INTEGER);
bool allOk = false;
if(m_db.getObjectByName(table).gettype() == "table")
if(m_db.getObjectByName(table)->type() == sqlb::Object::ObjectTypes::Table)
{
sqlb::Table t = sqlb::Table::parseSQL(m_db.getObjectByName(table).getsql()).first;
if(t.fields().size()) // parsing was OK
sqlb::TablePtr t = sqlb::Table::parseSQL(m_db.getObjectByName(table)->originalSql()).first.dynamicCast<sqlb::Table>();
if(t->fields().size()) // parsing was OK
{
m_headers.push_back(t.rowidColumn());
m_headers.append(t.fieldNames());
m_headers.push_back(t->rowidColumn());
m_headers.append(t->fieldNames());
// parse columns types
static QStringList dataTypes = QStringList()
@@ -61,7 +61,7 @@ void SqliteTableModel::setTable(const QString& table, const QVector<QString>& di
<< "REAL"
<< "TEXT"
<< "BLOB";
foreach(const sqlb::FieldPtr fld, t.fields())
foreach(const sqlb::FieldPtr fld, t->fields())
{
QString name(fld->type().toUpper());
int colType = dataTypes.indexOf(name);
@@ -267,13 +267,13 @@ QVariant SqliteTableModel::data(const QModelIndex &index, int role) const
sqlb::ForeignKeyClause SqliteTableModel::getForeignKeyClause(int column) const
{
DBBrowserObject obj = m_db.getObjectByName(m_sTable);
if(obj.getname().size() && (column >= 0 && column < obj.table.fields().count()))
sqlb::TablePtr obj = m_db.getObjectByName(m_sTable).dynamicCast<sqlb::Table>();
if(obj->name().size() && (column >= 0 && column < obj->fields().count()))
{
// 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 = obj.table.constraint({obj.table.fields().at(column)}, sqlb::Constraint::ForeignKeyConstraintType);
sqlb::ConstraintPtr ptr = obj->constraint({obj->fields().at(column)}, sqlb::Constraint::ForeignKeyConstraintType);
if(ptr)
return *(ptr.dynamicCast<sqlb::ForeignKeyClause>());
}
@@ -432,11 +432,11 @@ QModelIndex SqliteTableModel::dittoRecord(int old_row)
int firstEditedColumn = 0;
int new_row = rowCount() - 1;
sqlb::Table t = sqlb::Table::parseSQL(m_db.getObjectByName(m_sTable).getsql()).first;
sqlb::TablePtr t = sqlb::Table::parseSQL(m_db.getObjectByName(m_sTable)->originalSql()).first.dynamicCast<sqlb::Table>();
sqlb::FieldVector pk = t.primaryKey();
for (int col = 0; col < t.fields().size(); ++col) {
if(!pk.contains(t.fields().at(col))) {
sqlb::FieldVector pk = t->primaryKey();
for (int col = 0; col < t->fields().size(); ++col) {
if(!pk.contains(t->fields().at(col))) {
if (!firstEditedColumn)
firstEditedColumn = col + 1;

View File

@@ -22,6 +22,27 @@ QStringList fieldVectorToFieldNames(const FieldVector& vector)
return result;
}
QPair<ObjectPtr, bool> Object::parseSQL(Object::ObjectTypes type, const QString& sSQL)
{
// Parse SQL statement according to type
QPair<ObjectPtr, bool> result;
switch(type)
{
case Object::ObjectTypes::Table:
result = Table::parseSQL(sSQL);
break;
case Object::ObjectTypes::Index:
result = Index::parseSQL(sSQL);
break;
default:
return QPair<ObjectPtr, bool>(ObjectPtr(nullptr), false);
}
// Strore the original SQL statement and return the result
result.first->setOriginalSql(sSQL);
return result;
}
bool ForeignKeyClause::isSet() const
{
return m_override.size() || m_table.size();
@@ -256,7 +277,7 @@ bool Table::hasAutoIncrement() const
return false;
}
QPair<Table, bool> Table::parseSQL(const QString &sSQL)
QPair<ObjectPtr, bool> Table::parseSQL(const QString &sSQL)
{
std::stringstream s;
s << sSQL.toStdString();
@@ -276,7 +297,7 @@ QPair<Table, bool> Table::parseSQL(const QString &sSQL)
// Note: this needs to be done in two separate lines because otherwise the optimiser might decide to
// fetch the value for the second part of the pair (the modify supported flag) first. If it does so it will
// always be set to true because the table() method hasn't run yet and it's only set to false in there.
sqlb::Table tab = ctw.table();
sqlb::TablePtr tab = ctw.table();
return qMakePair(tab, ctw.modifysupported());
}
catch(antlr::ANTLRException& ex)
@@ -288,7 +309,7 @@ QPair<Table, bool> Table::parseSQL(const QString &sSQL)
qCritical() << "Sqlite parse error: " << sSQL; //TODO
}
return qMakePair(Table(""), false);
return qMakePair(TablePtr(new Table("")), false);
}
QString Table::sql() const
@@ -463,9 +484,9 @@ QString columnname(const antlr::RefAST& n)
}
}
Table CreateTableWalker::table()
TablePtr CreateTableWalker::table()
{
Table tab("");
Table* tab = new Table("");
if( m_root ) //CREATE TABLE
{
@@ -491,7 +512,7 @@ Table CreateTableWalker::table()
}
// Extract and set table name
tab.setName(tablename(s));
tab->setName(tablename(s));
// Special handling for virtual tables. If this is a virtual table, extract the USING part and skip all the
// rest of this function because virtual tables don't have column definitons
@@ -499,12 +520,12 @@ Table CreateTableWalker::table()
{
s = s->getNextSibling(); // USING
s = s->getNextSibling(); // module name
tab.setVirtualUsing(concatTextAST(s, true));
tab->setVirtualUsing(concatTextAST(s, true));
m_bModifySupported = false;
// TODO Maybe get the column list using the 'pragma table_info()' approach we're using for views
return tab;
return TablePtr(tab);
}
// This is a normal table, not a virtual one
@@ -555,7 +576,7 @@ Table CreateTableWalker::table()
do
{
QString col = columnname(tc);
FieldPtr field = tab.field(tab.findField(col));
FieldPtr field = tab->field(tab->findField(col));
fields.push_back(field);
tc = tc->getNextSibling();
@@ -579,7 +600,7 @@ Table CreateTableWalker::table()
}
} while(tc != antlr::nullAST && tc->getType() != sqlite3TokenTypes::RPAREN);
tab.addConstraint(fields, ConstraintPtr(pk));
tab->addConstraint(fields, ConstraintPtr(pk));
}
break;
case sqlite3TokenTypes::UNIQUE:
@@ -593,7 +614,7 @@ Table CreateTableWalker::table()
do
{
QString col = columnname(tc);
FieldPtr field = tab.field(tab.findField(col));
FieldPtr field = tab->field(tab->findField(col));
fields.push_back(field);
tc = tc->getNextSibling();
@@ -617,7 +638,7 @@ Table CreateTableWalker::table()
fields[0]->setUnique(true);
delete unique;
} else {
tab.addConstraint(fields, ConstraintPtr(unique));
tab->addConstraint(fields, ConstraintPtr(unique));
}
}
break;
@@ -634,7 +655,7 @@ Table CreateTableWalker::table()
do
{
QString col = columnname(tc);
FieldPtr field = tab.field(tab.findField(col));
FieldPtr field = tab->field(tab->findField(col));
fields.push_back(field);
tc = tc->getNextSibling();
@@ -666,7 +687,7 @@ Table CreateTableWalker::table()
}
fk->setConstraint(concatTextAST(tc, true));
tab.addConstraint(fields, ConstraintPtr(fk));
tab->addConstraint(fields, ConstraintPtr(fk));
}
break;
default:
@@ -685,15 +706,15 @@ Table CreateTableWalker::table()
s = s->getNextSibling(); // WITHOUT
s = s->getNextSibling(); // ROWID
tab.setRowidColumn(tab.fields().at(tab.findPk())->name());
tab->setRowidColumn(tab->fields().at(tab->findPk())->name());
}
}
}
return tab;
return TablePtr(tab);
}
void CreateTableWalker::parsecolumn(Table& table, antlr::RefAST c)
void CreateTableWalker::parsecolumn(Table* table, antlr::RefAST c)
{
QString colname;
QString type = "TEXT";
@@ -832,17 +853,17 @@ void CreateTableWalker::parsecolumn(Table& table, antlr::RefAST c)
FieldPtr f = FieldPtr(new Field(colname, type, notnull, defaultvalue, check, unique));
f->setAutoIncrement(autoincrement);
table.addField(f);
table->addField(f);
if(foreignKey)
table.addConstraint({f}, ConstraintPtr(foreignKey));
table->addConstraint({f}, ConstraintPtr(foreignKey));
if(primaryKey)
{
FieldVector v;
if(table.constraint(v, Constraint::PrimaryKeyConstraintType))
table.primaryKeyRef().push_back(f);
if(table->constraint(v, Constraint::PrimaryKeyConstraintType))
table->primaryKeyRef().push_back(f);
else
table.addConstraint({f}, ConstraintPtr(primaryKey));
table->addConstraint({f}, ConstraintPtr(primaryKey));
}
}
@@ -923,7 +944,7 @@ QString Index::sql() const
return sql + ";";
}
QPair<Index, bool> Index::parseSQL(const QString& sSQL)
QPair<ObjectPtr, bool> Index::parseSQL(const QString& sSQL)
{
std::stringstream s;
s << sSQL.toStdString();
@@ -943,7 +964,7 @@ QPair<Index, bool> Index::parseSQL(const QString& sSQL)
// Note: this needs to be done in two separate lines because otherwise the optimiser might decide to
// fetch the value for the second part of the pair (the modify supported flag) first. If it does so it will
// always be set to true because the table() method hasn't run yet and it's only set to false in there.
sqlb::Index index = ctw.index();
sqlb::IndexPtr index = ctw.index();
return qMakePair(index, ctw.modifysupported());
}
catch(antlr::ANTLRException& ex)
@@ -955,12 +976,12 @@ QPair<Index, bool> Index::parseSQL(const QString& sSQL)
qCritical() << "Sqlite parse error: " << sSQL; //TODO
}
return qMakePair(Index(""), false);
return qMakePair(IndexPtr(new Index("")), false);
}
Index CreateIndexWalker::index()
IndexPtr CreateIndexWalker::index()
{
Index index("");
Index* index = new Index("");
if(m_root) // CREATE INDEX
{
@@ -975,18 +996,18 @@ Index CreateIndexWalker::index()
{
// Is this a unique index?
if(s->getType() == Sqlite3Lexer::UNIQUE)
index.setUnique(true);
index->setUnique(true);
s = s->getNextSibling();
}
// Extract and set index name
index.setName(tablename(s));
index->setName(tablename(s));
// Get table name
s = s->getNextSibling(); // ON
s = s->getNextSibling(); // table name
index.setTable(tablename(s));
index->setTable(tablename(s));
s = s->getNextSibling(); // LPAREN
s = s->getNextSibling(); // first column name
@@ -1012,15 +1033,15 @@ Index CreateIndexWalker::index()
m_bModifySupported = false;
} else {
s = s->getNextSibling(); // expr
index.setWhereExpr(concatTextAST(s, true));
index->setWhereExpr(concatTextAST(s, true));
}
}
}
return index;
return IndexPtr(index);
}
void CreateIndexWalker::parsecolumn(Index& index, antlr::RefAST c)
void CreateIndexWalker::parsecolumn(Index* index, antlr::RefAST c)
{
QString name;
bool isExpression;
@@ -1074,7 +1095,7 @@ void CreateIndexWalker::parsecolumn(Index& index, antlr::RefAST c)
c = c->getNextSibling();
}
index.addColumn(IndexedColumnPtr(new IndexedColumn(name, isExpression, order)));
index->addColumn(IndexedColumnPtr(new IndexedColumn(name, isExpression, order)));
}
} //namespace sqlb

View File

@@ -15,15 +15,71 @@ namespace sqlb {
QString escapeIdentifier(QString id);
class Object;
class Table;
class Index;
class Field;
class Constraint;
class IndexedColumn;
typedef QSharedPointer<Object> ObjectPtr;
typedef QSharedPointer<Table> TablePtr;
typedef QSharedPointer<Index> IndexPtr;
typedef QSharedPointer<Field> FieldPtr;
typedef QSharedPointer<Constraint> ConstraintPtr;
typedef QVector<FieldPtr> FieldVector;
typedef QSharedPointer<IndexedColumn> IndexedColumnPtr;
typedef QVector<IndexedColumnPtr> IndexedColumnVector;
class Object
{
public:
enum ObjectTypes
{
Table,
Index,
View,
Trigger
};
explicit Object(const QString& name): m_name(name), m_temporary(false) {}
virtual ~Object() {}
virtual ObjectTypes type() const = 0;
void setName(const QString& name) { m_name = name; }
const QString& name() const { return m_name; }
void setOriginalSql(const QString& original_sql) { m_originalSql = original_sql; }
QString originalSql() const { return m_originalSql; }
void setTemporary(bool temp) { m_temporary = temp; }
bool isTemporary() const { return m_temporary; }
virtual QString baseTable() const { return QString(); }
virtual void clear() {}
/**
* @brief Returns the CREATE statement for this object
* @return A QString with the CREATE statement.
*/
virtual QString sql() const = 0;
/**
* @brief parseSQL Parses the CREATE statement in sSQL.
* @param sSQL The type of the object.
* @param sSQL The create statement.
* @return A pair. The first part is the parsed database object, the second a bool indicating if our
* parser fully understood the statement. The object may be empty if parsing failed.
*/
static QPair<ObjectPtr, bool> parseSQL(Object::ObjectTypes type, const QString& sSQL);
protected:
QString m_name;
QString m_originalSql;
bool m_temporary;
};
class Constraint
{
public:
@@ -160,15 +216,14 @@ private:
typedef QMultiHash<FieldVector, ConstraintPtr> ConstraintMap;
class Table
class Table : public Object
{
public:
explicit Table(const QString& name): m_name(name), m_rowidColumn("_rowid_"), m_temporary(false) {}
explicit Table(const QString& name): Object(name), m_rowidColumn("_rowid_") {}
virtual ~Table();
void setName(const QString& name) { m_name = name; }
virtual ObjectTypes type() const { return Object::Table; }
const QString& name() const { return m_name; }
const FieldVector& fields() const { return m_fields; }
/**
@@ -192,9 +247,6 @@ public:
QString virtualUsing() const { return m_virtual; }
bool isVirtual() const { return !m_virtual.isEmpty(); }
void setTemporary(bool temp) { m_temporary = temp; }
bool isTemporary() const { return m_temporary; }
void clear();
void addConstraint(FieldVector fields, ConstraintPtr constraint);
@@ -224,18 +276,16 @@ public:
* if our modify dialog supports the table.
* The table object may be a empty table if parsing failed.
*/
static QPair<Table, bool> parseSQL(const QString& sSQL);
static QPair<ObjectPtr, bool> parseSQL(const QString& sSQL);
private:
QStringList fieldList() const;
bool hasAutoIncrement() const;
private:
QString m_name;
FieldVector m_fields;
QString m_rowidColumn;
ConstraintMap m_constraints;
QString m_virtual;
bool m_temporary;
};
/**
@@ -251,11 +301,11 @@ public:
, m_bModifySupported(true)
{}
Table table();
TablePtr table();
bool modifysupported() const { return m_bModifySupported; }
private:
void parsecolumn(Table& table, antlr::RefAST c);
void parsecolumn(Table* table, antlr::RefAST c);
private:
antlr::RefAST m_root;
@@ -291,14 +341,15 @@ private:
QString m_order;
};
class Index
class Index : public Object
{
public:
explicit Index(const QString& name): m_name(name), m_unique(false) {}
explicit Index(const QString& name): Object(name), m_unique(false) {}
virtual ~Index();
void setName(const QString& name) { m_name = name; }
const QString& name() const { return m_name; }
virtual ObjectTypes type() const { return Object::Index; }
virtual QString baseTable() const { return m_table; }
void setUnique(bool unique) { m_unique = unique; }
bool unique() const { return m_unique; }
@@ -334,10 +385,9 @@ public:
* if our parser fully understood the statement.
* The index object may be if parsing failed.
*/
static QPair<Index, bool> parseSQL(const QString& sSQL);
static QPair<ObjectPtr, bool> parseSQL(const QString& sSQL);
private:
QString m_name;
bool m_unique;
QString m_table;
QString m_whereExpr;
@@ -357,11 +407,11 @@ public:
, m_bModifySupported(true)
{}
Index index();
IndexPtr index();
bool modifysupported() const { return m_bModifySupported; }
private:
void parsecolumn(Index& index, antlr::RefAST c);
void parsecolumn(Index* index, antlr::RefAST c);
private:
antlr::RefAST m_root;