From 631979c330bc356b4ef3ef75fb2b405604be4d6d Mon Sep 17 00:00:00 2001 From: Martin Kleusberg Date: Mon, 17 Aug 2015 00:17:48 +0200 Subject: [PATCH] Improve escpaing support When generating SQL statements properly escape all identifiers, even those containing backticks which apparently are allowed inside identifiers in SQLite. See issue #387. --- src/ColumnDisplayFormatDialog.cpp | 19 +++++----- src/CreateIndexDialog.cpp | 12 +++--- src/DbStructureModel.cpp | 2 +- src/EditTableDialog.cpp | 20 ++++++---- src/ExportCsvDialog.cpp | 2 +- src/ImportCsvDialog.cpp | 4 +- src/MainWindow.cpp | 4 +- src/SqlExecutionArea.cpp | 2 +- src/VacuumDialog.cpp | 2 +- src/sqlitedb.cpp | 61 +++++++++++++++++++------------ src/sqlitetablemodel.cpp | 12 ++++-- src/sqlitetypes.cpp | 15 +++++--- src/sqlitetypes.h | 2 + 13 files changed, 95 insertions(+), 62 deletions(-) diff --git a/src/ColumnDisplayFormatDialog.cpp b/src/ColumnDisplayFormatDialog.cpp index 31041a40..b0a5f0ca 100644 --- a/src/ColumnDisplayFormatDialog.cpp +++ b/src/ColumnDisplayFormatDialog.cpp @@ -1,5 +1,6 @@ #include "ColumnDisplayFormatDialog.h" #include "ui_ColumnDisplayFormatDialog.h" +#include "sqlitetypes.h" ColumnDisplayFormatDialog::ColumnDisplayFormatDialog(const QString& colname, QString current_format, QWidget* parent) : QDialog(parent), @@ -56,21 +57,21 @@ void ColumnDisplayFormatDialog::updateSqlCode() QString format = ui->comboDisplayFormat->itemData(ui->comboDisplayFormat->currentIndex()).toString(); #endif if(format == "default") - ui->editDisplayFormat->setText("`" + column_name + "`"); + ui->editDisplayFormat->setText(sqlb::escapeIdentifier(column_name)); else if(format == "lower") - ui->editDisplayFormat->setText("lower(`" + column_name + "`)"); + ui->editDisplayFormat->setText("lower(" + sqlb::escapeIdentifier(column_name) + ")"); else if(format == "upper") - ui->editDisplayFormat->setText("upper(`" + column_name + "`)"); + ui->editDisplayFormat->setText("upper(" + sqlb::escapeIdentifier(column_name) + ")"); else if(format == "epoch") - ui->editDisplayFormat->setText("datetime(`" + column_name + "`, 'unixepoch')"); + ui->editDisplayFormat->setText("datetime(" + sqlb::escapeIdentifier(column_name) + ", 'unixepoch')"); else if(format == "julian") - ui->editDisplayFormat->setText("datetime(`" + column_name + "`)"); + ui->editDisplayFormat->setText("datetime(" + sqlb::escapeIdentifier(column_name) + ")"); else if(format == "round") - ui->editDisplayFormat->setText("round(`" + column_name + "`)"); + ui->editDisplayFormat->setText("round(" + sqlb::escapeIdentifier(column_name) + ")"); else if(format == "hex") - ui->editDisplayFormat->setText("printf('%x', `" + column_name + "`)"); + ui->editDisplayFormat->setText("printf('%x', " + sqlb::escapeIdentifier(column_name) + ")"); else if(format == "octal") - ui->editDisplayFormat->setText("printf('%o', `" + column_name + "`)"); + ui->editDisplayFormat->setText("printf('%o', " + sqlb::escapeIdentifier(column_name) + ")"); else if(format == "exponent") - ui->editDisplayFormat->setText("printf('%e', `" + column_name + "`)"); + ui->editDisplayFormat->setText("printf('%e', " + sqlb::escapeIdentifier(column_name) + ")"); } diff --git a/src/CreateIndexDialog.cpp b/src/CreateIndexDialog.cpp index fc656e04..0ed1e937 100644 --- a/src/CreateIndexDialog.cpp +++ b/src/CreateIndexDialog.cpp @@ -57,7 +57,7 @@ void CreateIndexDialog::tableChanged(const QString& new_table) void CreateIndexDialog::checkInput() { bool valid = true; - if(ui->editIndexName->text().isEmpty() || ui->editIndexName->text().contains("`")) + if(ui->editIndexName->text().isEmpty()) valid = false; int num_columns = 0; @@ -74,17 +74,17 @@ void CreateIndexDialog::checkInput() void CreateIndexDialog::accept() { - QString sql = QString("CREATE %1 INDEX `%2` ON `%3` (") + QString sql = QString("CREATE %1 INDEX %2 ON %3 (") .arg(ui->checkIndexUnique->isChecked() ? "UNIQUE" : "") - .arg(ui->editIndexName->text()) - .arg(ui->comboTableName->currentText()); + .arg(sqlb::escapeIdentifier(ui->editIndexName->text())) + .arg(sqlb::escapeIdentifier(ui->comboTableName->currentText())); for(int i=0; i < ui->tableIndexColumns->rowCount(); ++i) { if(ui->tableIndexColumns->item(i, 1)->data(Qt::CheckStateRole) == Qt::Checked) { - sql.append(QString("`%1` %2,") - .arg(ui->tableIndexColumns->item(i, 0)->text()) + sql.append(QString("%1 %2,") + .arg(sqlb::escapeIdentifier(ui->tableIndexColumns->item(i, 0)->text())) .arg(qobject_cast(ui->tableIndexColumns->cellWidget(i, 2))->currentText())); } } diff --git a/src/DbStructureModel.cpp b/src/DbStructureModel.cpp index 7d4e6f55..f07945f0 100644 --- a/src/DbStructureModel.cpp +++ b/src/DbStructureModel.cpp @@ -218,7 +218,7 @@ QMimeData* DbStructureModel::mimeData(const QModelIndexList& indices) const tableModel.setTable(data(index.sibling(index.row(), 0), Qt::DisplayRole).toString()); for(int i=0; i < tableModel.rowCount(); ++i) { - QString insertStatement = "INSERT INTO `" + data(index.sibling(index.row(), 0), Qt::DisplayRole).toString() + "` VALUES("; + QString insertStatement = "INSERT INTO " + sqlb::escapeIdentifier(data(index.sibling(index.row(), 0), Qt::DisplayRole).toString()) + " VALUES("; for(int j=1; j < tableModel.columnCount(); ++j) insertStatement += QString("'%1',").arg(tableModel.data(tableModel.index(i, j)).toString()); insertStatement.chop(1); diff --git a/src/EditTableDialog.cpp b/src/EditTableDialog.cpp index 26272f75..b06e513e 100644 --- a/src/EditTableDialog.cpp +++ b/src/EditTableDialog.cpp @@ -16,7 +16,7 @@ EditTableDialog::EditTableDialog(DBBrowserDB* db, const QString& tableName, bool curTable(tableName), m_table(tableName), m_bNewTable(createTable), - m_sRestorePointName(QString("edittable_%1_save_%2").arg(curTable).arg(QDateTime::currentMSecsSinceEpoch())) + m_sRestorePointName(sqlb::escapeIdentifier(QString("edittable_%1_save_%2").arg(curTable).arg(QDateTime::currentMSecsSinceEpoch()))) { // Create UI ui->setupUi(this); @@ -173,7 +173,7 @@ void EditTableDialog::checkInput() { QString normTableName = ui->editTableName->text().trimmed(); bool valid = true; - if(normTableName.isEmpty() || normTableName.contains("`")) + if(normTableName.isEmpty()) valid = false; if(ui->treeWidget->topLevelItemCount() == 0) valid = false; @@ -272,7 +272,10 @@ void EditTableDialog::itemChanged(QTreeWidgetItem *item, int column) // we need to check for this case and cancel here. Maybe we can think of some way to modify the INSERT INTO ... SELECT statement // to at least replace all troublesome NULL values by the default value SqliteTableModel m(this, pdb); - m.setQuery(QString("SELECT COUNT(`%1`) FROM `%2` WHERE `%3` IS NULL;").arg(pdb->getObjectByName(curTable).table.rowidColumn()).arg(curTable).arg(field->name())); + m.setQuery(QString("SELECT COUNT(%1) FROM %2 WHERE %3 IS NULL;") + .arg(sqlb::escapeIdentifier(pdb->getObjectByName(curTable).table.rowidColumn())) + .arg(sqlb::escapeIdentifier(curTable)) + .arg(sqlb::escapeIdentifier(field->name()))); if(m.data(m.index(0, 0)).toInt() > 0) { // There is a NULL value, so print an error message, uncheck the combobox, and return here @@ -296,7 +299,10 @@ void EditTableDialog::itemChanged(QTreeWidgetItem *item, int column) if(!m_bNewTable) { SqliteTableModel m(this, pdb); - m.setQuery(QString("SELECT COUNT(*) FROM `%1` WHERE `%2` <> CAST(`%3` AS INTEGER);").arg(curTable).arg(field->name()).arg(field->name())); + m.setQuery(QString("SELECT COUNT(*) FROM %1 WHERE %2 <> CAST(%3 AS INTEGER);") + .arg(sqlb::escapeIdentifier(curTable)) + .arg(sqlb::escapeIdentifier(field->name())) + .arg(sqlb::escapeIdentifier(field->name()))); if(m.data(m.index(0, 0)).toInt() > 0) { // There is a non-integer value, so print an error message, uncheck the combobox, and return here @@ -337,14 +343,14 @@ void EditTableDialog::itemChanged(QTreeWidgetItem *item, int column) { // Because our renameColumn() function fails when setting a column to unique when it already contains the same values SqliteTableModel m(this, pdb); - m.setQuery(QString("SELECT COUNT(`%2`) FROM `%1`;").arg(curTable).arg(field->name())); + m.setQuery(QString("SELECT COUNT(%2) FROM %1;").arg(sqlb::escapeIdentifier(curTable)).arg(sqlb::escapeIdentifier(field->name()))); int rowcount = m.data(m.index(0, 0)).toInt(); - m.setQuery(QString("SELECT COUNT(distinct `%2`) FROM `%1`;").arg(curTable).arg(field->name())); + m.setQuery(QString("SELECT COUNT(DISTINCT %2) FROM %1;").arg(sqlb::escapeIdentifier(curTable)).arg(sqlb::escapeIdentifier(field->name()))); int uniquecount = m.data(m.index(0, 0)).toInt(); if(rowcount != uniquecount) { // There is a NULL value, so print an error message, uncheck the combobox, and return here - QMessageBox::information(this, qApp->applicationName(), tr("Column `%1` has no unique data.\n").arg(field->name()) + QMessageBox::information(this, qApp->applicationName(), tr("Column '%1'' has no unique data.\n").arg(field->name()) + tr("This makes it impossible to set this flag. Please change the table data first.")); item->setCheckState(column, Qt::Unchecked); return; diff --git a/src/ExportCsvDialog.cpp b/src/ExportCsvDialog.cpp index 293c698b..57c65c6f 100644 --- a/src/ExportCsvDialog.cpp +++ b/src/ExportCsvDialog.cpp @@ -210,7 +210,7 @@ void ExportCsvDialog::accept() { // if we are called from execute sql tab, query is already set // and we only export 1 select - QString sQuery = QString("SELECT * from `%1`;").arg(selectedItems.at(i)->text()); + QString sQuery = QString("SELECT * FROM %1;").arg(sqlb::escapeIdentifier(selectedItems.at(i)->text())); exportQuery(sQuery, filenames.at(i)); } diff --git a/src/ImportCsvDialog.cpp b/src/ImportCsvDialog.cpp index 2f3b75fe..2923b4e7 100644 --- a/src/ImportCsvDialog.cpp +++ b/src/ImportCsvDialog.cpp @@ -223,7 +223,7 @@ void ImportCsvDialog::accept() it != csv.csv().end(); ++it) { - QString sql = QString("INSERT INTO `%1` VALUES(").arg(ui->editName->text()); + QString sql = QString("INSERT INTO %1 VALUES(").arg(sqlb::escapeIdentifier(ui->editName->text())); QStringList insertlist; for(QStringList::const_iterator jt = it->begin(); jt != it->end(); ++jt) @@ -321,7 +321,7 @@ void ImportCsvDialog::updatePreview() void ImportCsvDialog::checkInput() { bool valid = true; - if(ui->editName->text().isEmpty() || ui->editName->text().contains("`")) + if(ui->editName->text().isEmpty()) valid = false; ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(valid); diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 9b5e7ee2..33f4d6e0 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -348,7 +348,7 @@ void MainWindow::populateTable(const QString& tablename) v.push_back(format); only_defaults = false; } else { - v.push_back("`" + db.getObjectByName(tablename).table.fields().at(i)->name() + "`"); + v.push_back(sqlb::escapeIdentifier(db.getObjectByName(tablename).table.fields().at(i)->name())); } } if(only_defaults) @@ -652,7 +652,7 @@ void MainWindow::deleteObject() QMessageBox::Yes, QMessageBox::No | QMessageBox::Default | QMessageBox::Escape) == QMessageBox::Yes) { // Delete the table - QString statement = QString("DROP %1 `%2`;").arg(type.toUpper()).arg(table); + QString statement = QString("DROP %1 %2;").arg(type.toUpper()).arg(sqlb::escapeIdentifier(table)); if(!db.executeSQL( statement)) { QString error = tr("Error: could not delete the %1. Message from database engine:\n%2").arg(type).arg(db.lastErrorMessage); diff --git a/src/SqlExecutionArea.cpp b/src/SqlExecutionArea.cpp index ec8b024a..83123e66 100644 --- a/src/SqlExecutionArea.cpp +++ b/src/SqlExecutionArea.cpp @@ -100,7 +100,7 @@ void SqlExecutionArea::saveAsView() return; // Create the view - if(db->executeSQL(QString("CREATE VIEW `%1` AS %2;").arg(name).arg(model->query()))) + if(db->executeSQL(QString("CREATE VIEW %1 AS %2;").arg(sqlb::escapeIdentifier(name)).arg(model->query()))) QMessageBox::information(this, qApp->applicationName(), tr("View successfully created.")); else QMessageBox::warning(this, qApp->applicationName(), tr("Error creating view: %1").arg(db->lastErrorMessage)); diff --git a/src/VacuumDialog.cpp b/src/VacuumDialog.cpp index 1252750b..d81b0f77 100644 --- a/src/VacuumDialog.cpp +++ b/src/VacuumDialog.cpp @@ -55,7 +55,7 @@ void VacuumDialog::accept() // No, so execute a vacuum command for each selected object individually QList selection = ui->treeSelectedObjects->selectedItems(); foreach(QTreeWidgetItem* item, selection) - db->executeSQL(QString("VACUUM `%1`;").arg(item->text(0)), false); + db->executeSQL(QString("VACUUM %1;").arg(sqlb::escapeIdentifier(item->text(0))), false); } QApplication::restoreOverrideCursor(); diff --git a/src/sqlitedb.cpp b/src/sqlitedb.cpp index 55d91f4c..92db54d3 100644 --- a/src/sqlitedb.cpp +++ b/src/sqlitedb.cpp @@ -164,14 +164,14 @@ bool DBBrowserDB::attach(const QString& filename, QString attach_as) // Attach database QString key; if(cipher) key = cipher->password(); - if(!executeSQL(QString("ATTACH '%1' AS `%2` KEY '%3'").arg(filename).arg(attach_as).arg(key), false)) + if(!executeSQL(QString("ATTACH '%1' AS %2 KEY '%3'").arg(filename).arg(sqlb::escapeIdentifier(attach_as)).arg(key), false)) { QMessageBox::warning(0, qApp->applicationName(), lastErrorMessage); return false; } if(cipher && cipher->pageSize() != 1024) { - if(!executeSQL(QString("PRAGMA `%1`.cipher_page_size = %2").arg(attach_as).arg(cipher->pageSize()), false)) + if(!executeSQL(QString("PRAGMA %1.cipher_page_size = %2").arg(sqlb::escapeIdentifier(attach_as)).arg(cipher->pageSize()), false)) { QMessageBox::warning(0, qApp->applicationName(), lastErrorMessage); return false; @@ -179,7 +179,7 @@ bool DBBrowserDB::attach(const QString& filename, QString attach_as) } #else // Attach database - if(!executeSQL(QString("ATTACH '%1' AS `%2`").arg(filename).arg(attach_as), false)) + if(!executeSQL(QString("ATTACH '%1' AS %2").arg(filename).arg(sqlb::escapeIdentifier(attach_as)), false)) { QMessageBox::warning(0, qApp->applicationName(), lastErrorMessage); return false; @@ -453,7 +453,7 @@ bool DBBrowserDB::dump(const QString& filename, // get columns QStringList cols(it->table.fieldNames()); - QString sQuery = QString("SELECT * FROM `%1`;").arg(it->getTableName()); + QString sQuery = QString("SELECT * FROM %1;").arg(sqlb::escapeIdentifier(it->getTableName())); QByteArray utf8Query = sQuery.toUtf8(); sqlite3_stmt *stmt; QString lineSep(QString(")%1\n").arg(insertNewSyntx?',':';')); @@ -470,7 +470,7 @@ bool DBBrowserDB::dump(const QString& filename, if (!insertNewSyntx || !counter) { - stream << "INSERT INTO `" << it->getTableName() << '`'; + stream << "INSERT INTO " << sqlb::escapeIdentifier(it->getTableName()); if (insertColNames) stream << " (" << cols.join(",") << ")"; stream << " VALUES ("; @@ -650,7 +650,11 @@ bool DBBrowserDB::executeMultiSQL(const QString& statement, bool dirty, bool log bool DBBrowserDB::getRow(const QString& sTableName, const QString& rowid, QList& rowdata) { - QString sQuery = QString("SELECT * FROM `%1` WHERE `%2`='%3';").arg(sTableName).arg(getObjectByName(sTableName).table.rowidColumn()).arg(rowid); + QString sQuery = QString("SELECT * FROM %1 WHERE %2='%3';") + .arg(sqlb::escapeIdentifier(sTableName)) + .arg(sqlb::escapeIdentifier(getObjectByName(sTableName).table.rowidColumn())) + .arg(rowid); + QByteArray utf8Query = sQuery.toUtf8(); sqlite3_stmt *stmt; bool ret = false; @@ -684,7 +688,7 @@ bool DBBrowserDB::getRow(const QString& sTableName, const QString& rowid, QList< QString DBBrowserDB::max(const sqlb::Table& t, sqlb::FieldPtr field) const { - QString sQuery = QString("SELECT MAX(CAST(`%2` AS INTEGER)) FROM `%1`;").arg(t.name()).arg(field->name()); + QString sQuery = QString("SELECT MAX(CAST(%2 AS INTEGER)) FROM %1;").arg(sqlb::escapeIdentifier(t.name())).arg(sqlb::escapeIdentifier(field->name())); QByteArray utf8Query = sQuery.toUtf8(); sqlite3_stmt *stmt; QString ret = "0"; @@ -708,7 +712,7 @@ QString DBBrowserDB::max(const sqlb::Table& t, sqlb::FieldPtr field) const QString DBBrowserDB::emptyInsertStmt(const sqlb::Table& t, const QString& pk_value) const { - QString stmt = QString("INSERT INTO `%1`").arg(t.name()); + QString stmt = QString("INSERT INTO %1").arg(sqlb::escapeIdentifier(t.name())); QStringList vals; QStringList fields; @@ -749,12 +753,14 @@ QString DBBrowserDB::emptyInsertStmt(const sqlb::Table& t, const QString& pk_val } if(fields.empty()) - stmt.append(" DEFAULT VALUES;"); - else { - stmt.append("(`"); - stmt.append(fields.join("`,`")); - stmt.append("`) VALUES ("); + stmt.append(" DEFAULT VALUES;"); + } else { + stmt.append("("); + foreach(const QString& f, fields) + stmt.append(sqlb::escapeIdentifier(f) + ","); + stmt.chop(1); + stmt.append(") VALUES ("); stmt.append(vals.join(",")); stmt.append(");"); } @@ -797,7 +803,10 @@ bool DBBrowserDB::deleteRecord(const QString& table, const QString& rowid) if (!isOpen()) return false; bool ok = false; - QString statement = QString("DELETE FROM `%1` WHERE `%2`='%3';").arg(table).arg(getObjectByName(table).table.rowidColumn()).arg(rowid); + QString statement = QString("DELETE FROM %1 WHERE %2='%3';") + .arg(sqlb::escapeIdentifier(table)) + .arg(sqlb::escapeIdentifier(getObjectByName(table).table.rowidColumn())) + .arg(rowid); if(executeSQL(statement)) ok = true; else @@ -810,7 +819,11 @@ bool DBBrowserDB::updateRecord(const QString& table, const QString& column, cons { if (!isOpen()) return false; - QString sql = QString("UPDATE `%1` SET `%2`=? WHERE `%3`='%4';").arg(table).arg(column).arg(getObjectByName(table).table.rowidColumn()).arg(rowid); + 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(rowid); logSQL(sql, kLogMsg_App); setRestorePoint(); @@ -865,7 +878,7 @@ bool DBBrowserDB::createTable(const QString& name, const sqlb::FieldVector& stru bool DBBrowserDB::addColumn(const QString& tablename, const sqlb::FieldPtr& field) { - QString sql = QString("ALTER TABLE `%1` ADD COLUMN %2").arg(tablename).arg(field->toString()); + QString sql = QString("ALTER TABLE %1 ADD COLUMN %2").arg(sqlb::escapeIdentifier(tablename)).arg(field->toString()); // Execute it and update the schema bool result = executeSQL(sql); @@ -880,9 +893,9 @@ bool DBBrowserDB::renameColumn(const QString& tablename, const QString& name, sq // function can be changed to executing something like this: //QString sql; //if(to.isNull()) - // sql = QString("ALTER TABLE `%1` DROP COLUMN `%2`;").arg(table).arg(column); + // sql = QString("ALTER TABLE %1 DROP COLUMN %2;").arg(sqlb::escapeIdentifier(table)).arg(sqlb::escapeIdentifier(column)); //else - // sql = QString("ALTER TABLE `%1` MODIFY `%2` %3").arg(tablename).arg(to).arg(type); // This is wrong... + // sql = QString("ALTER TABLE %1 MODIFY %2 %3").arg(sqlb::escapeIdentifier(tablename)).arg(sqlb::escapeIdentifier(to)).arg(type); // This is wrong... //return executeSQL(sql); // Collect information on the current DB layout @@ -925,7 +938,7 @@ bool DBBrowserDB::renameColumn(const QString& tablename, const QString& name, sq newSchema.removeField(name); for(int i=0;iname())); + select_cols.append(sqlb::escapeIdentifier(newSchema.fields().at(i)->name()) + ','); select_cols.chop(1); // remove last comma } else { // We want to modify it @@ -938,7 +951,7 @@ bool DBBrowserDB::renameColumn(const QString& tablename, const QString& name, sq // Get names of fields to select from old table now - after the field has been moved and before it might be renamed for(int i=0;iname())); + select_cols.append(sqlb::escapeIdentifier(newSchema.fields().at(i)->name()) + ','); select_cols.chop(1); // remove last comma // Modify field @@ -956,7 +969,7 @@ bool DBBrowserDB::renameColumn(const QString& tablename, const QString& name, sq } // Copy the data from the old table to the new one - if(!executeSQL(QString("INSERT INTO sqlitebrowser_rename_column_new_table SELECT %1 FROM `%2`;").arg(select_cols).arg(tablename))) + if(!executeSQL(QString("INSERT INTO sqlitebrowser_rename_column_new_table SELECT %1 FROM %2;").arg(select_cols).arg(sqlb::escapeIdentifier(tablename)))) { QString error(tr("renameColumn: copying data to new table failed. DB says:\n%1").arg(lastErrorMessage)); qWarning() << error; @@ -979,7 +992,7 @@ bool DBBrowserDB::renameColumn(const QString& tablename, const QString& name, sq setPragma("foreign_keys", "0"); // Delete the old table - if(!executeSQL(QString("DROP TABLE `%1`;").arg(tablename))) + if(!executeSQL(QString("DROP TABLE %1;").arg(sqlb::escapeIdentifier(tablename)))) { QString error(tr("renameColumn: deleting old table failed. DB says: %1").arg(lastErrorMessage)); qWarning() << error; @@ -1022,7 +1035,7 @@ bool DBBrowserDB::renameColumn(const QString& tablename, const QString& name, sq bool DBBrowserDB::renameTable(const QString& from_table, const QString& to_table) { - QString sql = QString("ALTER TABLE `%1` RENAME TO `%2`").arg(from_table, to_table); + QString sql = QString("ALTER TABLE %1 RENAME TO %2").arg(sqlb::escapeIdentifier(from_table)).arg(sqlb::escapeIdentifier(to_table)); if(!executeSQL(sql)) { QString error = tr("Error renaming table '%1' to '%2'." @@ -1129,7 +1142,7 @@ void DBBrowserDB::updateSchema( ) { (*it).table = sqlb::Table::parseSQL((*it).getsql()).first; } else if((*it).gettype() == "view") { - statement = QString("PRAGMA TABLE_INFO(`%1`);").arg((*it).getname()); + 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); diff --git a/src/sqlitetablemodel.cpp b/src/sqlitetablemodel.cpp index 9abdc824..08aa1749 100644 --- a/src/sqlitetablemodel.cpp +++ b/src/sqlitetablemodel.cpp @@ -69,7 +69,7 @@ void SqliteTableModel::setTable(const QString& table, const QVector& di if(!allOk) { - QString sColumnQuery = QString::fromUtf8("SELECT * FROM `%1`;").arg(table); + QString sColumnQuery = QString::fromUtf8("SELECT * FROM %1;").arg(sqlb::escapeIdentifier(table)); m_headers.push_back("rowid"); m_headers.append(getColumns(sColumnQuery, m_vDataTypes)); } @@ -484,7 +484,7 @@ void SqliteTableModel::buildQuery() column = QString("col%1").arg(i.key()); else column = m_headers.at(i.key()); - where.append(QString(" AND `%1` %2").arg(column).arg(i.value())); + where.append(QString(" AND %1 %2").arg(sqlb::escapeIdentifier(column)).arg(i.value())); } } @@ -498,7 +498,13 @@ void SqliteTableModel::buildQuery() selector.chop(1); } - QString sql = QString("SELECT `%1`,%2 FROM `%3` %4 ORDER BY `%5` %6").arg(m_headers.at(0)).arg(selector).arg(m_sTable).arg(where).arg(m_headers.at(m_iSortColumn)).arg(m_sSortOrder); + QString sql = QString("SELECT %1,%2 FROM %3 %4 ORDER BY %5 %6") + .arg(sqlb::escapeIdentifier(m_headers.at(0))) + .arg(selector) + .arg(sqlb::escapeIdentifier(m_sTable)) + .arg(where) + .arg(sqlb::escapeIdentifier(m_headers.at(m_iSortColumn))) + .arg(m_sSortOrder); setQuery(sql, true); } diff --git a/src/sqlitetypes.cpp b/src/sqlitetypes.cpp index 0f999bac..855bb6c8 100644 --- a/src/sqlitetypes.cpp +++ b/src/sqlitetypes.cpp @@ -9,6 +9,11 @@ namespace sqlb { QStringList Field::Datatypes = QStringList() << "INTEGER" << "TEXT" << "BLOB" << "REAL" << "NUMERIC"; +QString escapeIdentifier(QString id) +{ + return '`' + id.replace('`', "``") + '`'; +} + bool ForeignKeyClause::isSet() const { return m_override.size() || m_table.size(); @@ -22,13 +27,13 @@ QString ForeignKeyClause::toString() const if(m_override.size()) return m_override; - QString result = "`" + m_table + "`"; + QString result = escapeIdentifier(m_table); if(m_columns.size()) { result += "("; foreach(const QString& column, m_columns) - result += "`" + column + "`,"; + result += escapeIdentifier(column) + ','; result.chop(1); // Remove last comma result += ")"; } @@ -46,7 +51,7 @@ void ForeignKeyClause::setFromString(const QString& fk) QString Field::toString(const QString& indent, const QString& sep) const { - QString str = indent + '`' + m_name + '`' + sep + m_type; + QString str = indent + escapeIdentifier(m_name) + sep + m_type; if(m_notnull) str += " NOT NULL"; if(!m_defaultvalue.isEmpty()) @@ -204,7 +209,7 @@ QPair Table::parseSQL(const QString &sSQL) QString Table::sql() const { - QString sql = QString("CREATE TABLE `%1` (\n").arg(m_name); + QString sql = QString("CREATE TABLE %1 (\n").arg(escapeIdentifier(m_name)); sql += fieldList().join(",\n"); @@ -230,7 +235,7 @@ QString Table::sql() const foreach(FieldPtr f, m_fields) { if(f->foreignKey().isSet()) - sql += QString(",\n\tFOREIGN KEY(`%1`) REFERENCES %2").arg(f->name()).arg(f->foreignKey().toString()); + sql += QString(",\n\tFOREIGN KEY(%1) REFERENCES %2").arg(escapeIdentifier(f->name())).arg(f->foreignKey().toString()); } sql += "\n)"; diff --git a/src/sqlitetypes.h b/src/sqlitetypes.h index b3269efd..c65956da 100644 --- a/src/sqlitetypes.h +++ b/src/sqlitetypes.h @@ -12,6 +12,8 @@ namespace sqlb { +QString escapeIdentifier(QString id); + class ForeignKeyClause { public: