Banco de dados
SQLite com Arcane.Database: consultas parametrizadas, transações e modelos.
Conectar#
adopt Arcane.Database as DB
conn := DB.memory() # em memória: some ao terminar
conn := DB.connect("dados.db") # arquivo SQLitememory() é ideal para teste: cada execução começa limpa, e nada fica no disco.
Parâmetros, sempre#
DB.query(conn, "SELECT nome FROM produtos WHERE preco < ?", [100])As operações#
| Chamada | Para |
|---|---|
execute(conn, sql, params) | INSERT, UPDATE, DELETE, CREATE |
execute_many(conn, sql, lista) | vários INSERT de uma vez |
query(conn, sql, params) | várias linhas, como lista de vaults |
query_one(conn, sql, params) | uma linha, ou void |
count(conn, tabela) | atalho para COUNT(*) |
DB.execute(conn, """CREATE TABLE produtos (
id INTEGER PRIMARY KEY,
nome TEXT NOT NULL,
preco REAL NOT NULL
)""")
DB.execute(conn, "INSERT INTO produtos (nome, preco) VALUES (?, ?)", ["Mouse", 80.0])
DB.execute_many(conn, "INSERT INTO produtos (nome, preco) VALUES (?, ?)", [
["Teclado", 200.0],
["Monitor", 1200.0]
])execute_many#
Uma ida ao banco em vez de N. Para inserção em lote, a diferença de desempenho é grande.
Consultar#
todos := DB.query(conn, "SELECT nome, preco FROM produtos ORDER BY preco DESC")
cycle p in todos:
out $"{p["nome"].pad_end(10)} R$ {p["preco"]}"
caro := DB.query_one(conn, "SELECT nome FROM produtos ORDER BY preco DESC LIMIT 1")query devolve vaults com os nomes das colunas como chaves — dá para usar p["nome"] direto, sem índices.
Transações#
DB.begin(conn)
DB.execute(conn, "UPDATE contas SET saldo = saldo - 100 WHERE id = 1")
DB.execute(conn, "UPDATE contas SET saldo = saldo + 100 WHERE id = 2")
DB.commit(conn) # ou DB.rollback(conn)As duas operações acontecem juntas ou nenhuma. Sem transação, uma falha entre elas deixaria dinheiro sumido.
O padrão seguro combina com monitor:
DB.begin(conn)
monitor:
transferir(de, para, valor)
DB.commit(conn)
handle e:
DB.rollback(conn)
propagate e.messageConverter na fronteira#
record Produto:
id: Integer
nome: String
preco: Number
action buscar_todos():
linhas := DB.query(conn, "SELECT id, nome, preco FROM produtos")
yield linhas >> morph l: Produto(l["id"], l["nome"], l["preco"])Linhas do banco entram; records tipados saem. A partir dali, p.nome com verificação, não l["nome"] com risco de digitar errado.
Regra de negócio no banco#
action emprestar(livro_id, leitor):
ja := DB.query_one(conn, "SELECT id FROM emprestimos WHERE livro_id = ?", [livro_id])
given ja isnt void:
yield {"ok": no, "erros": ["livro ja emprestado"]}
..."Um livro só pode estar emprestado uma vez" depende do estado atual — não dá para validar sem consultar. Por isso essa checagem fica na camada de persistência, não nas regras puras.
Introspecção#
DB.tables(conn) # as tabelas
DB.columns(conn, "produtos") # as colunas
DB.table_exists(conn, "x")
DB.table_info(conn, "produtos") # tipos e restrições
DB.close(conn)