Drivers de Banco de Dados em Zig — PostgreSQL, SQLite, MySQL e Mais
O acesso a bancos de dados é essencial para a maioria das aplicações. O ecossistema Zig oferece drivers para os bancos mais populares, tanto como implementações nativas em Zig quanto como bindings para bibliotecas C existentes. A interoperabilidade nativa com C garante que qualquer driver C pode ser utilizado em projetos Zig com overhead zero.
SQLite — O Banco Embarcado
O SQLite é a escolha mais popular para aplicações que precisam de banco de dados local. A interop C do Zig torna a integração trivial:
Via @cImport Direto
const std = @import("std");
const c = @cImport({
@cInclude("sqlite3.h");
});
pub fn main() !void {
var db: ?*c.sqlite3 = null;
// Abrir banco
if (c.sqlite3_open("app.db", &db) != c.SQLITE_OK) {
std.debug.print("Erro ao abrir banco: {s}\n", .{c.sqlite3_errmsg(db)});
return;
}
defer _ = c.sqlite3_close(db);
// Criar tabela
var err_msg: ?[*:0]u8 = null;
const sql_create =
\\CREATE TABLE IF NOT EXISTS usuarios (
\\ id INTEGER PRIMARY KEY AUTOINCREMENT,
\\ nome TEXT NOT NULL,
\\ email TEXT UNIQUE NOT NULL,
\\ criado_em DATETIME DEFAULT CURRENT_TIMESTAMP
\\)
;
if (c.sqlite3_exec(db, sql_create, null, null, &err_msg) != c.SQLITE_OK) {
std.debug.print("Erro SQL: {s}\n", .{err_msg.?});
c.sqlite3_free(err_msg);
return;
}
// Inserir dados com prepared statement
var stmt: ?*c.sqlite3_stmt = null;
const sql_insert = "INSERT INTO usuarios (nome, email) VALUES (?, ?)";
if (c.sqlite3_prepare_v2(db, sql_insert, -1, &stmt, null) != c.SQLITE_OK) {
return;
}
defer _ = c.sqlite3_finalize(stmt);
// Bind parâmetros
_ = c.sqlite3_bind_text(stmt, 1, "Maria Silva", -1, null);
_ = c.sqlite3_bind_text(stmt, 2, "maria@exemplo.com", -1, null);
if (c.sqlite3_step(stmt) != c.SQLITE_DONE) {
std.debug.print("Erro ao inserir\n", .{});
}
}
zig-sqlite — Wrapper Ergonômico
const sqlite = @import("zig-sqlite");
pub fn main() !void {
var db = try sqlite.Database.open("app.db");
defer db.close();
// Query tipada
const Usuario = struct {
id: i64,
nome: []const u8,
email: []const u8,
};
var stmt = try db.prepare(
"SELECT id, nome, email FROM usuarios WHERE ativo = ?",
Usuario,
);
defer stmt.deinit();
try stmt.bind(.{true});
while (try stmt.next()) |usuario| {
std.debug.print("{}: {s} <{s}>\n", .{
usuario.id,
usuario.nome,
usuario.email,
});
}
}
PostgreSQL
pg.zig — Driver Nativo
O pg.zig é um driver PostgreSQL escrito inteiramente em Zig, sem dependências C:
const pg = @import("pg");
pub fn main() !void {
var pool = try pg.Pool.init(.{
.host = "localhost",
.port = 5432,
.database = "minha_app",
.user = "postgres",
.password = "senha",
.pool_size = 10,
});
defer pool.deinit();
// Query simples
var conn = try pool.acquire();
defer pool.release(conn);
// SELECT com parâmetros
var result = try conn.query(
"SELECT id, nome, email FROM usuarios WHERE ativo = $1 AND idade > $2",
.{ true, @as(i32, 18) },
);
defer result.deinit();
while (try result.next()) |row| {
const id = row.get(i64, 0);
const nome = row.get([]const u8, 1);
const email = row.get([]const u8, 2);
std.debug.print("{}: {s} <{s}>\n", .{ id, nome, email });
}
// INSERT
try conn.exec(
"INSERT INTO usuarios (nome, email) VALUES ($1, $2)",
.{ "João Santos", "joao@exemplo.com" },
);
// Transação
try conn.exec("BEGIN", .{});
errdefer conn.exec("ROLLBACK", .{}) catch {};
try conn.exec("UPDATE contas SET saldo = saldo - $1 WHERE id = $2", .{ 100.0, 1 });
try conn.exec("UPDATE contas SET saldo = saldo + $1 WHERE id = $2", .{ 100.0, 2 });
try conn.exec("COMMIT", .{});
}
libpq via Interop C
const c = @cImport({
@cInclude("libpq-fe.h");
});
pub fn main() !void {
const conn = c.PQconnectdb("host=localhost dbname=app user=postgres");
defer c.PQfinish(conn);
if (c.PQstatus(conn) != c.CONNECTION_OK) {
std.debug.print("Erro: {s}\n", .{c.PQerrorMessage(conn)});
return;
}
const result = c.PQexec(conn, "SELECT id, nome FROM usuarios");
defer c.PQclear(result);
const nrows = c.PQntuples(result);
var i: c_int = 0;
while (i < nrows) : (i += 1) {
const id = c.PQgetvalue(result, i, 0);
const nome = c.PQgetvalue(result, i, 1);
std.debug.print("ID: {s}, Nome: {s}\n", .{ id, nome });
}
}
MySQL/MariaDB
const c = @cImport({
@cInclude("mysql/mysql.h");
});
pub fn main() !void {
const mysql = c.mysql_init(null) orelse return;
defer c.mysql_close(mysql);
if (c.mysql_real_connect(
mysql,
"localhost",
"root",
"senha",
"app",
3306,
null,
0,
) == null) {
std.debug.print("Erro: {s}\n", .{c.mysql_error(mysql)});
return;
}
if (c.mysql_query(mysql, "SELECT * FROM produtos") != 0) {
return;
}
const result = c.mysql_store_result(mysql);
defer c.mysql_free_result(result);
while (c.mysql_fetch_row(result)) |row| {
std.debug.print("Produto: {s}\n", .{row[1]});
}
}
Redis
const redis = @import("zig-redis");
pub fn main() !void {
var client = try redis.Client.connect("localhost", 6379);
defer client.disconnect();
// SET/GET
try client.set("chave", "valor");
const valor = try client.get("chave");
std.debug.print("valor = {s}\n", .{valor.?});
// Expiração
try client.setex("sessao:abc123", 3600, "dados_sessao");
// Listas
try client.lpush("fila", "tarefa1");
try client.lpush("fila", "tarefa2");
const item = try client.rpop("fila");
std.debug.print("Próxima tarefa: {s}\n", .{item.?});
// Hash
try client.hset("usuario:1", "nome", "Maria");
try client.hset("usuario:1", "email", "maria@ex.com");
}
Pool de Conexões
Implementação genérica de pool para qualquer driver:
fn ConnectionPool(comptime Driver: type) type {
return struct {
const Self = @This();
conexoes: std.ArrayList(*Driver.Connection),
disponiveis: std.ArrayList(*Driver.Connection),
config: Driver.Config,
max_size: usize,
mutex: std.Thread.Mutex = .{},
pub fn init(config: Driver.Config, max_size: usize, allocator: std.mem.Allocator) Self {
return .{
.conexoes = std.ArrayList(*Driver.Connection).init(allocator),
.disponiveis = std.ArrayList(*Driver.Connection).init(allocator),
.config = config,
.max_size = max_size,
};
}
pub fn acquire(self: *Self) !*Driver.Connection {
self.mutex.lock();
defer self.mutex.unlock();
if (self.disponiveis.popOrNull()) |conn| {
return conn;
}
if (self.conexoes.items.len < self.max_size) {
const conn = try Driver.connect(self.config);
try self.conexoes.append(conn);
return conn;
}
return error.PoolExhausted;
}
pub fn release(self: *Self, conn: *Driver.Connection) void {
self.mutex.lock();
defer self.mutex.unlock();
self.disponiveis.append(conn) catch {};
}
};
}
TigerBeetle
Para aplicações financeiras, o TigerBeetle oferece performance incomparável. Confira o case detalhado para mais informações.
Boas Práticas
- Use prepared statements: Previna SQL injection e melhore performance
- Pool de conexões: Reutilize conexões para evitar overhead de handshake
- Transações explícitas: Use BEGIN/COMMIT para operações atômicas
- Trate erros de conexão: Implemente retry com backoff exponencial
- Feche recursos: Use
deferpara garantir fechamento de conexões e statements
Próximos Passos
Explore o TigerBeetle para cenários financeiros, as bibliotecas JSON para serialização de resultados, e os frameworks web para criar APIs com acesso a banco. Veja como fintechs usam Zig em produção com bancos de dados.