Pular para o conteúdo

Forge — banco de dados

Cinco motores, uma interface, zero dependências.

Forge conecta a PostgreSQL, MySQL, MariaDB, MongoDB, Redis e SQLite. Trocar o motor troca a URL, e mais nada:

dataforge
adopt Forge

db := Forge.conectar("postgres://usuario:senha@localhost:5432/app")
// ou "mysql://root@localhost/app"
// ou "mongodb://localhost/app"
// ou "redis://localhost"
// ou "dados.db"                    — SQLite

out Forge.versao(db)

Sem dependência nenhuma#

Cada driver fala o protocolo do seu banco, por socket. Não há psycopg2, PyMySQL, redis-py nem pymongo aqui — há um arquivo por protocolo, implementando a especificação publicada de cada servidor.

O custo foi escrevê-los. O que se ganha é que pip install dataforge funciona numa máquina sem compilador, e a versão do driver nunca briga com a de outro pacote.

MotorAutenticaçãoTestado contra
PostgreSQLSCRAM-SHA-256, MD5, trust16
MySQLcaching_sha2_password (com RSA), mysql_native_password8.4
MariaDBmysql_native_password11
RedisAUTH com usuário e senha7.4
MongoDBSCRAM-SHA-256 e SHA-17.0
SQLiteo do Python

Primeiro programa#

dataforge
adopt Forge

db := Forge.conectar(":memory:")

Forge.executar(db, """
    create table produtos (
        id integer primary key autoincrement,
        nome text not null,
        preco real
    )
""")

Forge.de(db, "produtos").inserir([
    {"nome": "Teclado", "preco": 250.0},
    {"nome": "Mouse", "preco": 90.0}
])

caros := Forge.de(db, "produtos").onde("preco", ">=", 100).buscar()
assert len(caros) is 1
assert caros[0]["nome"] is "Teclado"

Valores nunca entram no texto da consulta#

Esta é a garantia central, e ela vale para o construtor e para o SQL escrito à mão:

dataforge
adopt Forge

db := Forge.conectar(":memory:")
Forge.executar(db, "create table u (id integer primary key, nome text)")

malicioso := "'; drop table u; --"
Forge.de(db, "u").inserir({"nome": malicioso})

// a tabela continua lá: o valor foi tratado como dado, não como SQL
assert "u" in Forge.tabelas(db)
assert Forge.de(db, "u").contar() is 1

Erros que dizem o que houve#

Cada erro do servidor vira uma classe da linguagem, capturável por família:

dataforge
adopt Forge

db := Forge.conectar(":memory:")
Forge.executar(db, "create table u (id integer primary key, email text unique)")
Forge.de(db, "u").inserir({"email": "a@b.co"})

monitor:
    Forge.de(db, "u").inserir({"email": "a@b.co"})
handle ConstraintError as e:
    assert "UNIQUE" in e.message
ClasseQuando
ConnectionErroro servidor não respondeu ou recusou
AuthenticationErrorusuário ou senha inválidos
QueryErrorSQL inválido, tabela ou coluna inexistente
ConstraintErrorchave única, estrangeira, ou not null
TransactionErrordeadlock, ou confirmar sem transação aberta
PoolExhaustedErrortodas as conexões ocupadas
RecordNotFoundErrorbuscar_ou_erro não achou
ValidationErroro modelo recusou os dados

Todas descendem de DatabaseError, então handle DatabaseError pega qualquer uma. Ver catálogo de erros.

Por onde seguir#