mirror of
https://github.com/sqlitebrowser/sqlitebrowser.git
synced 2026-01-17 01:09:36 -06:00
Refactor the way we store a database schema
This commit does a lot of refactoring. But most noticeably it changes
two things:
1) Instead of saving all objects (tables, views, indices, triggers) of a
schema in a common map, we now store tables/views, indices and
triggers in three separate maps. This has a number of benefits:
- It resembles more closely how SQLite stores its data internally and
therefore achieves greater compatability e.g. for databases with a
view and a trigger with the same name.
- It reduces the need for runtime polymorphism. This makes the code
run a bit faster.
- By working with explicit types more often more error checking can
be done at compile time, making the code less error prone.
- The code becomes a lot clearer to read.
2) By making View inherit form Table, views are now a sort of tables.
This has the following benefits:
- This is a again how SQLite stores views internally which again
should increase compatibility a bit.
- We mostly treat views and tables the same anyway and with these
changes we can unify the code for them even more.
This commit is contained in:
@@ -189,25 +189,22 @@ void AddRecordDialog::populateFields()
|
||||
bool auto_increment = false;
|
||||
|
||||
// Initialize fields, fks and pk differently depending on whether it's a table or a view.
|
||||
const sqlb::ObjectPtr obj = pdb.getObjectByName(curTable);
|
||||
if (obj->type() == sqlb::Object::Table)
|
||||
const sqlb::TablePtr table = pdb.getTableByName(curTable);
|
||||
fields = table->fields;
|
||||
if (!table->isView())
|
||||
{
|
||||
sqlb::TablePtr m_table = pdb.getObjectByName<sqlb::Table>(curTable);
|
||||
fields = m_table->fields;
|
||||
|
||||
std::transform(fields.begin(), fields.end(), std::back_inserter(fks), [m_table](const auto& f) {
|
||||
return m_table->constraint({f.name()}, sqlb::Constraint::ForeignKeyConstraintType);
|
||||
std::transform(fields.begin(), fields.end(), std::back_inserter(fks), [table](const auto& f) {
|
||||
return table->constraint({f.name()}, sqlb::Constraint::ForeignKeyConstraintType);
|
||||
});
|
||||
|
||||
const auto pk_constraint = m_table->primaryKey();
|
||||
const auto pk_constraint = table->primaryKey();
|
||||
if(pk_constraint)
|
||||
{
|
||||
pk = pk_constraint->columnList();
|
||||
auto_increment = pk_constraint->autoIncrement();
|
||||
}
|
||||
} else {
|
||||
sqlb::ViewPtr m_view = pdb.getObjectByName<sqlb::View>(curTable);
|
||||
fields = m_view->fields;
|
||||
// It's a view
|
||||
fks.resize(fields.size(), sqlb::ConstraintPtr(nullptr));
|
||||
pk = pseudo_pk;
|
||||
}
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
#include <QMimeData>
|
||||
#include <QMessageBox>
|
||||
#include <QApplication>
|
||||
#include <unordered_map>
|
||||
#include <map>
|
||||
|
||||
DbStructureModel::DbStructureModel(DBBrowserDB& db, QObject* parent)
|
||||
: QAbstractItemModel(parent),
|
||||
@@ -304,108 +302,78 @@ bool DbStructureModel::dropMimeData(const QMimeData* data, Qt::DropAction action
|
||||
}
|
||||
}
|
||||
|
||||
static long calc_number_of_objects_by_type(const objectMap& objmap, const std::string& type)
|
||||
{
|
||||
auto objects = objmap.equal_range(type);
|
||||
if(objects.first == objmap.end())
|
||||
return 0;
|
||||
else
|
||||
return std::distance(objects.first, objects.second);
|
||||
}
|
||||
|
||||
void DbStructureModel::buildTree(QTreeWidgetItem* parent, const std::string& schema)
|
||||
{
|
||||
// Build a map from object type to tree node to simplify finding the correct tree node later
|
||||
std::unordered_map<std::string, QTreeWidgetItem*> typeToParentItem;
|
||||
|
||||
// Get object map for the given schema
|
||||
objectMap objmap = m_db.schemata[schema];
|
||||
const objectMap& objmap = m_db.schemata.at(schema);
|
||||
|
||||
// Prepare tree
|
||||
auto num_tables = std::count_if(objmap.tables.begin(), objmap.tables.end(), [](const auto& t) { return !t.second->isView(); });
|
||||
QTreeWidgetItem* itemTables = new QTreeWidgetItem(parent);
|
||||
itemTables->setIcon(ColumnName, IconCache::get("table"));
|
||||
itemTables->setText(ColumnName, tr("Tables (%1)").arg(calc_number_of_objects_by_type(objmap, "table")));
|
||||
typeToParentItem.insert({"table", itemTables});
|
||||
itemTables->setText(ColumnName, tr("Tables (%1)").arg(num_tables));
|
||||
|
||||
QTreeWidgetItem* itemIndices = new QTreeWidgetItem(parent);
|
||||
itemIndices->setIcon(ColumnName, IconCache::get("index"));
|
||||
itemIndices->setText(ColumnName, tr("Indices (%1)").arg(calc_number_of_objects_by_type(objmap, "index")));
|
||||
typeToParentItem.insert({"index", itemIndices});
|
||||
itemIndices->setText(ColumnName, tr("Indices (%1)").arg(objmap.indices.size()));
|
||||
|
||||
auto num_views = std::count_if(objmap.tables.begin(), objmap.tables.end(), [](const auto& t) { return t.second->isView(); });
|
||||
QTreeWidgetItem* itemViews = new QTreeWidgetItem(parent);
|
||||
itemViews->setIcon(ColumnName, IconCache::get("view"));
|
||||
itemViews->setText(ColumnName, tr("Views (%1)").arg(calc_number_of_objects_by_type(objmap, "view")));
|
||||
typeToParentItem.insert({"view", itemViews});
|
||||
itemViews->setText(ColumnName, tr("Views (%1)").arg(num_views));
|
||||
|
||||
QTreeWidgetItem* itemTriggers = new QTreeWidgetItem(parent);
|
||||
itemTriggers->setIcon(ColumnName, IconCache::get("trigger"));
|
||||
itemTriggers->setText(ColumnName, tr("Triggers (%1)").arg(calc_number_of_objects_by_type(objmap, "trigger")));
|
||||
typeToParentItem.insert({"trigger", itemTriggers});
|
||||
itemTriggers->setText(ColumnName, tr("Triggers (%1)").arg(objmap.triggers.size()));
|
||||
|
||||
// Get all database objects and sort them by their name.
|
||||
// This needs to be a multimap because SQLite allows views and triggers with the same name which means that names can appear twice.
|
||||
std::multimap<std::string, sqlb::ObjectPtr> dbobjs;
|
||||
for(const auto& it : objmap)
|
||||
dbobjs.insert({it.second->name(), it.second});
|
||||
|
||||
// Add the database objects to the tree nodes
|
||||
for(const auto& obj : dbobjs)
|
||||
// Add tables and views
|
||||
for(const auto& obj : objmap.tables)
|
||||
{
|
||||
sqlb::ObjectPtr it = obj.second;
|
||||
QTreeWidgetItem* item = addNode(schema, obj.second->name(), obj.second->isView() ? "view" : "table", obj.second->originalSql(), obj.second->isView() ? itemViews : itemTables);
|
||||
|
||||
// Object node
|
||||
QTreeWidgetItem* item = addNode(typeToParentItem.at(sqlb::Object::typeToString(it->type())), it, schema);
|
||||
// Add an extra node for the browsable section
|
||||
addNode(schema, obj.second->name(), obj.second->isView() ? "view" : "table", obj.second->originalSql(), browsablesRootItem);
|
||||
|
||||
// If it is a table or view add the field nodes, add an extra node for the browsable section
|
||||
if(it->type() == sqlb::Object::Types::Table || it->type() == sqlb::Object::Types::View)
|
||||
addNode(browsablesRootItem, it, schema);
|
||||
|
||||
// Add field nodes if there are any
|
||||
sqlb::FieldInfoList fieldList = it->fieldInformation();
|
||||
if(!fieldList.empty())
|
||||
sqlb::StringVector pk_columns;
|
||||
if(!obj.second->isView())
|
||||
{
|
||||
sqlb::StringVector pk_columns;
|
||||
if(it->type() == sqlb::Object::Types::Table)
|
||||
{
|
||||
const auto pk = std::dynamic_pointer_cast<sqlb::Table>(it)->primaryKey();
|
||||
if(pk)
|
||||
pk_columns = pk->columnList();
|
||||
}
|
||||
const auto pk = obj.second->primaryKey();
|
||||
if(pk)
|
||||
pk_columns = pk->columnList();
|
||||
}
|
||||
|
||||
for(const sqlb::FieldInfo& field : fieldList)
|
||||
{
|
||||
QTreeWidgetItem *fldItem = new QTreeWidgetItem(item);
|
||||
bool isFK = false;
|
||||
if(it->type() == sqlb::Object::Types::Table)
|
||||
isFK = std::dynamic_pointer_cast<sqlb::Table>(it)->constraint({field.name}, sqlb::Constraint::ForeignKeyConstraintType) != nullptr;
|
||||
for(const auto& field : obj.second->fields)
|
||||
{
|
||||
bool isPK = contains(pk_columns, field.name());
|
||||
bool isFK = obj.second->constraint({field.name()}, sqlb::Constraint::ForeignKeyConstraintType) != nullptr;
|
||||
|
||||
fldItem->setText(ColumnName, QString::fromStdString(field.name));
|
||||
fldItem->setText(ColumnObjectType, "field");
|
||||
fldItem->setText(ColumnDataType, QString::fromStdString(field.type));
|
||||
fldItem->setText(ColumnSQL, QString::fromStdString(field.sql));
|
||||
fldItem->setText(ColumnSchema, QString::fromStdString(schema));
|
||||
if(contains(pk_columns, field.name))
|
||||
fldItem->setIcon(ColumnName, IconCache::get("field_key"));
|
||||
else if(isFK)
|
||||
fldItem->setIcon(ColumnName, IconCache::get("field_fk"));
|
||||
else
|
||||
fldItem->setIcon(ColumnName, IconCache::get("field"));
|
||||
}
|
||||
addNode(schema, field.name(), "field", field.toString(" ", " "), item, field.type(), isPK ? "_key" : (isFK ? "_fk" : std::string{}));
|
||||
}
|
||||
}
|
||||
|
||||
// Add indices
|
||||
for(const auto& obj : objmap.indices)
|
||||
{
|
||||
QTreeWidgetItem* item = addNode(schema, obj.second->name(), "index", obj.second->originalSql(), itemIndices);
|
||||
|
||||
for(const auto& field : obj.second->fields)
|
||||
addNode(schema, field.name(), "field", field.toString(" ", " "), item, field.order());
|
||||
}
|
||||
|
||||
// Add triggers
|
||||
for(const auto& obj : objmap.triggers)
|
||||
addNode(schema, obj.second->name(), "trigger", obj.second->originalSql(), itemTriggers);
|
||||
}
|
||||
|
||||
QTreeWidgetItem* DbStructureModel::addNode(QTreeWidgetItem* parent, const sqlb::ObjectPtr& object, const std::string& schema)
|
||||
QTreeWidgetItem* DbStructureModel::addNode(const std::string& schema, const std::string& name, const std::string& object_type, const std::string& sql, QTreeWidgetItem* parent_item, const std::string& data_type, const std::string& icon_suffix)
|
||||
{
|
||||
std::string type = sqlb::Object::typeToString(object->type());
|
||||
|
||||
QTreeWidgetItem *item = new QTreeWidgetItem(parent);
|
||||
item->setIcon(ColumnName, IconCache::get(type));
|
||||
item->setText(ColumnName, QString::fromStdString(object->name()));
|
||||
item->setText(ColumnObjectType, QString::fromStdString(type));
|
||||
item->setText(ColumnSQL, QString::fromStdString(object->originalSql()));
|
||||
QTreeWidgetItem* item = new QTreeWidgetItem(parent_item);
|
||||
item->setText(ColumnName, QString::fromStdString(name));
|
||||
item->setText(ColumnObjectType, QString::fromStdString(object_type));
|
||||
item->setText(ColumnDataType, QString::fromStdString(data_type));
|
||||
item->setText(ColumnSQL, QString::fromStdString(sql));
|
||||
item->setText(ColumnSchema, QString::fromStdString(schema));
|
||||
|
||||
item->setIcon(ColumnName, IconCache::get(object_type + icon_suffix));
|
||||
return item;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
class DBBrowserDB;
|
||||
class QTreeWidgetItem;
|
||||
namespace sqlb { class Object; using ObjectPtr = std::shared_ptr<Object>; }
|
||||
|
||||
class DbStructureModel : public QAbstractItemModel
|
||||
{
|
||||
@@ -50,7 +49,7 @@ private:
|
||||
bool m_dropEnquotedNames;
|
||||
|
||||
void buildTree(QTreeWidgetItem* parent, const std::string& schema);
|
||||
QTreeWidgetItem* addNode(QTreeWidgetItem* parent, const sqlb::ObjectPtr& object, const std::string& schema);
|
||||
QTreeWidgetItem* addNode(const std::string& schema, const std::string& name, const std::string& object_type, const std::string& sql, QTreeWidgetItem* parent_item, const std::string& data_type = {}, const std::string& icon_suffix = {});
|
||||
QString getNameForDropping(const QString& domain, const QString& object, const QString& field) const;
|
||||
};
|
||||
|
||||
|
||||
@@ -25,20 +25,18 @@ EditIndexDialog::EditIndexDialog(DBBrowserDB& db, const sqlb::ObjectIdentifier&
|
||||
{
|
||||
for(const auto& it : pdb.schemata)
|
||||
{
|
||||
auto tables = it.second.equal_range("table");
|
||||
for(auto jt=tables.first;jt!=tables.second;++jt)
|
||||
for(const auto& jt : it.second.tables)
|
||||
{
|
||||
// Only show the schema name for non-main schemata
|
||||
sqlb::ObjectIdentifier obj(it.first, jt->second->name());
|
||||
sqlb::ObjectIdentifier obj(it.first, jt.first);
|
||||
dbobjs.insert({obj.toDisplayString(), obj});
|
||||
}
|
||||
}
|
||||
} else { // If this is an existing index, only offer tables of the current database schema
|
||||
auto tables = pdb.schemata[curIndex.schema()].equal_range("table");
|
||||
for(auto it=tables.first;it!=tables.second;++it)
|
||||
for(const auto& it : pdb.schemata[curIndex.schema()].tables)
|
||||
{
|
||||
// Only show the schema name for non-main schemata
|
||||
sqlb::ObjectIdentifier obj(curIndex.schema(), it->second->name());
|
||||
sqlb::ObjectIdentifier obj(curIndex.schema(), it.first);
|
||||
dbobjs.insert({obj.toDisplayString(), obj});
|
||||
}
|
||||
}
|
||||
@@ -54,7 +52,7 @@ EditIndexDialog::EditIndexDialog(DBBrowserDB& db, const sqlb::ObjectIdentifier&
|
||||
if(!newIndex)
|
||||
{
|
||||
// Load the current layout and fill in the dialog fields
|
||||
index = *(pdb.getObjectByName<sqlb::Index>(curIndex));
|
||||
index = *pdb.getIndexByName(curIndex);
|
||||
|
||||
ui->editIndexName->blockSignals(true);
|
||||
ui->editIndexName->setText(QString::fromStdString(index.name()));
|
||||
@@ -114,26 +112,25 @@ void EditIndexDialog::tableChanged(const QString& new_table, bool initialLoad)
|
||||
void EditIndexDialog::updateColumnLists()
|
||||
{
|
||||
// Fill the table column list
|
||||
sqlb::TablePtr table = pdb.getObjectByName<sqlb::Table>(sqlb::ObjectIdentifier(ui->comboTableName->currentData().toString().toStdString()));
|
||||
sqlb::TablePtr table = pdb.getTableByName(sqlb::ObjectIdentifier(ui->comboTableName->currentData().toString().toStdString()));
|
||||
if(!table)
|
||||
return;
|
||||
sqlb::FieldInfoList tableFields = table->fieldInformation();
|
||||
ui->tableTableColumns->setRowCount(static_cast<int>(tableFields.size()));
|
||||
ui->tableTableColumns->setRowCount(static_cast<int>(table->fields.size()));
|
||||
int tableRows = 0;
|
||||
for(size_t i=0;i<tableFields.size();++i)
|
||||
for(const auto& field: table->fields)
|
||||
{
|
||||
// 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(sqlb::findField(index, tableFields.at(i).name) == index.fields.end())
|
||||
if(sqlb::findField(index, field.name()) == index.fields.end())
|
||||
{
|
||||
// Put the name of the field in the first column
|
||||
QTableWidgetItem* name = new QTableWidgetItem(QString::fromStdString(tableFields.at(i).name));
|
||||
QTableWidgetItem* name = new QTableWidgetItem(QString::fromStdString(field.name()));
|
||||
name->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
|
||||
ui->tableTableColumns->setItem(tableRows, 0, name);
|
||||
|
||||
// Put the data type in the second column
|
||||
QTableWidgetItem* type = new QTableWidgetItem(QString::fromStdString(tableFields.at(i).type));
|
||||
QTableWidgetItem* type = new QTableWidgetItem(QString::fromStdString(field.type()));
|
||||
type->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
|
||||
ui->tableTableColumns->setItem(tableRows, 1, type);
|
||||
|
||||
|
||||
@@ -78,7 +78,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<sqlb::Table>(curTable));
|
||||
m_table = *pdb.getTableByName(curTable);
|
||||
ui->labelEditWarning->setVisible(!m_table.fullyParsed());
|
||||
|
||||
// Initialise the list of tracked columns for table layout changes
|
||||
@@ -492,13 +492,11 @@ void EditTableDialog::fieldItemChanged(QTreeWidgetItem *item, int column)
|
||||
if(!m_bNewTable)
|
||||
{
|
||||
const auto pk = m_table.primaryKey();
|
||||
const auto tables = pdb.schemata[curTable.schema()].equal_range("table");
|
||||
for(auto it=tables.first;it!=tables.second;++it)
|
||||
for(const auto& it : pdb.schemata[curTable.schema()].tables)
|
||||
{
|
||||
const sqlb::ObjectPtr& fkobj = it->second;
|
||||
const sqlb::TablePtr& fkobj = it.second;
|
||||
|
||||
|
||||
auto fks = std::dynamic_pointer_cast<sqlb::Table>(fkobj)->constraints({}, sqlb::Constraint::ForeignKeyConstraintType);
|
||||
auto fks = fkobj->constraints({}, sqlb::Constraint::ForeignKeyConstraintType);
|
||||
for(const sqlb::ConstraintPtr& fkptr : fks)
|
||||
{
|
||||
auto fk = std::dynamic_pointer_cast<sqlb::ForeignKeyClause>(fkptr);
|
||||
@@ -591,7 +589,7 @@ void EditTableDialog::fieldItemChanged(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 coalesce(NULL,%3) IS NULL;").arg(
|
||||
QString::fromStdString(sqlb::joinStringVector(sqlb::escapeIdentifier(pdb.getObjectByName<sqlb::Table>(curTable)->rowidColumns()), ",")),
|
||||
QString::fromStdString(sqlb::joinStringVector(sqlb::escapeIdentifier(pdb.getTableByName(curTable)->rowidColumns()), ",")),
|
||||
QString::fromStdString(curTable.toString()),
|
||||
QString::fromStdString(sqlb::escapeIdentifier(field.name()))));
|
||||
if(!m.completeCache())
|
||||
|
||||
@@ -47,18 +47,10 @@ ExportDataDialog::ExportDataDialog(DBBrowserDB& db, ExportFormats format, QWidge
|
||||
// Get list of tables to export
|
||||
for(const auto& it : pdb.schemata)
|
||||
{
|
||||
const auto tables = it.second.equal_range("table");
|
||||
const auto views = it.second.equal_range("view");
|
||||
std::map<std::string, sqlb::ObjectPtr> objects;
|
||||
for(auto jt=tables.first;jt!=tables.second;++jt)
|
||||
objects.insert({jt->second->name(), jt->second});
|
||||
for(auto jt=views.first;jt!=views.second;++jt)
|
||||
objects.insert({jt->second->name(), jt->second});
|
||||
|
||||
for(const auto& jt : objects)
|
||||
for(const auto& jt : it.second.tables)
|
||||
{
|
||||
sqlb::ObjectIdentifier obj(it.first, jt.second->name());
|
||||
QListWidgetItem* item = new QListWidgetItem(IconCache::get(sqlb::Object::typeToString(jt.second->type())), QString::fromStdString(obj.toDisplayString()));
|
||||
sqlb::ObjectIdentifier obj(it.first, jt.first);
|
||||
QListWidgetItem* item = new QListWidgetItem(IconCache::get(jt.second->isView() ? "view" : "table"), QString::fromStdString(obj.toDisplayString()));
|
||||
item->setData(Qt::UserRole, QString::fromStdString(obj.toSerialised()));
|
||||
ui->listTables->addItem(item);
|
||||
}
|
||||
|
||||
@@ -29,9 +29,8 @@ ExportSqlDialog::ExportSqlDialog(DBBrowserDB* db, QWidget* parent, const QString
|
||||
ui->comboOldSchema->setCurrentIndex(Settings::getValue("exportsql", "oldschema").toInt());
|
||||
|
||||
// Get list of tables to export
|
||||
const auto objects = pdb->schemata["main"].equal_range("table");
|
||||
for(auto it=objects.first;it!=objects.second;++it)
|
||||
ui->listTables->addItem(new QListWidgetItem(IconCache::get(sqlb::Object::typeToString(it->second->type())), QString::fromStdString(it->second->name())));
|
||||
for(const auto& it : pdb->schemata["main"].tables)
|
||||
ui->listTables->addItem(new QListWidgetItem(IconCache::get(it.second->isView() ? "view" : "table"), QString::fromStdString(it.first)));
|
||||
|
||||
// Sort list of tables and select the table specified in the
|
||||
// selection parameter or all tables if table not specified
|
||||
|
||||
@@ -139,12 +139,12 @@ QWidget* ExtendedTableWidgetEditorDelegate::createEditor(QWidget* parent, const
|
||||
std::string column;
|
||||
// If no column name is set, assume the primary key is meant
|
||||
if(fk.columns().empty()) {
|
||||
sqlb::TablePtr obj = m->db().getObjectByName<sqlb::Table>(foreignTable);
|
||||
sqlb::TablePtr obj = m->db().getTableByName(foreignTable);
|
||||
column = obj->primaryKey()->columnList().front();
|
||||
} else
|
||||
column = fk.columns().at(0);
|
||||
|
||||
sqlb::TablePtr currentTable = m->db().getObjectByName<sqlb::Table>(m->currentTableName());
|
||||
sqlb::TablePtr currentTable = m->db().getTableByName(m->currentTableName());
|
||||
QString query = QString("SELECT %1 FROM %2").arg(QString::fromStdString(sqlb::escapeIdentifier(column)), QString::fromStdString(foreignTable.toString()));
|
||||
|
||||
// if the current column of the current table does NOT have not-null constraint,
|
||||
@@ -792,7 +792,7 @@ void ExtendedTableWidget::cut()
|
||||
{
|
||||
const QModelIndexList& indices = selectionModel()->selectedIndexes();
|
||||
SqliteTableModel* m = qobject_cast<SqliteTableModel*>(model());
|
||||
sqlb::TablePtr currentTable = m->db().getObjectByName<sqlb::Table>(m->currentTableName());
|
||||
sqlb::TablePtr currentTable = m->db().getTableByName(m->currentTableName());
|
||||
|
||||
copy(false, false);
|
||||
|
||||
@@ -1164,7 +1164,7 @@ void ExtendedTableWidget::currentChanged(const QModelIndex ¤t, const QMode
|
||||
void ExtendedTableWidget::setToNull(const QModelIndexList& indices)
|
||||
{
|
||||
SqliteTableModel* m = qobject_cast<SqliteTableModel*>(model());
|
||||
sqlb::TablePtr currentTable = m->db().getObjectByName<sqlb::Table>(m->currentTableName());
|
||||
sqlb::TablePtr currentTable = m->db().getTableByName(m->currentTableName());
|
||||
|
||||
// Check if some column in the selection has a NOT NULL constraint, before trying to update the cells.
|
||||
if(currentTable) {
|
||||
|
||||
@@ -83,11 +83,11 @@ ForeignKeyEditorDelegate::ForeignKeyEditorDelegate(const DBBrowserDB& db, sqlb::
|
||||
{
|
||||
for(const auto& it : m_db.schemata)
|
||||
{
|
||||
for(const auto& jt : it.second)
|
||||
for(const auto& jt : it.second.tables)
|
||||
{
|
||||
// Don't insert the current table into the list. The name and fields of the current table are always taken from the m_table reference
|
||||
if(jt.second->type() == sqlb::Object::Types::Table && jt.second->name() != m_table.name())
|
||||
m_tablesIds.insert({jt.second->name(), std::dynamic_pointer_cast<sqlb::Table>(jt.second)->fieldNames()});
|
||||
if(jt.first != m_table.name())
|
||||
m_tablesIds.insert({jt.first, jt.second->fieldNames()});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -499,10 +499,10 @@ bool ImportCsvDialog::importCsv(const QString& fileName, const QString& name)
|
||||
|
||||
// Are we importing into an existing table?
|
||||
bool importToExistingTable = false;
|
||||
const sqlb::ObjectPtr obj = pdb->getObjectByName(sqlb::ObjectIdentifier("main", tableName.toStdString()));
|
||||
if(obj && obj->type() == sqlb::Object::Types::Table)
|
||||
const sqlb::TablePtr tbl = pdb->getTableByName(sqlb::ObjectIdentifier("main", tableName.toStdString()));
|
||||
if(tbl)
|
||||
{
|
||||
if(std::dynamic_pointer_cast<sqlb::Table>(obj)->fields.size() != fieldList.size())
|
||||
if(tbl->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));
|
||||
@@ -564,7 +564,6 @@ 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::Table>(sqlb::ObjectIdentifier("main", tableName.toStdString()));
|
||||
if(tbl)
|
||||
{
|
||||
for(const sqlb::Field& f : tbl->fields)
|
||||
|
||||
@@ -641,16 +641,12 @@ void MainWindow::populateStructure(const std::vector<sqlb::ObjectIdentifier>& ol
|
||||
{
|
||||
SqlUiLexer::TablesAndColumnsMap tablesToColumnsMap;
|
||||
|
||||
for(const auto& jt : it.second)
|
||||
for(const auto& jt : it.second.tables)
|
||||
{
|
||||
if(jt.second->type() == sqlb::Object::Types::Table || jt.second->type() == sqlb::Object::Types::View)
|
||||
{
|
||||
QString objectname = QString::fromStdString(jt.second->name());
|
||||
QString objectname = QString::fromStdString(jt.first);
|
||||
|
||||
sqlb::FieldInfoList fi = jt.second->fieldInformation();
|
||||
for(const sqlb::FieldInfo& f : fi)
|
||||
tablesToColumnsMap[objectname].push_back(QString::fromStdString(f.name));
|
||||
}
|
||||
for(const auto& f : jt.second->fields)
|
||||
tablesToColumnsMap[objectname].push_back(QString::fromStdString(f.name()));
|
||||
}
|
||||
|
||||
qualifiedTablesMap[QString::fromStdString(it.first)] = tablesToColumnsMap;
|
||||
@@ -989,13 +985,13 @@ void MainWindow::editObject()
|
||||
if(dialog.exec())
|
||||
refreshTableBrowsers();
|
||||
} else if(type == "view") {
|
||||
sqlb::ViewPtr view = db.getObjectByName<sqlb::View>(obj);
|
||||
sqlb::TablePtr view = db.getTableByName(obj);
|
||||
runSqlNewTab(QString("DROP VIEW %1;\n%2").arg(QString::fromStdString(obj.toString()), QString::fromStdString(view->sql())),
|
||||
tr("Edit View %1").arg(QString::fromStdString(obj.toDisplayString())),
|
||||
"https://www.sqlite.org/lang_createview.html",
|
||||
/* autoRun */ false);
|
||||
} else if(type == "trigger") {
|
||||
sqlb::TriggerPtr trigger = db.getObjectByName<sqlb::Trigger>(obj);
|
||||
sqlb::TriggerPtr trigger = db.getTriggerByName(obj);
|
||||
runSqlNewTab(QString("DROP TRIGGER %1;\n%2").arg(QString::fromStdString(obj.toString()), QString::fromStdString(trigger->sql())),
|
||||
tr("Edit Trigger %1").arg(QString::fromStdString(obj.toDisplayString())),
|
||||
"https://www.sqlite.org/lang_createtrigger.html",
|
||||
@@ -2500,10 +2496,8 @@ static void loadCondFormatMap(BrowseDataTableSettings::CondFormatMap& condFormat
|
||||
}
|
||||
}
|
||||
|
||||
static void loadBrowseDataTableSettings(BrowseDataTableSettings& settings, sqlb::ObjectPtr obj, QXmlStreamReader& xml)
|
||||
static void loadBrowseDataTableSettings(BrowseDataTableSettings& settings, sqlb::TablePtr obj, QXmlStreamReader& xml)
|
||||
{
|
||||
const auto field_information = obj->fieldInformation();
|
||||
|
||||
settings.showRowid = xml.attributes().value("show_row_id").toInt();
|
||||
settings.encoding = xml.attributes().value("encoding").toString();
|
||||
settings.plotXAxis = xml.attributes().value("plot_x_axis").toString();
|
||||
@@ -2520,8 +2514,8 @@ static void loadBrowseDataTableSettings(BrowseDataTableSettings& settings, sqlb:
|
||||
{
|
||||
int index = xml.attributes().value("index").toInt();
|
||||
int mode = xml.attributes().value("mode").toInt();
|
||||
if(static_cast<size_t>(index) < field_information.size())
|
||||
settings.sortColumns.emplace_back(field_information.at(static_cast<size_t>(index)).name, mode == Qt::AscendingOrder ? sqlb::OrderBy::Ascending : sqlb::OrderBy::Descending);
|
||||
if(static_cast<size_t>(index) < obj->fields.size())
|
||||
settings.sortColumns.emplace_back(obj->fields.at(static_cast<size_t>(index)).name(), mode == Qt::AscendingOrder ? sqlb::OrderBy::Ascending : sqlb::OrderBy::Descending);
|
||||
xml.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
@@ -2539,7 +2533,7 @@ static void loadBrowseDataTableSettings(BrowseDataTableSettings& settings, sqlb:
|
||||
size_t index = xml.attributes().value("index").toUInt();
|
||||
QString value = xml.attributes().value("value").toString();
|
||||
if(!value.isEmpty())
|
||||
settings.filterValues[field_information.at(index).name] = value;
|
||||
settings.filterValues[obj->fields.at(index).name()] = value;
|
||||
xml.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
@@ -2807,7 +2801,7 @@ bool MainWindow::loadProject(QString filename, bool readOnly)
|
||||
sqlb::ObjectIdentifier (xml.attributes().value("schema").toString().toStdString(),
|
||||
xml.attributes().value("name").toString().toStdString());
|
||||
BrowseDataTableSettings settings;
|
||||
loadBrowseDataTableSettings(settings, db.getObjectByName(tableIdentifier), xml);
|
||||
loadBrowseDataTableSettings(settings, db.getTableByName(tableIdentifier), xml);
|
||||
TableBrowser::setSettings(tableIdentifier, settings);
|
||||
}
|
||||
}
|
||||
@@ -2893,7 +2887,7 @@ static void saveCondFormatMap(const QString& elementName, const BrowseDataTableS
|
||||
xml.writeEndElement();
|
||||
}
|
||||
|
||||
static void saveBrowseDataTableSettings(const BrowseDataTableSettings& object, sqlb::ObjectPtr obj, QXmlStreamWriter& xml)
|
||||
static void saveBrowseDataTableSettings(const BrowseDataTableSettings& object, sqlb::TablePtr obj, QXmlStreamWriter& xml)
|
||||
{
|
||||
xml.writeAttribute("show_row_id", QString::number(object.showRowid));
|
||||
xml.writeAttribute("encoding", object.encoding);
|
||||
@@ -3098,7 +3092,7 @@ void MainWindow::saveProject(const QString& currentFilename)
|
||||
xml.writeAttribute("schema", QString::fromStdString(tableIt->first.schema()));
|
||||
xml.writeAttribute("name", QString::fromStdString(tableIt->first.name()));
|
||||
|
||||
auto obj = db.getObjectByName(tableIt->first);
|
||||
auto obj = db.getTableByName(tableIt->first);
|
||||
saveBrowseDataTableSettings(tableIt->second, obj, xml);
|
||||
xml.writeEndElement();
|
||||
}
|
||||
@@ -3409,7 +3403,7 @@ void MainWindow::saveAsView(const std::string& query)
|
||||
name = QInputDialog::getText(this, qApp->applicationName(), tr("Please specify the view name")).trimmed();
|
||||
if(name.isNull())
|
||||
return;
|
||||
if(db.getObjectByName(sqlb::ObjectIdentifier("main", name.toStdString())) != nullptr)
|
||||
if(db.getTableByName(sqlb::ObjectIdentifier("main", name.toStdString())))
|
||||
QMessageBox::warning(this, qApp->applicationName(), tr("There is already an object with that name. Please choose a different name."));
|
||||
else
|
||||
break;
|
||||
@@ -3831,15 +3825,12 @@ QList<TableBrowserDock*> MainWindow::allTableBrowserDocks() const
|
||||
void MainWindow::newRowCountsTab()
|
||||
{
|
||||
QString sql;
|
||||
for(const auto& it : db.schemata["main"])
|
||||
for(const auto& it : db.schemata["main"].tables)
|
||||
{
|
||||
if(it.second->type() == sqlb::Object::Table || it.second->type() == sqlb::Object::View)
|
||||
{
|
||||
sql += QString("SELECT %1 AS \"name\", %2 AS \"type\", COUNT(*) AS \"rows\" FROM %3\nUNION ").arg(
|
||||
QString::fromStdString(sqlb::escapeString(it.second->name())),
|
||||
QString::fromStdString(sqlb::escapeString(it.second->typeToString(it.second->type()))),
|
||||
QString::fromStdString(sqlb::escapeIdentifier(it.second->name())));
|
||||
}
|
||||
sql += QString("SELECT %1 AS \"name\", '%2' AS \"type\", COUNT(*) AS \"rows\" FROM %3\nUNION ").arg(
|
||||
QString::fromStdString(sqlb::escapeString(it.first)),
|
||||
QString::fromStdString(it.second->isView() ? "view" : "table"),
|
||||
QString::fromStdString(sqlb::escapeIdentifier(it.first)));
|
||||
}
|
||||
sql.chop(7); // Remove the last "\nUNION " at the end
|
||||
|
||||
|
||||
@@ -725,10 +725,10 @@ void TableBrowser::updateRecordsetLabel()
|
||||
sqlb::ObjectIdentifier current_table = currentlyBrowsedTableName();
|
||||
if(!current_table.isEmpty() && !m_model->query().empty())
|
||||
{
|
||||
auto obj = db->getObjectByName(current_table);
|
||||
auto obj = db->getTableByName(current_table);
|
||||
is_table_or_unlocked_view = obj && (
|
||||
(obj->type() == sqlb::Object::View && m_model->hasPseudoPk()) ||
|
||||
(obj->type() == sqlb::Object::Table));
|
||||
(obj->isView() && m_model->hasPseudoPk()) ||
|
||||
(!obj->isView()));
|
||||
}
|
||||
enableEditing(m_model->rowCountAvailable() != SqliteTableModel::RowCount::Unknown && is_table_or_unlocked_view);
|
||||
|
||||
@@ -764,20 +764,19 @@ sqlb::Query TableBrowser::buildQuery(const BrowseDataTableSettings& storedData,
|
||||
|
||||
// Display formats
|
||||
bool only_defaults = true;
|
||||
const auto table = db->getObjectByName(tablename);
|
||||
const auto table = db->getTableByName(tablename);
|
||||
if(table)
|
||||
{
|
||||
// When there is at least one custom display format, we have to set all columns for the query explicitly here
|
||||
const sqlb::FieldInfoList& tablefields = table->fieldInformation();
|
||||
for(size_t i=0; i<tablefields.size(); ++i)
|
||||
for(size_t i=0; i<table->fields.size(); ++i)
|
||||
{
|
||||
QString format = contains(storedData.displayFormats, i+1) ? storedData.displayFormats.at(i+1) : QString();
|
||||
if(format.size())
|
||||
{
|
||||
query.selectedColumns().emplace_back(tablefields.at(i).name, format.toStdString());
|
||||
query.selectedColumns().emplace_back(table->fields.at(i).name(), format.toStdString());
|
||||
only_defaults = false;
|
||||
} else {
|
||||
query.selectedColumns().emplace_back(tablefields.at(i).name, tablefields.at(i).name);
|
||||
query.selectedColumns().emplace_back(table->fields.at(i).name(), table->fields.at(i).name());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -837,12 +836,12 @@ void TableBrowser::applyViewportSettings(const BrowseDataTableSettings& storedDa
|
||||
ui->editGlobalFilter->setText(text);
|
||||
|
||||
// Show/hide some menu options depending on whether this is a table or a view
|
||||
const auto table = db->getObjectByName<sqlb::Table>(tablename);
|
||||
if(table)
|
||||
const auto table = db->getTableByName(tablename);
|
||||
if(!table->isView())
|
||||
{
|
||||
// Table
|
||||
ui->actionUnlockViewEditing->setVisible(false);
|
||||
ui->actionShowRowidColumn->setVisible(!std::dynamic_pointer_cast<sqlb::Table>(table)->withoutRowidTable());
|
||||
ui->actionShowRowidColumn->setVisible(!table->withoutRowidTable());
|
||||
} else {
|
||||
// View
|
||||
ui->actionUnlockViewEditing->setVisible(true);
|
||||
@@ -930,7 +929,7 @@ void TableBrowser::generateFilters()
|
||||
// Set filters blocking signals for this since the filter is already applied to the browse table model
|
||||
FilterTableHeader* filterHeader = qobject_cast<FilterTableHeader*>(ui->dataTable->horizontalHeader());
|
||||
bool oldState = filterHeader->blockSignals(true);
|
||||
auto obj = db->getObjectByName(currentlyBrowsedTableName());
|
||||
auto obj = db->getTableByName(currentlyBrowsedTableName());
|
||||
for(auto filterIt=settings.filterValues.cbegin();filterIt!=settings.filterValues.cend();++filterIt)
|
||||
ui->dataTable->setFilter(sqlb::getFieldNumber(obj, filterIt->first) + 1, filterIt->second);
|
||||
filterHeader->blockSignals(oldState);
|
||||
@@ -939,10 +938,10 @@ void TableBrowser::generateFilters()
|
||||
void TableBrowser::unlockViewEditing(bool unlock, QString pk)
|
||||
{
|
||||
sqlb::ObjectIdentifier currentTable = currentlyBrowsedTableName();
|
||||
sqlb::ViewPtr obj = db->getObjectByName<sqlb::View>(currentTable);
|
||||
sqlb::TablePtr view = db->getTableByName(currentTable);
|
||||
|
||||
// If this isn't a view just unlock editing and return
|
||||
if(!obj)
|
||||
if(!view)
|
||||
{
|
||||
m_model->setPseudoPk(m_model->pseudoPk());
|
||||
enableEditing(true);
|
||||
@@ -957,7 +956,7 @@ void TableBrowser::unlockViewEditing(bool unlock, QString pk)
|
||||
bool ok;
|
||||
|
||||
QStringList options;
|
||||
for(const auto& n : obj->fieldNames())
|
||||
for(const auto& n : view->fieldNames())
|
||||
options.push_back(QString::fromStdString(n));
|
||||
|
||||
// Ask for a PK
|
||||
@@ -1210,7 +1209,7 @@ void TableBrowser::showRecordPopupMenu(const QPoint& pos)
|
||||
QMenu popupRecordMenu(this);
|
||||
|
||||
// "Delete and duplicate records" can only be done on writable objects
|
||||
if(db->getObjectByName(currentlyBrowsedTableName())->type() == sqlb::Object::Types::Table && !db->readOnly()) {
|
||||
if(db->getTableByName(currentlyBrowsedTableName()) && !db->readOnly()) {
|
||||
|
||||
// Select the row if it is not already in the selection.
|
||||
QModelIndexList rowList = ui->dataTable->selectionModel()->selectedRows();
|
||||
@@ -1382,11 +1381,8 @@ void TableBrowser::editDisplayFormat()
|
||||
// column is always the rowid column. Ultimately, get the column name from the column object
|
||||
sqlb::ObjectIdentifier current_table = currentlyBrowsedTableName();
|
||||
size_t field_number = sender()->property("clicked_column").toUInt();
|
||||
QString field_name;
|
||||
if (db->getObjectByName(current_table)->type() == sqlb::Object::Table)
|
||||
field_name = QString::fromStdString(db->getObjectByName<sqlb::Table>(current_table)->fields.at(field_number-1).name());
|
||||
else
|
||||
field_name = QString::fromStdString(db->getObjectByName<sqlb::View>(current_table)->fieldNames().at(field_number-1));
|
||||
QString field_name = QString::fromStdString(db->getTableByName(current_table)->fields.at(field_number-1).name());
|
||||
|
||||
// Get the current display format of the field
|
||||
QString current_displayformat = m_settings[current_table].displayFormats[field_number];
|
||||
|
||||
@@ -1483,7 +1479,7 @@ void TableBrowser::setDefaultTableEncoding()
|
||||
void TableBrowser::jumpToRow(const sqlb::ObjectIdentifier& table, std::string column, const QByteArray& value)
|
||||
{
|
||||
// First check if table exists
|
||||
sqlb::TablePtr obj = db->getObjectByName<sqlb::Table>(table);
|
||||
sqlb::TablePtr obj = db->getTableByName(table);
|
||||
if(!obj)
|
||||
return;
|
||||
|
||||
|
||||
@@ -35,18 +35,6 @@ bool Object::operator==(const Object& rhs) const
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string Object::typeToString(Types type)
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case Types::Table: return "table";
|
||||
case Types::Index: return "index";
|
||||
case Types::View: return "view";
|
||||
case Types::Trigger: return "trigger";
|
||||
}
|
||||
return std::string();
|
||||
}
|
||||
|
||||
ConstraintPtr Constraint::makeConstraint(ConstraintTypes type)
|
||||
{
|
||||
switch(type)
|
||||
@@ -441,13 +429,6 @@ StringVector Table::rowidColumns() const
|
||||
return {"_rowid_"};
|
||||
}
|
||||
|
||||
FieldInfoList Table::fieldInformation() const
|
||||
{
|
||||
FieldInfoList result;
|
||||
std::transform(fields.begin(), fields.end(), std::back_inserter(result), [](const auto& f) { return FieldInfo(f.name(), f.type(), f.toString(" ", " "), f.affinity()); });
|
||||
return result;
|
||||
}
|
||||
|
||||
TablePtr Table::parseSQL(const std::string& sSQL)
|
||||
{
|
||||
parser::ParserDriver drv;
|
||||
@@ -458,7 +439,9 @@ TablePtr Table::parseSQL(const std::string& sSQL)
|
||||
return t;
|
||||
} else {
|
||||
std::cerr << "Sqlite parse error: " << sSQL << std::endl;
|
||||
return std::make_shared<Table>("");
|
||||
TablePtr t = std::make_shared<Table>("");
|
||||
t->setOriginalSql(sSQL);
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -672,13 +655,6 @@ std::string Index::sql(const std::string& schema, bool ifNotExists) const
|
||||
return sql + ";";
|
||||
}
|
||||
|
||||
FieldInfoList Index::fieldInformation() const
|
||||
{
|
||||
FieldInfoList result;
|
||||
std::transform(fields.begin(), fields.end(), std::back_inserter(result), [](const auto& c) { return FieldInfo(c.name(), c.order(), c.toString(" ", " "), Field::IntegerAffinity); });
|
||||
return result;
|
||||
}
|
||||
|
||||
IndexPtr Index::parseSQL(const std::string& sSQL)
|
||||
{
|
||||
parser::ParserDriver drv;
|
||||
@@ -689,10 +665,18 @@ IndexPtr Index::parseSQL(const std::string& sSQL)
|
||||
return i;
|
||||
} else {
|
||||
std::cerr << "Sqlite parse error: " << sSQL << std::endl;
|
||||
return std::make_shared<Index>("");
|
||||
IndexPtr i = std::make_shared<Index>("");
|
||||
i->setOriginalSql(sSQL);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
std::string getBaseTable<Index>(IndexPtr object)
|
||||
{
|
||||
return object->table();
|
||||
}
|
||||
|
||||
|
||||
|
||||
ViewPtr View::parseSQL(const std::string& sSQL)
|
||||
@@ -704,20 +688,6 @@ ViewPtr View::parseSQL(const std::string& sSQL)
|
||||
return v;
|
||||
}
|
||||
|
||||
StringVector View::fieldNames() const
|
||||
{
|
||||
StringVector sl;
|
||||
std::transform(fields.begin(), fields.end(), std::back_inserter(sl), [](const auto& f) { return f.name(); });
|
||||
return sl;
|
||||
}
|
||||
|
||||
FieldInfoList View::fieldInformation() const
|
||||
{
|
||||
FieldInfoList result;
|
||||
std::transform(fields.begin(), fields.end(), std::back_inserter(result), [](const auto& f) { return FieldInfo(f.name(), f.type(), f.toString(" ", " "), f.affinity()); });
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
TriggerPtr Trigger::parseSQL(const std::string& sSQL)
|
||||
{
|
||||
@@ -728,4 +698,10 @@ TriggerPtr Trigger::parseSQL(const std::string& sSQL)
|
||||
return t;
|
||||
}
|
||||
|
||||
template<>
|
||||
std::string getBaseTable<Trigger>(TriggerPtr object)
|
||||
{
|
||||
return object->table();
|
||||
}
|
||||
|
||||
} //namespace sqlb
|
||||
|
||||
@@ -61,7 +61,6 @@ class Trigger;
|
||||
class Field;
|
||||
class Constraint;
|
||||
class IndexedColumn;
|
||||
struct FieldInfo;
|
||||
using ObjectPtr = std::shared_ptr<Object>;
|
||||
using TablePtr = std::shared_ptr<Table>;
|
||||
using IndexPtr = std::shared_ptr<Index>;
|
||||
@@ -71,40 +70,24 @@ using ConstraintPtr = std::shared_ptr<Constraint>;
|
||||
using FieldVector = std::vector<Field>;
|
||||
using IndexedColumnVector = std::vector<IndexedColumn>;
|
||||
using ConstraintSet = std::set<ConstraintPtr>;
|
||||
using FieldInfoList = std::vector<FieldInfo>;
|
||||
|
||||
class Object
|
||||
{
|
||||
public:
|
||||
enum Types
|
||||
{
|
||||
Table,
|
||||
Index,
|
||||
View,
|
||||
Trigger
|
||||
};
|
||||
|
||||
explicit Object(const std::string& name): m_name(name), m_fullyParsed(false) {}
|
||||
virtual ~Object() = default;
|
||||
|
||||
bool operator==(const Object& rhs) const;
|
||||
|
||||
virtual Types type() const = 0;
|
||||
static std::string typeToString(Types type);
|
||||
|
||||
void setName(const std::string& name) { m_name = name; }
|
||||
const std::string& name() const { return m_name; }
|
||||
|
||||
void setOriginalSql(const std::string& original_sql) { m_originalSql = original_sql; }
|
||||
std::string originalSql() const { return m_originalSql; }
|
||||
|
||||
virtual std::string baseTable() const { return std::string(); }
|
||||
|
||||
void setFullyParsed(bool fully_parsed) { m_fullyParsed = fully_parsed; }
|
||||
bool fullyParsed() const { return m_fullyParsed; }
|
||||
|
||||
virtual FieldInfoList fieldInformation() const { return FieldInfoList(); }
|
||||
|
||||
/**
|
||||
* @brief Returns the CREATE statement for this object
|
||||
* @param schema The schema name of the object
|
||||
@@ -420,18 +403,6 @@ private:
|
||||
std::shared_ptr<GeneratedColumnConstraint> m_generated;
|
||||
};
|
||||
|
||||
struct FieldInfo
|
||||
{
|
||||
FieldInfo(const std::string& name_, const std::string& type_, const std::string& sql_, Field::Affinity affinity_)
|
||||
: name(name_), type(type_), sql(sql_), affinity(affinity_)
|
||||
{}
|
||||
|
||||
std::string name;
|
||||
std::string type;
|
||||
std::string sql;
|
||||
Field::Affinity affinity;
|
||||
};
|
||||
|
||||
class Table : public Object
|
||||
{
|
||||
public:
|
||||
@@ -441,7 +412,7 @@ public:
|
||||
|
||||
bool operator==(const Table& rhs) const;
|
||||
|
||||
Types type() const override { return Object::Table; }
|
||||
virtual bool isView() const { return false; }
|
||||
|
||||
FieldVector fields;
|
||||
using field_type = Field;
|
||||
@@ -463,8 +434,6 @@ public:
|
||||
const std::string& virtualUsing() const { return m_virtual; }
|
||||
bool isVirtual() const { return !m_virtual.empty(); }
|
||||
|
||||
FieldInfoList fieldInformation() const override;
|
||||
|
||||
void addConstraint(ConstraintPtr constraint);
|
||||
void setConstraint(ConstraintPtr constraint);
|
||||
void removeConstraint(ConstraintPtr constraint);
|
||||
@@ -531,14 +500,10 @@ public:
|
||||
explicit Index(const std::string& name): Object(name), m_unique(false) {}
|
||||
Index& operator=(const Index& rhs);
|
||||
|
||||
Types type() const override { return Object::Index; }
|
||||
|
||||
IndexedColumnVector fields;
|
||||
using field_type = IndexedColumn;
|
||||
using field_iterator = IndexedColumnVector::iterator;
|
||||
|
||||
std::string baseTable() const override { return m_table; }
|
||||
|
||||
void setUnique(bool unique) { m_unique = unique; }
|
||||
bool unique() const { return m_unique; }
|
||||
|
||||
@@ -561,8 +526,6 @@ public:
|
||||
*/
|
||||
static IndexPtr parseSQL(const std::string& sSQL);
|
||||
|
||||
FieldInfoList fieldInformation() const override;
|
||||
|
||||
private:
|
||||
StringVector columnSqlList() const;
|
||||
|
||||
@@ -571,23 +534,17 @@ private:
|
||||
std::string m_whereExpr;
|
||||
};
|
||||
|
||||
class View : public Object
|
||||
class View : public Table
|
||||
{
|
||||
public:
|
||||
explicit View(const std::string& name): Object(name) {}
|
||||
explicit View(const std::string& name): Table(name) {}
|
||||
|
||||
Types type() const override { return Object::View; }
|
||||
|
||||
FieldVector fields;
|
||||
virtual bool isView() const override { return true; }
|
||||
|
||||
std::string sql(const std::string& /*schema*/ = "main", bool /*ifNotExists*/ = false) const override
|
||||
{ /* TODO */ return m_originalSql; }
|
||||
|
||||
static ViewPtr parseSQL(const std::string& sSQL);
|
||||
|
||||
StringVector fieldNames() const;
|
||||
|
||||
FieldInfoList fieldInformation() const override;
|
||||
};
|
||||
|
||||
class Trigger : public Object
|
||||
@@ -595,15 +552,11 @@ class Trigger : public Object
|
||||
public:
|
||||
explicit Trigger(const std::string& name): Object(name) {}
|
||||
|
||||
Types type() const override { return Object::Trigger; }
|
||||
|
||||
std::string sql(const std::string& /*schema*/ = "main", bool /*ifNotExists*/ = false) const override
|
||||
{ /* TODO */ return m_originalSql; }
|
||||
|
||||
static TriggerPtr parseSQL(const std::string& sSQL);
|
||||
|
||||
std::string baseTable() const override { return m_table; }
|
||||
|
||||
void setTable(const std::string& table) { m_table = table; }
|
||||
std::string table() const { return m_table; }
|
||||
|
||||
@@ -611,6 +564,22 @@ private:
|
||||
std::string m_table;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Return the name of the base table of the given object. For indices and triggers that is the table which the object is related to.
|
||||
*/
|
||||
|
||||
template<typename T>
|
||||
std::string getBaseTable(std::shared_ptr<T> /*object*/)
|
||||
{
|
||||
return std::string{};
|
||||
}
|
||||
|
||||
template<>
|
||||
std::string getBaseTable<Index>(IndexPtr object);
|
||||
|
||||
template<>
|
||||
std::string getBaseTable<Trigger>(TriggerPtr object);
|
||||
|
||||
/**
|
||||
* @brief findField Finds a field in the database object and returns an iterator to it.
|
||||
* @param object
|
||||
@@ -681,12 +650,11 @@ bool removeField(T& object, const std::string& name)
|
||||
* TODO Remove this function. Whereever it is used we make the assumption that the queried columns are exactly equal to the columns of the table or view.
|
||||
* For more complex queries this is not true and in fact it already is a dubious assumption because we also select the rowid column.
|
||||
*/
|
||||
inline size_t getFieldNumber(ObjectPtr object, const std::string& name)
|
||||
inline size_t getFieldNumber(TablePtr object, const std::string& name)
|
||||
{
|
||||
const auto object_fields = object->fieldInformation();
|
||||
for(size_t i=0;i<object_fields.size();i++)
|
||||
for(size_t i=0;i<object->fields.size();i++)
|
||||
{
|
||||
if(object_fields[i].name == name)
|
||||
if(object->fields[i].name() == name)
|
||||
return i;
|
||||
}
|
||||
return 0;
|
||||
|
||||
242
src/sqlitedb.cpp
242
src/sqlitedb.cpp
@@ -879,16 +879,15 @@ bool DBBrowserDB::dump(const QString& filePath,
|
||||
// Count the total number of all records in all tables for the progress dialog
|
||||
size_t numRecordsTotal = 0;
|
||||
objectMap objMap = schemata.at("main"); // We only always export the main database, not the attached databases
|
||||
std::vector<sqlb::ObjectPtr> tables;
|
||||
auto all_tables = objMap.equal_range("table");
|
||||
for(auto it=all_tables.first;it!=all_tables.second;++it)
|
||||
std::vector<sqlb::TablePtr> tables;
|
||||
for(const auto& it : objMap.tables)
|
||||
{
|
||||
// Never export the sqlite_stat1 and the sqlite_sequence tables if they exist. Also only export any tables which are selected for export.
|
||||
if(it->second->name() != "sqlite_stat1" && it->second->name() != "sqlite_sequence" && contains(tablesToDump, it->second->name()))
|
||||
if(!it.second->isView() && it.first != "sqlite_stat1" && it.first != "sqlite_sequence" && contains(tablesToDump, it.first))
|
||||
{
|
||||
// Get the number of records in this table and remember to export it
|
||||
tables.push_back(it->second);
|
||||
numRecordsTotal += querySingleValueFromDb("SELECT COUNT(*) FROM " + sqlb::ObjectIdentifier("main", it->second->name()).toString()).toUInt();
|
||||
tables.push_back(it.second);
|
||||
numRecordsTotal += querySingleValueFromDb("SELECT COUNT(*) FROM " + sqlb::ObjectIdentifier("main", it.first).toString()).toUInt();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -927,7 +926,7 @@ bool DBBrowserDB::dump(const QString& filePath,
|
||||
for(const auto& it : tables)
|
||||
{
|
||||
// get columns
|
||||
sqlb::StringVector cols = std::dynamic_pointer_cast<sqlb::Table>(it)->fieldNames();
|
||||
sqlb::StringVector cols = it->fieldNames();
|
||||
|
||||
std::string sQuery = "SELECT * FROM " + sqlb::escapeIdentifier(it->name());
|
||||
sqlite3_stmt *stmt;
|
||||
@@ -1014,33 +1013,37 @@ bool DBBrowserDB::dump(const QString& filePath,
|
||||
// Finally export all objects other than tables
|
||||
if(exportSchema)
|
||||
{
|
||||
for(const auto& obj : objMap)
|
||||
{
|
||||
const auto& it = obj.second;
|
||||
|
||||
// Make sure it's not a table again
|
||||
if(it->type() == sqlb::Object::Types::Table)
|
||||
continue;
|
||||
|
||||
// If this object is based on a table (e.g. is an index for that table) it depends on the existence of this table.
|
||||
// So if we didn't export the base table this depends on, don't export this object either.
|
||||
if(!it->baseTable().empty() && !contains(tablesToDump, it->baseTable()))
|
||||
continue;
|
||||
|
||||
// Write the SQL string used to create this object to the output file
|
||||
if(!it->originalSql().empty())
|
||||
auto writeSchema = [&stream, &tablesToDump, keepOldSchema](const QString& type, auto objects) {
|
||||
for(const auto& obj : objects)
|
||||
{
|
||||
if(!keepOldSchema)
|
||||
stream << QString("DROP %1 IF EXISTS %2;\n").arg(
|
||||
QString::fromStdString(sqlb::Object::typeToString(it->type())).toUpper(),
|
||||
QString::fromStdString(sqlb::escapeIdentifier(it->name())));
|
||||
const auto& it = obj.second;
|
||||
|
||||
if(it->fullyParsed())
|
||||
stream << QString::fromStdString(it->sql("main", true)) << "\n";
|
||||
else
|
||||
stream << QString::fromStdString(it->originalSql()) << ";\n";
|
||||
// If this object is based on a table (e.g. is an index for that table) it depends on the existence of this table.
|
||||
// So if we didn't export the base table this depends on, don't export this object either.
|
||||
if(!sqlb::getBaseTable(it).empty() && !contains(tablesToDump, sqlb::getBaseTable(it)))
|
||||
continue;
|
||||
|
||||
// Write the SQL string used to create this object to the output file
|
||||
if(!it->originalSql().empty())
|
||||
{
|
||||
if(!keepOldSchema)
|
||||
stream << QString("DROP %1 IF EXISTS %2;\n").arg(
|
||||
type.toUpper(),
|
||||
QString::fromStdString(sqlb::escapeIdentifier(it->name())));
|
||||
|
||||
if(it->fullyParsed())
|
||||
stream << QString::fromStdString(it->sql("main", true)) << "\n";
|
||||
else
|
||||
stream << QString::fromStdString(it->originalSql()) << ";\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::map<std::string, sqlb::TablePtr> views;
|
||||
std::copy_if(objMap.tables.begin(), objMap.tables.end(), std::inserter(views, views.end()), [](const auto& t) { return t.second->isView(); });
|
||||
writeSchema("view", views);
|
||||
writeSchema("index", objMap.indices);
|
||||
writeSchema("trigger", objMap.triggers);
|
||||
}
|
||||
|
||||
// Done
|
||||
@@ -1297,7 +1300,7 @@ bool DBBrowserDB::getRow(const sqlb::ObjectIdentifier& table, const QString& row
|
||||
std::string query = "SELECT * FROM " + table.toString() + " WHERE ";
|
||||
|
||||
// For a single rowid column we can use a simple WHERE condition, for multiple rowid columns we have to use sqlb_make_single_value to decode the composed rowid values.
|
||||
sqlb::StringVector pks = getObjectByName<sqlb::Table>(table)->rowidColumns();
|
||||
sqlb::StringVector pks = getTableByName(table)->rowidColumns();
|
||||
if(pks.size() == 1)
|
||||
query += sqlb::escapeIdentifier(pks.front()) + "='" + rowid.toStdString() + "'";
|
||||
else
|
||||
@@ -1405,7 +1408,7 @@ QString DBBrowserDB::addRecord(const sqlb::ObjectIdentifier& tablename)
|
||||
if(!_db)
|
||||
return QString();
|
||||
|
||||
sqlb::TablePtr table = getObjectByName<sqlb::Table>(tablename);
|
||||
sqlb::TablePtr table = getTableByName(tablename);
|
||||
if(!table)
|
||||
return QString();
|
||||
|
||||
@@ -1548,7 +1551,7 @@ sqlb::StringVector DBBrowserDB::primaryKeyForEditing(const sqlb::ObjectIdentifie
|
||||
|
||||
if(pseudo_pk.empty())
|
||||
{
|
||||
sqlb::TablePtr tbl = getObjectByName<sqlb::Table>(table);
|
||||
sqlb::TablePtr tbl = getTableByName(table);
|
||||
if(tbl)
|
||||
return tbl->rowidColumns();
|
||||
} else {
|
||||
@@ -1591,14 +1594,14 @@ bool DBBrowserDB::alterTable(const sqlb::ObjectIdentifier& tablename, const sqlb
|
||||
newSchemaName = tablename.schema();
|
||||
|
||||
// When renaming the table in the current schema, check if it doesn't exist already in there
|
||||
if(tablename.name() != new_table.name() && getObjectByName(sqlb::ObjectIdentifier(newSchemaName, new_table.name())) != nullptr)
|
||||
if(tablename.name() != new_table.name() && getTableByName(sqlb::ObjectIdentifier(newSchemaName, new_table.name())) != nullptr)
|
||||
{
|
||||
lastErrorMessage = tr("A table with the name '%1' already exists in schema '%2'.").arg(QString::fromStdString(new_table.name()), QString::fromStdString(newSchemaName));
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// We're moving the table to a different schema. So check first if it doesn't already exist in the new schema.
|
||||
if(newSchemaName != tablename.schema() && getObjectByName(sqlb::ObjectIdentifier(newSchemaName, new_table.name())) != nullptr)
|
||||
if(newSchemaName != tablename.schema() && getTableByName(sqlb::ObjectIdentifier(newSchemaName, new_table.name())) != nullptr)
|
||||
{
|
||||
lastErrorMessage = tr("A table with the name '%1' already exists in schema '%2'.").arg(QString::fromStdString(new_table.name()), QString::fromStdString(newSchemaName));
|
||||
return false;
|
||||
@@ -1606,7 +1609,7 @@ bool DBBrowserDB::alterTable(const sqlb::ObjectIdentifier& tablename, const sqlb
|
||||
}
|
||||
|
||||
// Get old table schema
|
||||
sqlb::TablePtr old_table_ptr = getObjectByName<sqlb::Table>(tablename);
|
||||
sqlb::TablePtr old_table_ptr = getTableByName(tablename);
|
||||
if(old_table_ptr == nullptr)
|
||||
{
|
||||
lastErrorMessage = tr("No table with name '%1' exists in schema '%2'.").arg(QString::fromStdString(tablename.name()), QString::fromStdString(tablename.schema()));
|
||||
@@ -1735,7 +1738,7 @@ bool DBBrowserDB::alterTable(const sqlb::ObjectIdentifier& tablename, const sqlb
|
||||
if(changed_something)
|
||||
{
|
||||
updateSchema();
|
||||
old_table = *getObjectByName<sqlb::Table>(sqlb::ObjectIdentifier(tablename.schema(), new_table.name()));
|
||||
old_table = *getTableByName(sqlb::ObjectIdentifier(tablename.schema(), new_table.name()));
|
||||
}
|
||||
|
||||
// Check if there's still more work to be done or if we are finished now
|
||||
@@ -1803,53 +1806,58 @@ bool DBBrowserDB::alterTable(const sqlb::ObjectIdentifier& tablename, const sqlb
|
||||
|
||||
// Save all indices, triggers and views associated with this table because SQLite deletes them when we drop the table in the next step
|
||||
std::vector<std::string> otherObjectsSql;
|
||||
for(const auto& schema : schemata[tablename.schema()])
|
||||
{
|
||||
const auto& it = schema.second;
|
||||
|
||||
// If this object references the table and it's not the table itself save it's SQL string
|
||||
if(it->baseTable() == old_table.name() && it->type() != sqlb::Object::Types::Table)
|
||||
auto saveRelatedObjects = [old_table, track_columns, &otherObjectsSql, newSchemaName](const auto& objects) {
|
||||
for(const auto& obj : objects)
|
||||
{
|
||||
// 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->type() == sqlb::Object::Types::Index)
|
||||
const auto& it = obj.second;
|
||||
|
||||
// If this object references the table save its SQL string
|
||||
if(sqlb::getBaseTable(it) == old_table.name())
|
||||
{
|
||||
sqlb::IndexPtr idx = std::dynamic_pointer_cast<sqlb::Index>(it);
|
||||
|
||||
// Loop through all changes to the table schema. For indices only the column names are relevant, so it suffices to look at the
|
||||
// list of tracked columns
|
||||
for(const auto& from_it : track_columns)
|
||||
// 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(std::is_same<decltype(objects), decltype(objectMap::indices)>::value)
|
||||
{
|
||||
const auto& from = from_it.first;
|
||||
const auto& to = from_it.second;
|
||||
sqlb::IndexPtr idx = std::dynamic_pointer_cast<sqlb::Index>(it);
|
||||
|
||||
// Are we updating the field name or are we removing the field entirely?
|
||||
if(!to.isNull())
|
||||
// Loop through all changes to the table schema. For indices only the column names are relevant, so it suffices to look at the
|
||||
// list of tracked columns
|
||||
for(const auto& from_it : track_columns)
|
||||
{
|
||||
// We're updating the field name. So search for it in the index and replace it wherever it is found
|
||||
for(size_t i=0;i<idx->fields.size();i++)
|
||||
{
|
||||
if(idx->fields[i].name() == from.toStdString())
|
||||
idx->fields[i].setName(to.toStdString());
|
||||
}
|
||||
} else {
|
||||
// We're removing a field. So remove it from any indices, too.
|
||||
while(sqlb::removeField(idx, from.toStdString()))
|
||||
;
|
||||
}
|
||||
}
|
||||
const auto& from = from_it.first;
|
||||
const auto& to = from_it.second;
|
||||
|
||||
// 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->fields.size())
|
||||
otherObjectsSql.push_back(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
|
||||
// hope for the best.
|
||||
otherObjectsSql.push_back(it->originalSql() + ";");
|
||||
// Are we updating the field name or are we removing the field entirely?
|
||||
if(!to.isNull())
|
||||
{
|
||||
// We're updating the field name. So search for it in the index and replace it wherever it is found
|
||||
for(size_t i=0;i<idx->fields.size();i++)
|
||||
{
|
||||
if(idx->fields[i].name() == from.toStdString())
|
||||
idx->fields[i].setName(to.toStdString());
|
||||
}
|
||||
} else {
|
||||
// We're removing a field. So remove it from any indices, too.
|
||||
while(sqlb::removeField(idx, from.toStdString()))
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
// 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->fields.size())
|
||||
otherObjectsSql.push_back(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
|
||||
// hope for the best.
|
||||
otherObjectsSql.push_back(it->originalSql() + ";");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
saveRelatedObjects(schemata[tablename.schema()].tables); // We can safely pass the tables along with the views here since they never have a base table set
|
||||
saveRelatedObjects(schemata[tablename.schema()].indices);
|
||||
saveRelatedObjects(schemata[tablename.schema()].triggers);
|
||||
|
||||
// We need to disable foreign keys here. The reason is that in the next step the entire table will be dropped and there might be foreign keys
|
||||
// in other tables that reference this table. These foreign keys would then cause the drop command in the next step to fail. However, we can't
|
||||
@@ -1964,7 +1972,7 @@ void DBBrowserDB::updateSchema()
|
||||
const std::string schema_name = db_values.at(1).toStdString();
|
||||
|
||||
// Always add the schema to the map. This makes sure it's even then added when there are no objects in the database
|
||||
schemata[schema_name] = objectMap();
|
||||
objectMap& object_map = schemata[schema_name];
|
||||
|
||||
// Get a list of all the tables for the current database schema. We need to do this differently for normal databases and the temporary schema
|
||||
// because SQLite doesn't understand the "temp.sqlite_master" notation.
|
||||
@@ -1974,7 +1982,7 @@ void DBBrowserDB::updateSchema()
|
||||
else
|
||||
statement = "SELECT type,name,sql,tbl_name FROM " + sqlb::escapeIdentifier(schema_name) + ".sqlite_master;";
|
||||
|
||||
if(!executeSQL(statement, false, true, [this, schema_name](int, std::vector<QByteArray> values, std::vector<QByteArray>) -> bool {
|
||||
if(!executeSQL(statement, false, true, [this, schema_name, &object_map](int, std::vector<QByteArray> values, std::vector<QByteArray>) -> bool {
|
||||
const std::string val_type = values.at(0).toStdString();
|
||||
const std::string val_name = values.at(1).toStdString();
|
||||
std::string val_sql = values.at(2).toStdString();
|
||||
@@ -1984,51 +1992,45 @@ void DBBrowserDB::updateSchema()
|
||||
{
|
||||
val_sql.erase(std::remove(val_sql.begin(), val_sql.end(), '\r'), val_sql.end());
|
||||
|
||||
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
|
||||
return false;
|
||||
|
||||
// If parsing wasn't successful set the object name and SQL manually, so that at least the name is going to be correct
|
||||
if(!object->fullyParsed())
|
||||
if(val_type == "table" || val_type == "view")
|
||||
{
|
||||
object->setName(val_name);
|
||||
object->setOriginalSql(val_sql);
|
||||
}
|
||||
sqlb::TablePtr table;
|
||||
if(val_type == "table")
|
||||
table = sqlb::Table::parseSQL(val_sql);
|
||||
else
|
||||
table = sqlb::View::parseSQL(val_sql);
|
||||
|
||||
// For virtual tables, views, and tables we could not parse at all,
|
||||
// query the column list using the SQLite pragma to at least get
|
||||
// some information on them when our parser does not.
|
||||
if((!object->fullyParsed() && object->fieldInformation().empty()) ||
|
||||
(object->type() == sqlb::Object::Types::Table && std::dynamic_pointer_cast<sqlb::Table>(object)->isVirtual()) ||
|
||||
object->type() == sqlb::Object::Types::View)
|
||||
{
|
||||
const auto columns = queryColumnInformation(schema_name, val_name);
|
||||
if(!table->fullyParsed())
|
||||
table->setName(val_name);
|
||||
|
||||
if(object->type() == sqlb::Object::Types::Table)
|
||||
// For virtual tables, views, and tables we could not parse at all,
|
||||
// query the column list using the SQLite pragma to at least get
|
||||
// some information on them when our parser does not.
|
||||
if((!table->fullyParsed() && table->fields.empty()) || table->isVirtual())
|
||||
{
|
||||
sqlb::TablePtr tab = std::dynamic_pointer_cast<sqlb::Table>(object);
|
||||
for(const auto& column : columns)
|
||||
tab->fields.emplace_back(column.first, column.second);
|
||||
} else {
|
||||
sqlb::ViewPtr view = std::dynamic_pointer_cast<sqlb::View>(object);
|
||||
for(const auto& column : columns)
|
||||
view->fields.emplace_back(column.first, column.second);
|
||||
}
|
||||
} 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 = std::dynamic_pointer_cast<sqlb::Trigger>(object);
|
||||
trg->setTable(val_tblname);
|
||||
}
|
||||
const auto columns = queryColumnInformation(schema_name, val_name);
|
||||
|
||||
schemata[schema_name].insert({val_type, object});
|
||||
for(const auto& column : columns)
|
||||
table->fields.emplace_back(column.first, column.second);
|
||||
}
|
||||
|
||||
object_map.tables.insert({val_name, table});
|
||||
} else if(val_type == "index") {
|
||||
sqlb::IndexPtr index = sqlb::Index::parseSQL(val_sql);
|
||||
if(!index->fullyParsed())
|
||||
index->setName(val_name);
|
||||
|
||||
object_map.indices.insert({val_name, index});
|
||||
} else if(val_type == "trigger") {
|
||||
sqlb::TriggerPtr trigger = sqlb::Trigger::parseSQL(val_sql);
|
||||
trigger->setName(val_name);
|
||||
trigger->setOriginalSql(val_sql);
|
||||
|
||||
// For triggers set the name of the table the trigger operates on here because we don't have a parser for trigger statements yet.
|
||||
trigger->setTable(val_tblname);
|
||||
|
||||
object_map.triggers.insert({val_name, trigger});
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -2199,7 +2201,7 @@ std::string DBBrowserDB::generateTemporaryTableName(const std::string& schema) c
|
||||
while(true)
|
||||
{
|
||||
std::string table_name = "sqlb_temp_table_" + std::to_string(++counter);
|
||||
if(!getObjectByName(sqlb::ObjectIdentifier(schema, table_name)))
|
||||
if(!getTableByName(sqlb::ObjectIdentifier(schema, table_name)))
|
||||
return table_name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,19 @@ enum LogMessageType
|
||||
kLogMsg_ErrorLog
|
||||
};
|
||||
|
||||
using objectMap = std::multimap<std::string, sqlb::ObjectPtr>; // Maps from object type (table, index, view, trigger) to a pointer to the object representation
|
||||
struct objectMap
|
||||
{
|
||||
// These map from object name to object pointer
|
||||
std::map<std::string, sqlb::TablePtr> tables; // This stores tables AND views
|
||||
std::map<std::string, sqlb::IndexPtr> indices;
|
||||
std::map<std::string, sqlb::TriggerPtr> triggers;
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
return tables.empty() && indices.empty() && triggers.empty();
|
||||
}
|
||||
};
|
||||
|
||||
using schemaMap = std::map<std::string, objectMap>; // Maps from the schema name (main, temp, attached schemas) to the object map for that schema
|
||||
|
||||
int collCompare(void* pArg, int sizeA, const void* sA, int sizeB, const void* sB);
|
||||
@@ -209,15 +221,28 @@ public:
|
||||
*/
|
||||
bool alterTable(const sqlb::ObjectIdentifier& tablename, const sqlb::Table& new_table, AlterTableTrackColumns track_columns, std::string newSchemaName = std::string());
|
||||
|
||||
template<typename T = sqlb::Object>
|
||||
const std::shared_ptr<T> getObjectByName(const sqlb::ObjectIdentifier& name) const
|
||||
const sqlb::TablePtr getTableByName(const sqlb::ObjectIdentifier& name) const
|
||||
{
|
||||
for(auto& it : schemata.at(name.schema()))
|
||||
{
|
||||
if(it.second->name() == name.name())
|
||||
return std::dynamic_pointer_cast<T>(it.second);
|
||||
}
|
||||
return std::shared_ptr<T>();
|
||||
const auto& schema = schemata.at(name.schema());
|
||||
if(schema.tables.count(name.name()))
|
||||
return schema.tables.at(name.name());
|
||||
return sqlb::TablePtr{};
|
||||
}
|
||||
|
||||
const sqlb::IndexPtr getIndexByName(const sqlb::ObjectIdentifier& name) const
|
||||
{
|
||||
const auto& schema = schemata.at(name.schema());
|
||||
if(schema.indices.count(name.name()))
|
||||
return schema.indices.at(name.name());
|
||||
return sqlb::IndexPtr{};
|
||||
}
|
||||
|
||||
const sqlb::TriggerPtr getTriggerByName(const sqlb::ObjectIdentifier& name) const
|
||||
{
|
||||
const auto& schema = schemata.at(name.schema());
|
||||
if(schema.triggers.count(name.name()))
|
||||
return schema.triggers.at(name.name());
|
||||
return sqlb::TriggerPtr{};
|
||||
}
|
||||
|
||||
bool isOpen() const;
|
||||
|
||||
@@ -127,15 +127,14 @@ void SqliteTableModel::setQuery(const sqlb::Query& query)
|
||||
// Save the query
|
||||
m_query = query;
|
||||
|
||||
// Retrieve field names and types
|
||||
sqlb::ObjectPtr object = m_db.getObjectByName(query.table());
|
||||
|
||||
// Set the row id columns
|
||||
m_table_of_query = std::dynamic_pointer_cast<sqlb::Table>(object);
|
||||
if(m_table_of_query) // It is a table
|
||||
m_table_of_query = m_db.getTableByName(query.table());
|
||||
if(!m_table_of_query->isView())
|
||||
{
|
||||
// It is a table
|
||||
m_query.setRowIdColumns(m_table_of_query->rowidColumns());
|
||||
} else {
|
||||
// It is a view
|
||||
if(m_query.rowIdColumns().empty())
|
||||
m_query.setRowIdColumn("_rowid_");
|
||||
}
|
||||
@@ -143,10 +142,10 @@ void SqliteTableModel::setQuery(const sqlb::Query& query)
|
||||
m_headers.push_back(sqlb::joinStringVector(m_query.rowIdColumns(), ","));
|
||||
|
||||
// Store field names and affinity data types
|
||||
for(const auto& fld : object->fieldInformation())
|
||||
for(const auto& fld : m_table_of_query->fields)
|
||||
{
|
||||
m_headers.push_back(fld.name);
|
||||
m_vDataTypes.push_back(fld.affinity);
|
||||
m_headers.push_back(fld.name());
|
||||
m_vDataTypes.push_back(fld.affinity());
|
||||
}
|
||||
|
||||
// Tell the query object about the column names
|
||||
@@ -960,7 +959,7 @@ bool SqliteTableModel::isEditable(const QModelIndex& index) const
|
||||
return false;
|
||||
if(!m_db.isOpen())
|
||||
return false;
|
||||
if(!m_table_of_query && !m_query.hasCustomRowIdColumn())
|
||||
if((!m_table_of_query || m_table_of_query->isView()) && !m_query.hasCustomRowIdColumn())
|
||||
return false;
|
||||
|
||||
// Extra check when the index parameter is set and pointing to a generated column in a table
|
||||
|
||||
Reference in New Issue
Block a user