EDSQLite

Banco de dados relacional embutido, inspirado no SQLite, escrito em Rust do zero.

Versão 0.14.0 · pré-lançamento · macOS e Linux

$ edsqlite loja.db
edsqlite> SELECT p.nome, sum(v.qtd) AS vendidos
     ...>   FROM produtos AS p
     ...>   JOIN vendas AS v ON v.produto = p.nome
     ...>   GROUP BY p.nome ORDER BY vendidos DESC;
caneta|15
mochila|1
edsqlite> EXPLAIN QUERY PLAN SELECT * FROM vendas
     ...>   WHERE produto = 'caneta';
QUERY PLAN
`--SEARCH vendas USING INDEX v_produto (produto=?)

É estável? Dá para usar?

Dá para usar, e o formato do arquivo não muda mais. A 0.12 tem tudo o que estava planejado para a v1.0: o formato está congelado e documentado, e todo banco criado desde a 0.3 vai abrir em todas as versões 1.x. A partir da 0.12, uma versão antiga que recebe um banco mais novo o recusa sem tocar nele. Ela ainda sai como pré-lançamento, antes do selo de v1.0.

Já dá para confiar

  • SQL do dia a dia: CREATE TABLE/INDEX, INSERT, UPDATE, DELETE e SELECT com WHERE, ORDER BY e LIMIT.
  • JOINs, GROUP BY, HAVING, DISTINCT e subconsultas.
  • CASE, CAST e as funções de texto substr, replace e trim.
  • Restrições: NOT NULL, DEFAULT, UNIQUE, PRIMARY KEY e INTEGER PRIMARY KEY.
  • Commit atômico mesmo com queda de energia ou kill -9.
  • Várias conexões ao mesmo tempo, com locks e modo WAL.
  • Resultados iguais aos do SQLite em mais de 8 milhões de consultas geradas e em 6 mil consultas do corpus oficial de testes do SQLite.
  • Formato do arquivo congelado, com política de compatibilidade.

Ainda não

  • CHECK, chaves estrangeiras, LEFT JOIN, UNION, views, ALTER TABLE e muitas funções.
  • Locks no Windows.

O que já tem na 0.12

SQL com tipos do SQLite

Afinidade por coluna, as cinco classes de valor, aritmética, LIKE, IN, BETWEEN e as mesmas conversões do SQLite.

Índices e planner

Índices simples, compostos e UNIQUE. O planner escolhe buscas por índice ou por rowid e mostra tudo no EXPLAIN QUERY PLAN.

JOINs e agregação

JOIN ... ON, vírgula, CROSS JOIN, count, sum, avg, min, max, group_concat, GROUP BY, HAVING e DISTINCT.

Subconsultas

Escalares, IN (SELECT ...) e EXISTS, correlacionadas ou não.

Restrições

NOT NULL, DEFAULT, UNIQUE, PRIMARY KEY e INTEGER PRIMARY KEY, que é o próprio rowid, como no SQLite. PRAGMA table_info mostra tudo.

CASE, CAST e texto

CASE com e sem base, CAST para os cinco tipos e as funções substr, replace, trim, ltrim e rtrim, com os detalhes do SQLite.

Transações duráveis

BEGIN/COMMIT/ROLLBACK com rollback journal. Testado com milhares de crashes simulados e kill -9 de verdade.

Concorrência e WAL

Várias conexões, em threads ou processos, com busy_timeout. No modo WAL, leitores não bloqueiam o escritor.

Comandos preparados

prepare, bind e step, com parâmetros ?, ?N e :nome. Um comando preparado é recompilado sozinho quando outra conexão muda o schema.

Formato estável

O arquivo está documentado byte a byte e congelado. Os códigos de erro têm os números do SQLite (BUSY, CONSTRAINT, NOTADB...).

API C

No formato da API C do SQLite (edsqlite_open, prepare_v2, step, bind_*, column_*), com header para C e C++. É o caminho para drivers em outras linguagens.

Instalação

  1. Baixe o .tar.gz do seu sistema na última versão.
  2. Extraia e coloque o edsqlite num diretório do seu PATH:
tar xzf edsqlite-0.14.0-macos-arm64.tar.gz
sudo mv edsqlite /usr/local/bin/
edsqlite --version

No macOS, o binário não é assinado pela Apple. Se o sistema bloquear a execução, rode uma vez:

xattr -d com.apple.quarantine /usr/local/bin/edsqlite

DBeaver: baixe o edsqlite-jdbc-<versão>.jar e cadastre-o uma vez em Database → Driver Manager → New (Driver Type Generic, Class Name br.edsqlite.jdbc.EDSQLiteDriver, URL Template jdbc:edsqlite:{file}, Embedded, e o .jar em Libraries). Depois, é como no SQLite: nova conexão, EDSQLite, o caminho do arquivo e Finish. Não precisa de Java instalado: o driver roda no Java do DBeaver.

Biblioteca C: os pacotes edsqlite-capi-<versão>-<sistema>.tar.gz trazem include/edsqlite.h e a biblioteca (.dylib ou .so, e .a), para usar o EDSQLite de C, C++ ou de um driver. A de Linux usa a glibc 2.34 ou mais nova (Ubuntu 22.04+, Debian 12+, RHEL 9+).

Uso: edsqlite dados.db abre (ou cria) um banco; edsqlite sem argumento usa um banco temporário em memória. Os comandos terminam em ;. Comandos do REPL: .headers on|off, .tables, .indexes e .exit. Também dá para rodar um script: edsqlite dados.db < script.sql.

Como é testado

TesteO que garante
Diferencial contra o SQLite 3.53SQL aleatório (expressões, índices, JOINs, agregação, subconsultas) nos dois bancos, com resultado idêntico: mais de 16 milhões de consultas e comandos, nenhuma divergência.
Crash em cada operação de I/OO banco fica sempre no estado de antes ou de depois da transação, nos dois modos de journal.
kill -9 realNenhuma transação pela metade nem perdida depois de confirmada.
Stress de concorrênciaEscritor e leitores em threads e em processos, sem leitura inconsistente nem deadlock.
Relatórios estilo TPC-HConsultas de relatório sobre 10 mil itens dão exatamente o resultado do SQLite.
sqllogictest do SQLite6106 consultas do corpus oficial de conformidade do SQLite, com as respostas que o próprio SQLite deu.
Queda de energia em toda a suíteCada script de teste roda com um crash simulado em cada operação de I/O: uns 5800 crashes a cada execução.
FuzzingSQL arbitrário e arquivos de banco corrompidos, todas as noites: nunca um panic, só erros.
Cobertura94,7% das linhas cobertas pelos testes, com metas mínimas nos módulos de armazenamento e da VM.

Roadmap

v0.1 a v0.12 concluídas

Parser, VM de bytecode, B-tree em disco, tipos com afinidade, índices e planner, transações duráveis, concorrência com WAL, JOINs, agregação, subconsultas, CASE, CAST, funções de texto, restrições e, na 0.12, tudo o que estava planejado para a v1.0 (abaixo).

v1.0 · API estável e testes obsessivos pronta na 0.12

O que faltava para chamar de versão estável.

  1. Formato do arquivo congelado, documentado byte a byte e com política de compatibilidade: versão no cabeçalho, versão futura recusada sem tocar no arquivo, semver na API.
  2. API com parâmetros: prepare, bind, step, reset e finalize, com ?, ?N e :nome no SQL, sem precisar montar SQL como texto.
  3. Fuzzing com cargo-fuzz: SQL arbitrário e arquivos de banco corrompidos, sempre sem panic.
  4. Códigos de erro estáveis, com os números do SQLite.
  5. Recompilação automática de um comando preparado quando o schema muda.
  6. Queda de energia simulada em toda a suíte de testes, não só nas transações.
  7. Cobertura de testes medida: 94,7% das linhas, acima da meta de 90% nos módulos de armazenamento e da VM.
  8. 6106 consultas do corpus de conformidade sqllogictest do SQLite como teste.
  9. Uma API C no formato da do SQLite, com header para C e C++.

Driver JDBC (DBeaver) pronto

Para abrir bancos EDSQLite em ferramentas como o DBeaver.

  • Pronto: vários comandos em execução ao mesmo tempo na mesma conexão (ler um resultado enquanto roda outra consulta), como as ferramentas de banco fazem.
  • Pronto: last_insert_rowid, para as chaves geradas de um INSERT.
  • Pronto: biblioteca e API C nos downloads das releases.
  • Pronto: o driver JDBC (edsqlite-jdbc-<versão>.jar), para abrir bancos EDSQLite no DBeaver como se abre um SQLite.

Recursos de SQL que faltam

Os mais usados primeiro.

  • Restrições: CHECK, AUTOINCREMENT, chaves estrangeiras e ON CONFLICT/REPLACE.
  • Consultas: LEFT JOIN, UNION/INTERSECT/EXCEPT, subconsultas no FROM, WITH (CTE), USING/NATURAL, INSERT ... SELECT.
  • Funções: instr, printf, as de data e hora e as de janela.
  • Schema: ALTER TABLE (renomear e acrescentar colunas), CREATE VIEW, triggers, COLLATE, índices DESC.

Plataformas e desempenho

  • Locks no Windows. Hoje eles só existem em Unix (macOS e Linux); no Windows, duas conexões no mesmo arquivo não se protegem uma da outra.
  • Instalação por Homebrew e pacote para Linux.
  • Planner com estatísticas (ANALYZE), índices cobertos e busca com IN.
  • Transações maiores que a memória e ORDER BY com espaço em disco.
  • VACUUM, para devolver ao disco o espaço livre.

Depois

  • Busca de texto completo, JSON e funções definidas pelo usuário.