Estrutura do Projeto
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlRecord>
#include <QSqlError>
class MainWindow : public QMainWindow {
Q_OBJECT
public:
explicit MainWindow(QWidget* parent = nullptr);
~MainWindow();
private:
bool configurarBaseDados();
QSqlDatabase m_dbConn;
};
#endif
Implementação com Qt SQL
A estratégia consiste em comparar a estrutura de tabelas esperada (definida como string SQL de criação) com a estrutura atual no banco de dados. Se houverr colunas extras na definição esperada, elas são adicionadas via ALTER TABLE.
#include "mainwindow.h"
#include <QApplication>
#include <QDir>
#include <QDebug>
// Definições esperadas das tabelas (versão atual)
const QStringList TABELAS_ESPERADAS = {
"CREATE TABLE usuarios(id INTEGER, idade INTEGER, nome VARCHAR, cargo VARCHAR)",
"CREATE TABLE produtos(id INTEGER, quantidade INTEGER, descricao VARCHAR, preco REAL)"
};
const QStringList NOMES_TABELAS = { "usuarios", "produtos" };
MainWindow::MainWindow(QWidget* parent)
: QMainWindow(parent)
{
configurarBaseDados();
}
MainWindow::~MainWindow() {}
bool MainWindow::configurarBaseDados()
{
m_dbConn = QSqlDatabase::addDatabase("QSQLITE", "conexao_principal");
QString dirDados = QApplication::applicationDirPath() + "/dados";
QString caminhoBanco = dirDados + "/app.db";
QDir dir(dirDados);
if (!dir.exists()) {
dir.mkpath(dirDados);
}
m_dbConn.setDatabaseName(caminhoBanco);
if (!m_dbConn.open()) {
qCritical() << "Falha ao abrir banco:" << m_dbConn.lastError().text();
return false;
}
QStringList tabelasExistentes = m_dbConn.tables();
QSqlQuery query(m_dbConn);
for (int idx = 0; idx < NOMES_TABELAS.size(); ++idx) {
QString nomeTabela = NOMES_TABELAS[idx];
QString sqlCriacao = TABELAS_ESPERADAS[idx];
if (tabelasExistentes.contains(nomeTabela)) {
// Obter colunas atuais da tabela
QString sqlSelect = QString("SELECT * FROM %1 LIMIT 1").arg(nomeTabela);
if (!query.exec(sqlSelect)) {
qWarning() << "Erro ao consultar:" << query.lastError().text();
continue;
}
QStringList colunasAtuais;
QSqlRecord registro = query.record();
for (int c = 0; c < registro.count(); ++c) {
colunasAtuais.append(registro.fieldName(c));
}
// Extrair definição de colunas do SQL esperado
QString parteColunas = sqlCriacao.mid(
sqlCriacao.indexOf("(") + 1,
sqlCriacao.indexOf(")") - sqlCriacao.indexOf("(") - 1
);
parteColunas.remove("\n");
QStringList definicaoColunas = parteColunas.split(",");
// Verificar colunas que precisam ser adicionadas
for (int k = colunasAtuais.size(); k < definicaoColunas.size(); ++k) {
QString defColuna = definicaoColunas[k].trimmed();
if (!defColuna.isEmpty()) {
QString nomeColuna = defColuna.split(" ").first();
if (!colunasAtuais.contains(nomeColuna)) {
QString sqlAlter = QString("ALTER TABLE %1 ADD COLUMN %2")
.arg(nomeTabela)
.arg(defColuna.trimmed());
qDebug() << "Executando:" << sqlAlter;
if (!query.exec(sqlAlter)) {
qWarning() << "Erro ao adicionar coluna:" << query.lastError().text();
}
}
}
}
} else {
// Criar tabela do zero
if (!query.exec(sqlCriacao)) {
qWarning() << "Erro ao criar tabela:" << query.lastError().text();
}
}
}
return true;
}
Abordagem Alternativa com API nativa do SQLite3
Para casos onde se prefere ou necessita usar a API C do SQLite diretamente, o mesmo princípio de comparação de colunas pode ser aplicado usando sqlite3_prepare_v2 e sqlite3_step com a consulta pragma_table_info.
#include <sqlite3.h>
bool atualizarEsquemaComSqlite3()
{
QString dirDados = QApplication::applicationDirPath() + "/dados";
QString caminhoBanco = dirDados + "/app.db";
sqlite3* handle = nullptr;
if (sqlite3_open(caminhoBanco.toStdString().c_str(), &handle) != SQLITE_OK) {
qCritical() << sqlite3_errmsg(handle);
return false;
}
char* msgErro = nullptr;
sqlite3_stmt* stmt = nullptr;
for (int idx = 0; idx < NOMES_TABELAS.size(); ++idx) {
QString nomeTabela = NOMES_TABELAS[idx];
QString sqlCriacao = TABELAS_ESPERADAS[idx];
// Consultar colunas existentes via pragma_table_info
QString sqlInfo = QString("SELECT * FROM pragma_table_info('%1')").arg(nomeTabela);
QStringList colunasExistentes;
if (sqlite3_prepare_v2(handle, sqlInfo.toStdString().c_str(), -1, &stmt, nullptr) == SQLITE_OK) {
while (sqlite3_step(stmt) == SQLITE_ROW) {
const char* nomeCol = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1));
colunasExistentes.append(QString::fromUtf8(nomeCol));
}
}
sqlite3_finalize(stmt);
stmt = nullptr;
// Extrair colunas esperadas do SQL de criação
QString trechoColunas = sqlCriacao.mid(
sqlCriacao.indexOf("(") + 1,
sqlCriacao.indexOf(")") - sqlCriacao.indexOf("(") - 1
);
trechoColunas.remove("\n");
QStringList listaColunasEsperadas = trechoColunas.split(",");
if (!colunasExistentes.isEmpty() && listaColunasEsperadas.size() != colunasExistentes.size()) {
for (int k = colunasExistentes.size(); k < listaColunasEsperadas.size(); ++k) {
QString defColuna = listaColunasEsperadas[k].trimmed();
if (!defColuna.isEmpty()) {
QString nomeColuna = defColuna.split(" ").first();
if (!colunasExistentes.contains(nomeColuna)) {
QString sqlAlter = QString("ALTER TABLE %1 ADD COLUMN %2")
.arg(nomeTabela)
.arg(defColuna);
qDebug() << "Adicionando coluna:" << sqlAlter;
if (sqlite3_exec(handle, sqlAlter.toStdString().c_str(), nullptr, nullptr, &msgErro) != SQLITE_OK) {
qWarning() << "Erro:" << sqlite3_errmsg(handle);
sqlite3_free(msgErro);
}
}
}
}
} else if (colunasExistentes.isEmpty()) {
// Tabela não existe - criar
if (sqlite3_exec(handle, sqlCriacao.toStdString().c_str(), nullptr, nullptr, &msgErro) != SQLITE_OK) {
qWarning() << "Erro ao criar:" << sqlite3_errmsg(handle);
sqlite3_free(msgErro);
}
}
}
sqlite3_close(handle);
return true;
}
Em ambos os métodos, ao modificar as definições em TABELAS_ESPERADAS para incluir novas colunas, o mecanismo detecta automaticamente a diferença entre o esquema atual e o esperado, aplicando apenas as adições necessárias sem comprometer dados pré-existentes.