Drivers de Banco de Dados em Zig — PostgreSQL, SQLite, MySQL e Mais

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

  1. Use prepared statements: Previna SQL injection e melhore performance
  2. Pool de conexões: Reutilize conexões para evitar overhead de handshake
  3. Transações explícitas: Use BEGIN/COMMIT para operações atômicas
  4. Trate erros de conexão: Implemente retry com backoff exponencial
  5. Feche recursos: Use defer para 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.

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.