Cleverton Bueno
← Voltar para todos os artigos

O Mundo Antes do SQL – Uma Viagem ao C-tree e aos Bancos de Dados ISAM

Este artigo recorda como era gerenciar dados antes do SQL, explicando o funcionamento dos bancos ISAM como o C-tree, onde a estrutura vivia no código e o programador era responsável por tudo, incluindo o controle de concorrência.

Olá, pessoal!
Hoje, quando vocês trabalham com PostgreSQL, MySQL, SQL Server ou qualquer outro banco de dados moderno, vocês estão acostumados a um certo luxo. Vocês escrevem um SELECT * FROM clientes WHERE cidade = 'Porto Alegre', e a mágica acontece. Mas já pararam para pensar como a gente gerenciava dezenas de usuários acessando milhões de registros nos anos 90, sem nada disso?
Pois é, hoje eu quero levar vocês em uma viagem no tempo, para a era dos bancos de dados ISAM (Indexed Sequential Access Method). Na CompCet, a nossa ferramenta de escolha, que herdamos e dominamos, era o C-tree. E a primeira coisa que vocês precisam entender é: o C-tree não era um "servidor de banco de dados" que a gente instalava. Ele era uma biblioteca, um conjunto de funções que a gente compilava junto com a nossa aplicação em C. O banco de dados era, literalmente, parte do nosso programa.

A Planta Baixa: Onde a Estrutura Vivia no Código

No mundo SQL de hoje, a primeira coisa que vocês fazem é abrir uma ferramenta e digitar CREATE TABLE. A estrutura da tabela, seus campos e tipos, vivem dentro do banco de dados. Para nós, a "planta baixa" do banco de dados vivia dentro do nosso próprio código, em um arquivo de cabeçalho (.h). Vejam este trecho do nosso fonte, que definia a estrutura da nossa principal tabela, a de Fichas de Atendimento:

Snippet de Código 1:

// Arquivo: CtFic.h
#define FICDAT "/fichas/dados/fic/Fichas" // O nome do arquivo físico no disco

// Definição dos índices
#define NFIC      (_FicDat[0].tfilno)     /* Arquivo de Fichas       */
#define CODFIC    (_FicDat[0].tfilno)+1   /* Indice por Codigo da Ficha */
#define DATFIC    (_FicDat[0].tfilno)+2   /* Indice por Convenio e Data */
#define PACFIC    (_FicDat[0].tfilno)+4   /* Indice por Nome do Paciente */

// Definição dos arquivos de dados
IFIL _FicDat[] =
{
    {
        FICDAT,               /* nome do arquivo de dados */
        -1,                  /* numero do arquivo de dados */
        sizeof(struct Ficha),  /* tamanho do registro de dados em bytes */
        // ... outras configurações ...
        5,                  /* numero de indices associados */
        &_FicIdx[0],         /* apontador para vetor de indices */
        // ...
    }
};

"SELECT? Não, a gente ia buscar na mão!" A maior diferença, porém, estava na hora de consultar os dados. Não existia uma linguagem de consulta. Se eu quisesse encontrar uma ficha pelo nome do paciente, eu não podia simplesmente pedir "me traga todas as fichas do paciente 'João da Silva'".

O processo era manual, navegacional. Nós tínhamos que dizer ao C-tree, passo a passo, o que fazer. O nosso arquivo fonte era uma biblioteca que criamos para facilitar esse trabalho, e os nomes das funções já contam a história:

  • FindFic(NFic, &Fic): Encontrava um atendimento específico. Por baixo dos panos, ela usava a função EQLREC (Equal Record) do C-tree.
  • GetAntFic(&Fic) e GetProFic(&Fic): Pegavam o registro anterior (PRVREC) ou o próximo (NXTREC) em ordem de índice.
  • GetFirstFic(&Fic) e GetLastFic(&Fic): Iam para o primeiro (FRSREC) ou o último (LSTREC) registro do índice.

Se eu quisesse listar todos os pacientes em ordem alfabética, o meu programa precisava fazer um loop:

  • Chamar FirstPacFic() para posicionar no primeiro paciente.
  • Processar aquele registro.
  • Chamar ProPacFic() para ir para o próximo.
  • Repetir até a função retornar um erro de "fim de arquivo".

Era um trabalho procedural, artesanal. Nós não declarávamos o que queríamos; nós programávamos o caminho para chegar lá.

A Lei do Mais Forte: Gerenciando Concorrência Manualmente

Agora, imaginem 20 recepcionistas usando o sistema ao mesmo tempo. O que impedia que duas delas tentassem alterar a mesma ficha de atendimento exatamente no mesmo instante, corrompendo os dados? Hoje, o banco de dados cuida disso com transações e níveis de isolamento. Na nossa época, isso era 100% nossa responsabilidade como programadores.

O manual do C-tree, no capítulo sobre "Record Locking", era a nossa bíblia. Ele explicava o "protocolo de travamento de duas fases" que precisávamos implementar:

  • Fase de Aquisição: Antes de alterar um registro, meu código precisava explicitamente "ligar o modo de travamento", com uma chamada como LKISAM(ENABLE). A partir daí, toda leitura de um registro (RRDREC) tentaria colocar um "cadeado" exclusivo nele.
  • Fase de Liberação: Após modificar e gravar o registro de volta (RWTREC), eu precisava explicitamente "liberar todos os cadeados", com uma chamada LKISAM(FREE).

Era um jogo de alto risco. Se o programa travasse entre o ENABLE e o FREE, aquele registro ficaria "trancado" para sempre, exigindo uma intervenção manual para liberar. Era uma programação tensa, onde cada linha de código importava.

Integridade de Dados: Uma Questão de Disciplina

E quanto à integridade entre as "tabelas"? Se um médico saísse da clínica, o que acontecia com as milhares de fichas associadas a ele? Hoje, um FOREIGN KEY com ON DELETE RESTRICT impediria a exclusão do médico.
Na nossa época, nada impedia. Se eu deletasse o registro do médico, as fichas ficariam "órfãs". A responsabilidade de verificar todas as dependências antes de uma exclusão era, novamente, totalmente do programador. Nós criávamos nossas próprias funções para garantir essa integridade lógica. O C-tree garantia a integridade física do arquivo, impedindo que ele se corrompesse em uma queda de energia, mas a integridade lógica dos dados era nossa arte e nossa ciência.

Conclusão: O Poder e a Grande Responsabilidade

Olhando para trás, o trabalho com o C-tree era fascinante. Ele nos dava um poder e uma velocidade incríveis, porque estávamos operando muito perto do "metal", controlando diretamente o acesso aos arquivos. Nossa aplicação era, em si, o próprio motor do banco de dados.
O preço desse poder era uma responsabilidade imensa. Nós éramos os administradores do banco de dados (DBAs), os otimizadores de consulta e os gerentes de concorrência, tudo isso dentro do nosso código em C.
Hoje, as ferramentas que vocês usam são fantásticas, seguras e abstraem toda essa complexidade. Mas entender como as coisas funcionavam nesse nível fundamental me deu uma base e um respeito pelo trabalho invisível que os bancos de dados modernos fazem por nós. E essa, talvez, seja a maior lição que essa nossa viagem no tempo pode ensinar.