Skip to content
mobius1qwe edited this page Oct 15, 2026 · 2 revisions

Internos: DBWare 🚧

Home > Guia de contribuição > Internos: DBWare

Para quem mexe no código: módulo, pool de conexões, cache de SQL e os conectores FireDAC, Zeos e SQLdb. Aqui fica o porquê de cada parte ser como é, e o que não pode mudar sem quebrar algo. O código traz só a descrição curta de cada coisa.

Conteúdo da versão 2.0, ainda em desenvolvimento (branch 2.0).

Storage XML e o formato nativo

  • Quem decide se a resposta vai no formato nativo é o link do cliente. O TRALDBSQLCache chama PrepareForDataSet com o dataset que vai ler a resposta; o TRALStorageXMLLink anota se é FireDAC, Zeos ou TBufDataset (pelo nome da classe, para não depender dos pacotes deles) e manda isso nas propriedades do storage. No servidor, o TRALDBModule pergunta ao storage (AllowNative) se aquele par cliente/driver aceita o nativo. Os outros links respondem como antes: nativo só com o mesmo driver.
  • FireDAC grava e lê XML pelo MSXML, que é COM. A thread do servidor e um programa console não iniciaram COM, e o FireDAC falha com "No active document". RALDBFireDAC e RALDBFiredacMemTable chamam CoInitializeEx em volta do sfXML, e só desfazem o que eles fizeram.
  • O fcl-db só lê dfXML. dfXMLUTF8 existe no enum e não tem leitor registrado, nem no FPC 3.3. O texto do dfXML passa pela codepage do sistema, que o RAL deixa em UTF-8 (LazUTF8), então os acentos sobrevivem.
  • O nativo do sqldb abre a query com buffer. TBufDataset.SaveToStream não funciona numa query unidirecional, e o OpenNative do sqldb só serve à resposta nativa. O TRALDBBufDataset carrega a resposta nativa com as definições que vêm nela: as que ele adivinhou do getsqlfields saem antes, como no memtable do FireDAC.

O formato que viaja (RALStorageTypes)

Na 1.x estas funções ficam em RALDBTypes; na 2.0 a unit é RALStorageTypes, e RALDBTypes repassa para ela.

  • Dois valores viajam exatos. NUMERIC/DECIMAL (ftBCD, ftFMTBcd) vão como sftBCD, com os dígitos em texto: como double, 12345678901234.5678 chegaria 12345678901234.6 fora do caminho nativo do FireDAC. E os milissegundos das datas vão no storage BSON (o kxBSON grava segundos inteiros) e no tempo Unix (dtfUnix, com decimais do segundo). Um leitor antigo não lê nenhum dos dois: RALLegacyWire := True grava o formato antigo para um lado cujos pares são mais velhos. A leitura aceita os dois formatos.
  • O stream nativo carrega FieldType, não RALFieldType (TRALDBInfoField.NativeDriver). Um cliente desse driver que monta os campos pelo schema (o Fields Editor monta) tem de usar FieldType, senão o campo não bate com o que carrega: um NUMERIC que o FireDAC leva como ftBCD para a carga de um TFMTBCDField, e uma DATE lida por um TDateTimeField levanta EConvertError. Servidor antigo não manda a chave: lê -1, e o cliente monta o que sempre montou.
  • O tamanho diz se é data, hora ou os dois (RALDateTimeKind): sftDateTime sozinho não diz. Amarrada como data e hora, uma hora chegaria numa coluna TIME do SQLite como '1899-12-30 hh:nn:ss.zzz', que nenhum driver lê de volta. Leitor antigo ignora o tamanho.
  • As tabelas de nomes de tipo são preenchidas na initialization, antes de qualquer thread, e depois só lidas. GetEnumName percorre a tabela RTTI e, no Delphi, devolve UTF-16 que ainda precisa virar StringRAL, por campo e por linha. As tabelas são preenchidas por GetEnumName, não à mão, porque TFieldType muda de um compilador para outro. Preenchidas na primeira chamada, elas correriam entre threads: com 6 threads, 441 de 3000 rodadas deram nome errado ou exceção, e um '' vira ftUnknown, que o servidor recusa ("Field is of an unknown type") nos primeiros pedidos DAO paralelos.
  • Enum que vem do fio se compara como Cardinal. O Delphi compara um enum de até 128 membros como byte com sinal (Win32 e Win64), então Ord(x) > Ord(High(T)) deixa passar de 128 a 255. Um case também não salva: no Win32 o 128 cai no ramo de sftShortInt, e no FPC o salto pela tabela do case dá access violation. Por isso todo valor do fio passa por RALIsFieldTypeOrdinal antes do cast.
  • RALDBResponseError em três passos. AnswerException responde com um param Exception, mas um param sozinho no corpo viaja sem nome (vira o corpo anônimo); param ausente é nil, então as leituras precisam de guarda (é o caminho de erro); e uma resposta só com status (um proxy, um Answer(status)) não pode virar uma exceção de mensagem vazia. O status vai escrito na mensagem, para quem procura pelo número encontrá-lo.

Clone this wiki locally