Introdução ao SQLite3
SQLite é um sistema de gerenciamento de banco de dados relacional (SGBDR) notável por sua implementação compacta e eficiente em linguagem C. Diferente de outros SGBDRs tradicionais que operam como servidores separados, o SQLite se destaca por ser "serverless", ou seja, o banco de dados é incorporado diretamente na aplicação que o utiliza. Ele armazena o banco de dados em um único arquivo de disco, o que facilita enormemente sua portabilidade e integração.
Suas características principais incluem:
- Leveza e Velocidade: Possui uma pegada de memória mínima e oferece alta performance para a maioria das operações.
- Independência: Não requer um processo de servidor separado, configuração complexa ou administração.
- Alta Confiabilidade: Projetado para robustez e durabilidade dos dados.
- Multiplataforma: Compatível com praticamente qualquer sistema operacional.
- Amplamente Utilizado: Encontrado em milhões de dispositivos e aplicações, desde smartphones e navegadores web até sistemas embarcados e desktops.
Para mais informações, você pode visitar o site oficial: https://www.sqlite.org/index.html
Obtenção do Código Fonte do SQLite3
Para integrar o SQLite3 em projetos C ou C++, é comum baixar o código fonte e compilá-lo juntamente com sua aplicação. O site oficial oferece pacotes de código fonte que incluem o famoso "amalgamation", um arquivo sqlite3.c que contém todo o código necessário em um único arquivo para simplificar a compilação.
Você pode baixar a versão mais recente diretamente do site de downloads do SQLite (https://www.sqlite.org/download.html). Procure pelo pacote sqlite-autoconf-\*.tar.gz.
wget https://www.sqlite.org/2023/sqlite-autoconf-3410200.tar.gz
tar -zxvf sqlite-autoconf-3410200.tar.gz
Após a extração, você encontrará um diretório com diversos arquivos. Para a maioria dos projetos, os arquivos essenciais são sqlite3.c e sqlite3.h. Recomenda-se copiá-los para um subdiretório do seu projeto, por exemplo, lib/sqlite3, e remover o diretório original descompactado:
mkdir -p lib/sqlite3
cp sqlite-autoconf-3410200/sqlite3.c lib/sqlite3/
cp sqlite-autoconf-3410200/sqlite3.h lib/sqlite3/
cp sqlite-autoconf-3410200/sqlite3ext.h lib/sqlite3/ # Opcional, para extensões
rm -r sqlite-autoconf-3410200
A estrutura de arquivos do seu projeto pode ficar assim:
.
├── build/
├── CMakeLists.txt
├── app.cpp
└── lib/
└── sqlite3/
├── sqlite3.c
├── sqlite3ext.h
└── sqlite3.h
Configurando um Projeto CMake
Para um projeto C++ moderno, CMake é uma excelente ferramenta para gerenciar a compilação. Abaixo, um exemplo de CMakeLists.txt que inclui a biblioteca SQLite3 e a vincula a um executável:
cmake_minimum_required(VERSION 3.10 FATAL_ERROR)
project(GerenciadorDadosSQLite LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Incluir o diretório onde os headers do SQLite estão
include_directories(lib/sqlite3)
# Adicionar a biblioteca SQLite3 estaticamente
add_library(sqlite_core STATIC lib/sqlite3/sqlite3.c)
# Em sistemas Linux, pode ser necessário vincular com 'dl' para funções de carregamento dinâmico
# target_link_libraries(sqlite_core PRIVATE dl) # Descomente se necessário
# Criar o executável principal
add_executable(app_sqlite app.cpp)
# Vincular o executável com a biblioteca SQLite
target_link_libraries(app_sqlite PRIVATE sqlite_core)
Exemplo de Código C++ Simples (app.cpp)
Este exemplo demonstra a abertura de um banco de dados, a criação de uma tabela, inserção de dados e uma consulta simples usando as APIs de C do SQLite3.
#include <iostream>
#include <sqlite3.h>
#include <string>
#include <vector>
// Callback para processar resultados de consultas SELECT
static int callback_exemplo(void *dados_extras, int n_colunas, char **valores, char **nomes_colunas) {
std::cout << "Registro: ";
for (int i = 0; i < n_colunas; i++) {
std::cout << nomes_colunas[i] << " = " << (valores[i] ? valores[i] : "NULL") << (i < n_colunas - 1 ? ", " : "");
}
std::cout << std::endl;
return 0;
}
int main() {
sqlite3 *conexao_db;
char *mensagem_erro = nullptr;
int resultado_operacao;
const std::string nome_banco = "meu_aplicativo.db";
// 1. Abrir ou criar o banco de dados
resultado_operacao = sqlite3_open(nome_banco.c_str(), &conexao_db);
if (resultado_operacao != SQLITE_OK) {
std::cerr << "Erro ao abrir o banco de dados: " << sqlite3_errmsg(conexao_db) << std::endl;
return 1;
}
std::cout << "Banco de dados '" << nome_banco << "' aberto com sucesso." << std::endl;
// 2. Criar uma tabela (se não existir)
std::string sql_criar_tabela = R"(
CREATE TABLE IF NOT EXISTS clientes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nome TEXT NOT NULL,
email TEXT UNIQUE,
idade INTEGER
);
)";
resultado_operacao = sqlite3_exec(conexao_db, sql_criar_tabela.c_str(), nullptr, nullptr, &mensagem_erro);
if (resultado_operacao != SQLITE_OK) {
std::cerr << "Erro ao criar tabela: " << mensagem_erro << std::endl;
sqlite3_free(mensagem_erro);
sqlite3_close(conexao_db);
return 1;
}
std::cout << "Tabela 'clientes' verificada/criada." << std::endl;
// 3. Inserir dados
std::string sql_inserir_cliente1 = "INSERT INTO clientes (nome, email, idade) VALUES ('João Silva', 'joao.silva@email.com', 28);";
resultado_operacao = sqlite3_exec(conexao_db, sql_inserir_cliente1.c_str(), nullptr, nullptr, &mensagem_erro);
if (resultado_operacao != SQLITE_OK) {
std::cerr << "Erro ao inserir cliente 1: " << mensagem_erro << std::endl;
sqlite3_free(mensagem_erro);
} else {
std::cout << "Cliente 'João Silva' inserido." << std::endl;
}
std::string sql_inserir_cliente2 = "INSERT INTO clientes (nome, email, idade) VALUES ('Maria Souza', 'maria.souza@email.com', 35);";
resultado_operacao = sqlite3_exec(conexao_db, sql_inserir_cliente2.c_str(), nullptr, nullptr, &mensagem_erro);
if (resultado_operacao != SQLITE_OK) {
std::cerr << "Erro ao inserir cliente 2: " << mensagem_erro << std::endl;
sqlite3_free(mensagem_erro);
} else {
std::cout << "Cliente 'Maria Souza' inserido." << std::endl;
}
// 4. Consultar dados e usar o callback
std::string sql_consultar = "SELECT id, nome, email, idade FROM clientes WHERE idade > 30;";
std::cout << "\nClientes com mais de 30 anos:" << std::endl;
resultado_operacao = sqlite3_exec(conexao_db, sql_consultar.c_str(), callback_exemplo, nullptr, &mensagem_erro);
if (resultado_operacao != SQLITE_OK) {
std::cerr << "Erro ao consultar dados: " << mensagem_erro << std::endl;
sqlite3_free(mensagem_erro);
}
// 5. Atualizar dados
std::string sql_atualizar = "UPDATE clientes SET idade = 29 WHERE nome = 'João Silva';";
resultado_operacao = sqlite3_exec(conexao_db, sql_atualizar.c_str(), nullptr, nullptr, &mensagem_erro);
if (resultado_operacao != SQLITE_OK) {
std::cerr << "Erro ao atualizar cliente: " << mensagem_erro << std::endl;
sqlite3_free(mensagem_erro);
} else {
std::cout << "\nIdade de 'João Silva' atualizada." << std::endl;
}
// 6. Consultar todos os dados para verificar a atualização
sql_consultar = "SELECT id, nome, email, idade FROM clientes;";
std::cout << "\nTodos os clientes após atualização:" << std::endl;
resultado_operacao = sqlite3_exec(conexao_db, sql_consultar.c_str(), callback_exemplo, nullptr, &mensagem_erro);
if (resultado_operacao != SQLITE_OK) {
std::cerr << "Erro ao consultar dados após atualização: " << mensagem_erro << std::endl;
sqlite3_free(mensagem_erro);
}
// 7. Fechar o banco de dados
sqlite3_close(conexao_db);
std::cout << "\nBanco de dados fechado." << std::endl;
return 0;
}
Compilação
Para compilar o projeto, execute os seguintes comandos no diretório raiz do seu projeto:
mkdir -p build && cd build
cmake ..
make
Isso gerará o executável app\_sqlite no diretório build.
Fundamentos de SQL no SQLite
SQLite é compatível com a maior parte do padrão SQL-92, mas possui algumas particularidades. Compreender os comandos SQL básicos é essencial para interagir com o banco de dados.
Instalação da Ferramenta CLI (Command Line Interface)
Para experimentar comandos SQL diretamente, você pode instalar a ferramenta CLI do SQLite em sistemas baseados em Debian/Ubuntu:
sudo apt install sqlite3
Para abrir um banco de dados (ou criar um novo se não existir):
sqlite3 meu_banco_de_testes.db
Dentro do shell do SQLite, os comandos SQL são terminados por um ponto e vírgula (;).
Comandos SQL Essenciais
1. Criação de Tabelas (CREATE TABLE)
Tabelas são as estruturas fundamentais para organizar dados em um banco de dados relacional. Cada tabela possui um nome e uma coleção de colunas, cada uma com seu tipo de dado.
CREATE TABLE IF NOT EXISTS produtos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nome TEXT NOT NULL,
preco REAL,
estoque INTEGER DEFAULT 0
);
IF NOT EXISTS: Garante que a tabela só será criada se ainda não existir.INTEGER PRIMARY KEY AUTOINCREMENT: Defineidcomo chave primária, um inteiro que será automaticamente incrementado.TEXT NOT NULL:nomeé uma coluna de texto que não pode ser nula.REAL: Tipo de dado para números de ponto flutuante.INTEGER DEFAULT 0:estoqueé um inteiro com valor padrão de 0.
2. Inserção de Dados (INSERT INTO)
Para adicionar novas linhas (registros) a uma tabela:
INSERT INTO produtos (nome, preco, estoque) VALUES ('Notebook', 1500.00, 10);
INSERT INTO produtos (nome, preco, estoque) VALUES ('Mouse', 25.50, 50);
Se você não especificar as colunas, precisará fornecer valores para todas as colunas na ordem em que foram definidas:
INSERT INTO produtos VALUES (NULL, 'Teclado', 75.00, 30); -- NULL para id AUTOINCREMENT
3. Consulta de Dados (SELECT)
Para recuperar dados de uma ou mais tabelas:
SELECT * FROM produtos; -- Seleciona todas as colunas e todas as linhas
Para selecionar colunas específicas e filtrar resultdaos:
SELECT nome, preco FROM produtos WHERE preco > 50.00 ORDER BY preco DESC;
WHERE: Filtra as linhas com base em uma condição.ORDER BY: Classifica os resultados.
4. Atualização de Dados (UPDATE)
Para modificar dados existentes em uma ou mais linhas:
UPDATE produtos SET estoque = 15 WHERE nome = 'Notebook';
UPDATE produtos SET preco = preco * 1.05 WHERE id = 2; -- Aumenta o preço em 5%
A cláusula WHERE é crucial para especificar quais registros serão atualiazdos. Sem ela, todos os registros da tabela seriam modificados.
5. Exclusão de Dados (DELETE FROM)
Para remover linhas de uma tabela:
DELETE FROM produtos WHERE estoque < 5;
Para remover todas as linhas de uma tabela (mantendo a estrutura da tabela):
DELETE FROM produtos;
Atenção ao usar DELETE FROM sem a cláusula WHERE, pois isso apaga todos os dados da tabela.
Você pode combinar condições com AND ou OR na cláusula WHERE:
DELETE FROM produtos WHERE preco < 30.00 AND estoque = 0;
SQLiteCpp: Um Wrapper C++ Amigável
Embora as APIs C do SQLite sejam robustas, trabalhar diretamente com ponteiros, char\*\* e callbacks pode ser tedioso em projetos C++. SQLiteCpp é um wrapper C++ de código aberto que oferece uma interface mais idiomática e segura, utilizando classes e exceções C++.
Obtenção do SQLiteCpp
O SQLiteCpp está disponível no GitHub e pode ser incluído como um submódulo Git em seu projeto:
git clone https://github.com/SRombauts/SQLiteCpp.git
cd SQLiteCpp
git submodule init
git submodule update
Configuração CMake para SQLiteCpp
Para integrar SQLiteCpp ao seu projeto CMake, você pode adicioná-lo como um subdiretório:
cmake_minimum_required(VERSION 3.10 FATAL_ERROR)
project(SQLiteCppDemo LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Adicionar SQLiteCpp como um subdiretório
# Isso adicionará a biblioteca SQLiteCpp ao seu projeto CMake
add_subdirectory(SQLiteCpp)
# Incluir o diretório de headers do SQLiteCpp
include_directories(SQLiteCpp/include)
# Criar o executável principal
add_executable(app_with_wrapper main_wrapper.cpp)
# Vincular o executável com a biblioteca SQLiteCpp
# SQLiteCpp já gerencia a vinculação com a biblioteca C do SQLite internamente
target_link_libraries(app_with_wrapper PRIVATE SQLiteCpp)
Exemplo de Uso do SQLiteCpp (main\_wrapper.cpp)
#include <iostream>
#include <SQLiteCpp/SQLiteCpp.h> // Header principal do SQLiteCpp
int main() {
try {
// Abrir um arquivo de banco de dados no modo de leitura/escrita, criando-o se não existir
// SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE são flags de modo
SQLite::Database db("dados_clientes.db", SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE);
std::cout << "Arquivo de banco de dados '" << db.getFilename() << "' aberto com sucesso." << std::endl;
// Remover a tabela "cadastro" se ela existir, e depois criá-la
db.exec("DROP TABLE IF EXISTS cadastro");
db.exec("CREATE TABLE cadastro (id INTEGER PRIMARY KEY AUTOINCREMENT, nome TEXT NOT NULL, idade INTEGER)");
std::cout << "Tabela 'cadastro' criada." << std::endl;
// Inserir registros
int registros_afetados;
registros_afetados = db.exec("INSERT INTO cadastro (nome, idade) VALUES ('Ana', 25)");
std::cout << "Inserido 'Ana', registros afetados: " << registros_afetados << std::endl;
registros_afetados = db.exec("INSERT INTO cadastro (nome, idade) VALUES ('Bruno', 30)");
std::cout << "Inserido 'Bruno', registros afetados: " << registros_afetados << std::endl;
// Atualizar um registro
registros_afetados = db.exec("UPDATE cadastro SET idade = 31 WHERE nome = 'Bruno'");
std::cout << "Atualizado 'Bruno', registros afetados: " << registros_afetados << std::endl;
// Consultar e exibir os resultados usando SQLite::Statement
SQLite::Statement query(db, "SELECT id, nome, idade FROM cadastro");
std::cout << "\nDados da tabela 'cadastro':" << std::endl;
while (query.executeStep()) { // Itera por cada linha do resultado
int id_cliente = query.getColumn(0);
std::string nome_cliente = query.getColumn(1);
int idade_cliente = query.getColumn(2);
std::cout << "ID: " << id_cliente << ", Nome: " << nome_cliente << ", Idade: " << idade_cliente << std::endl;
}
// Deletar um registro
registros_afetados = db.exec("DELETE FROM cadastro WHERE nome = 'Ana'");
std::cout << "\nDeletado 'Ana', registros afetados: " << registros_afetados << std::endl;
// Consultar novamente para verificar a exclusão
SQLite::Statement query_pos_delete(db, "SELECT nome FROM cadastro");
std::cout << "Registros restantes na tabela 'cadastro':" << std::endl;
while (query_pos_delete.executeStep()) {
std::cout << "- " << query_pos_delete.getColumn(0).getText() << std::endl;
}
db.exec("DROP TABLE cadastro"); // Excluir a tabela no final
std::cout << "\nTabela 'cadastro' excluída." << std::endl;
} catch (std::exception& e) {
// Tratamento de exceções para erros de banco de dados
std::cerr << "Exceção SQLite: " << e.what() << std::endl;
return EXIT_FAILURE;
}
return 0;
}