Especialização em Sistemas com Python & POO com Type Hints • Capítulo 16


🗺️ Mapa Conceitual do Tópico

flowchart TD
    subgraph ENGINE["Motor Relacional SQLite3 Integrado"]
        ACID["Garantias Transacionais ACID\nAtomicidade · Consistência · Isolamento · Durabilidade"]
        PRAGMA["Pragmas Mandatórios de Engenharia\nPRAGMA foreign_keys = ON · PRAGMA journal_mode = WAL"]
        ROW["conn.row_factory = sqlite3.Row\nAcesso O(1) Indexado por Nome de Coluna"]
    end

    subgraph SECURITY["Segurança e Parametrizacão de Queries"]
        PLACEHOLDER["Consultas Parametrizadas (?, :param)\nImunidade Completa contra SQL Injection (OWASP Top 1)"]
        ANTI_PATTERN["Anti-pattern: F-Strings em SQL (Vulnerabilidade Crítica)"]
    end

    subgraph PATTERNS["Padrões de Persistência (DAO & Repository)"]
        ENT["Entidade de Domínio Pura (Dataclass)"]
        DAO["Data Access Object (DAO)\nExecuta DDL/DML · Mapeia Tuplas para Entidades"]
        TRANS["with conn: Bloco Atômico com Commit / Rollback Automático"]
    end

    ENGINE --> SECURITY
    SECURITY --> PATTERNS
    PATTERNS --> PROD["Aplicações Desktop, Dispositivos Embarcados e Testes Ágeis"]

    style ENGINE fill:#ede7f6,stroke:#512da8,stroke-width:2px
    style SECURITY fill:#ffebee,stroke:#f44336,stroke-width:2px
    style PATTERNS fill:#e1f5fe,stroke:#0288d1,stroke-width:2px

💾 1. O Banco Relacional SQLite3 no Ecossistema Python

Diferente de SGBDs cliente-servidor como PostgreSQL ou MySQL, o SQLite3 é um banco de dados relacional embarcado (in-process), que reside diretamente no espaço de memória da aplicação e persiste seus dados em um único arquivo de disco multiplataforma (ou puramente em memória via :memory:).

Pragmas Mandatórios de Engenharia

Por questões de compatibilidade retroativa com sistemas legados da década de 1990, o SQLite vem por padrão com certos recursos críticos desativados:

  1. Ativação de Chaves Estrangeiras: O suporte a FOREIGN KEY vem desabilitado por padrão! Deve ser ativado em toda conexão recém-aberta:
    PRAGMA foreign_keys = ON;
    
  2. Modo WAL (Write-Ahead Logging): Em servidores de aplicação com concorrência, o modo padrão de journal bloqueia o banco inteiro durante escritas. O modo WAL permite múltiplos leitores simultâneos enquanto um escritor grava no disco:
    PRAGMA journal_mode = WAL;
    

🔒 2. Prevenção de SQL Injection e Consultas Parametrizadas

A regra de ouro inegociável da engenharia de banco de dados é: nunca interpole variáveis diretamente em strings SQL via f-strings ou operador %:

# VULNERABILIDADE GRAVE DE SEGURANÇA (SQL Injection / OWASP A03):
# Se usuario_input for "' OR '1'='1", todo o banco é exposto!
cursor.execute(f"SELECT * FROM usuarios WHERE email = '{usuario_input}'")

# PADRÃO OURO DE ENGENHARIA (Placeholders Parametrizados):
# O driver envia a consulta compilada e os parâmetros em canais separados
cursor.execute("SELECT * FROM usuarios WHERE email = ?", (usuario_input,))
cursor.execute("SELECT * FROM usuarios WHERE email = :email", {"email": usuario_input})

⚡ 3. Gestão Transacional ACID e sqlite3.Row

Gerenciador de Contexto de Transações (with conn:)

A classe sqlite3.Connection atua nativamente como gerenciador de contexto transacional:

  • Se o bloco with conn: terminar sem erros, o driver emite um COMMIT automático no banco.
  • Se qualquer exceção for lançada dentro do bloco, o driver emite um ROLLBACK instantâneo, revertendo todas as alterações para o estado consistente anterior.

Acesso Dinâmico com conn.row_factory = sqlite3.Row

Por padrão, cursor.fetchall() retorna tuplas brutas sem nome (('João', 25)). Configurar conn.row_factory = sqlite3.Row transforma os retornos em mapeamentos otimizados de alta performance:

conn.row_factory = sqlite3.Row
cursor = conn.execute("SELECT id, nome, saldo FROM contas WHERE id = ?", (1,))
linha = cursor.fetchone()

# Acesso duplo transparente: por chave textual ou por índice numérico
print(linha["nome"], linha["saldo"])
print(linha[0], linha[1])

🏛️ 4. O Padrão Data Access Object (DAO / Repository)

O padrão DAO isola todas as instruções SQL (DDL, DML) dentro de uma classe especializada, mantendo as entidades de domínio desacopladas de dialetos de persistência:

from dataclasses import dataclass
from decimal import Decimal
import sqlite3
from typing import Self

@dataclass(slots=True)
class Produto:
    id: int | None
    sku: str
    nome: str
    preco: Decimal
    estoque: int

class ProdutoDAO:
    def __init__(self, conexao: sqlite3.Connection) -> None:
        self._conn: sqlite3.Connection = conexao

    def salvar(self, produto: Produto) -> int:
        query = """
        INSERT INTO produtos (sku, nome, preco, estoque)
        VALUES (?, ?, ?, ?)
        ON CONFLICT(sku) DO UPDATE SET
            preco = excluded.preco,
            estoque = excluded.estoque;
        """
        with self._conn:
            cursor = self._conn.execute(
                query, (produto.sku, produto.nome, str(produto.preco), produto.estoque)
            )
            return cursor.lastrowid or 0

Essa separação viabiliza testes unitários ultrarrápidos utilizando bancos SQLite em memória (:memory:), simulando com precisão o comportamento relacional sem dependências externas.