Tenho recebido um grande número de pedidos para enviar os updates Delphi 5 Enterprise Update Pack #1 e Delphi 5 ADO Update Pack #2, aparentemente porquê os arquivos não se encontram mais disponíveis no site da CodeGear. Vai entender...
Então aqui estão eles (arquivos zipados com 7zip):
Delphi 5 Enterprise Update Pack #1
Delphi 5 ADO Update Pack #2
Thursday, July 23, 2009
Delph 5 Update Packs
I am receiving a large number of requests to send Delphi 5 Enterprise Update Pack #1 and Delphi 5 ADO Update Pack #2, apparently because they are not available from Embarcadero website anymore. Go figure it out...
So here they are (7zip compressed files):
Delphi 5 Enterprise Update Pack #1
Delphi 5 ADO Update Pack #2
So here they are (7zip compressed files):
Delphi 5 Enterprise Update Pack #1
Delphi 5 ADO Update Pack #2
Friday, July 17, 2009
DataSnap Patch
Lendo o forum da Codegear encontrei um post com uma interessante listagem de patches para o DataSnap do Delphi versões 5, 6 e 7. Uma consulta aos patches mostra que vários deles são bem úteis e contornam erros relativamente comuns:
Unit Provider.pas:
1802
2338
2638
2792
4006
4014
Unit DBClient.pas:
430
1266
1381
1471
1520
1982
2333
4301
5707
Alguns destes patches, senão todos, foram publicados há bastante tempo pelo papa do DataSnap, Dan Miser no site www.distribucon.com. Infelizmente o código fonte dos bug fixes do DataSnap neste site não estão mais acessíveis devido a um erro no site (eventualmente consegue-se uma cópia dos fontes destes patches no cache do google).
Utilizando o WinMerge criei dois patches (arquivos diff) que podem ser utilizados com a ferramenta Patch for Windows. Aplicados aos arquivos Provider.pas e DBClient.pas originais da versão específica do Delphi, gerarão os arquivos fontes modificados em sua versão final, contendo todos os patches.
Atenção: Não utilize o arquivo patch em fontes originais de outra versão que não a especificada.
Provider_patch_D602.txt (Delphi 6.02)
DBClient_patch_D602.txt (Delphi 6.02)
Provider_patch_D71.txt (Delphi 7.1)
DBClient_patch_D71.txt (Delphi 7.1)
Um outro patch para o Provider.pas, contendo todos estes patches e ainda as alterações de um outro post meu sobre modificações no TDataSetProvider pode ser baixado aqui:
Provider_patch_enh_D602.txt (Delphi 6.02 - Bug fixes + enhancements)
Provider_patch_enh_D71.txt (Delphi 7.1 - Bug fixes + enhancements)
O download do executável patch.exe (zipado) pode ser obtido diretamente aqui.
Para quem nunca usou o patch.exe, a linha de comando para transformar o seu fonte original, digamos Provider.pas, no arquivo modificado será:
patch.exe -p1 -b Provider.pas < Provider_patch_D602.txt
Observação: Todos os update packs do Delphi 6 e 7 foram aplicados antes da geração do arquivo de Patch (Delphi 6 Update pack 1 e 2, Delphi 7 Update Pack 1). Logo, estes updates devem ser aplicados ANTES do patch.
Unit Provider.pas:
1802
2338
2638
2792
4006
4014
Unit DBClient.pas:
430
1266
1381
1471
1520
1982
2333
4301
5707
Alguns destes patches, senão todos, foram publicados há bastante tempo pelo papa do DataSnap, Dan Miser no site www.distribucon.com. Infelizmente o código fonte dos bug fixes do DataSnap neste site não estão mais acessíveis devido a um erro no site (eventualmente consegue-se uma cópia dos fontes destes patches no cache do google).
Utilizando o WinMerge criei dois patches (arquivos diff) que podem ser utilizados com a ferramenta Patch for Windows. Aplicados aos arquivos Provider.pas e DBClient.pas originais da versão específica do Delphi, gerarão os arquivos fontes modificados em sua versão final, contendo todos os patches.
Atenção: Não utilize o arquivo patch em fontes originais de outra versão que não a especificada.
Provider_patch_D602.txt (Delphi 6.02)
DBClient_patch_D602.txt (Delphi 6.02)
Provider_patch_D71.txt (Delphi 7.1)
DBClient_patch_D71.txt (Delphi 7.1)
Um outro patch para o Provider.pas, contendo todos estes patches e ainda as alterações de um outro post meu sobre modificações no TDataSetProvider pode ser baixado aqui:
Provider_patch_enh_D602.txt (Delphi 6.02 - Bug fixes + enhancements)
Provider_patch_enh_D71.txt (Delphi 7.1 - Bug fixes + enhancements)
O download do executável patch.exe (zipado) pode ser obtido diretamente aqui.
Para quem nunca usou o patch.exe, a linha de comando para transformar o seu fonte original, digamos Provider.pas, no arquivo modificado será:
patch.exe -p1 -b Provider.pas < Provider_patch_D602.txt
Observação: Todos os update packs do Delphi 6 e 7 foram aplicados antes da geração do arquivo de Patch (Delphi 6 Update pack 1 e 2, Delphi 7 Update Pack 1). Logo, estes updates devem ser aplicados ANTES do patch.
Labels:
DataSetProvider,
DataSnap,
Delphi
Sunday, June 28, 2009
Erro no DataSnap: "LinkFields to detail must be unique"
Estive brigando com um erro "LinkFields to detail must be unique" do DataSnap, numa tela que continha 3 DataSets numa relação Mater-Detail típica. O mais interessante é que este tipo de relação é bem comum e eu já desenvolvi com DataSnap este tipo de construção incontáveis vezes, mas este erro resolveu aparecer para ficar.
Considere as seguintes tabelas numa relação Master-Detail:
Os campos id_master (PK), id_detail (PK) e id_detail2 (PK) são inteiros e os desc_master, desc_detail e desc_detail2 são Varchar. O campo id_master é FK na tabela detalhe de primeiro nível, table_detail. O campo id_detail é FK na tabela detalhe de segundo nível, table_detail2. As PKs são todas auto-incremento com valor gerado no servidor (de aplicação).
Tenho um DataModule com 3 DataSets (no meu caso, usei TADOQuery) contendo os seguintes comandos SQL:
Existem ainda 2 componentes TDataSource ligando o qryDetail ao qryMaster (dsMaster) pela propriedade DataSource, e também um ligando o qryDetail2 ao qryDetail (dsDetail). Desta forma, tenho um DataSetProvider ligado ao DataSource dsMaster que irá prover os dados aos meus ClientDataSet. Eis meu DataModule:

O cdsMaster está ligado ao DataSetProvider, prvMaster, o cdsDetail ligado ao DataSetField cdsMasterqryDetail, e o cdsDetail2 está ligado ao DataSetField cdsDetailqryDetail2. Uma construção comum quando se trata de relação master-detail usando DataSnap.
O fato de estar tudo junto (apenas 2 camadas) facilita o entendimento e o debug. Mas poderia estar distribuído, com as queries e o provider num servidor de aplicação e os ClientDataSets na aplicação cliente.
ProviderFlags dos campos configurados corretamente (incluído pfInKey para id_master, id_detail e id_detail2 nas 3 queries), um form simples com 3 DBNavigators e 3 DBGrids ligados aos 3 ClientDataSets. Pois bem, era de se esperar que tudo funcionasse as mil maravilhas na primeira tentativa, certo? Errado!
Ao rodar o programa, inseri dados na tabela master e salvei, chamando então cdsMaster.ApplyUpdates. Tudo Ok. Inseri também registros no cdsDetail, tudo Ok. Ao rodar a aplicação e inserir registros no cdsDetail2 (o detalhe de segundo nível) e chamar o cdsMaster.ApplyUpdates, kabooommmm! Lá vem o erro "Link Fields to detail must be unique". Não fazia o menor sentido uma vez que os FK's são corretamente preenchidos automaticamente pelo DataSnap e os ID's são preenchidos com valores únicos negativos. Não havia nada errado!
O erro acontece ao aplicar os updates. Mas nem chega ao Provider. O erro acontece mesmo no ClientDataSet ANTES de enviar o Update ao Provider.
Aí fui debugar usando o DBClient.pas. O erro ocorre no método TCustomClientDataSet.InternalPost, na linha:
Ou seja... Como isto ocorre dentro do Midas.dll (ou MidasLib.dcu), não há como debugar além deste ponto.
Usei o Google atrás de uma solução, fiz todas as alterações possíveis que pude imaginar para ver se o erro cessava, mas nada funcionava.
Depois de um tempo pensando, eu me lembrei que esta construção onde a PK da tabela master vira FK da tabela detalhe não me é usual. Eu sempre propago as chaves do mestre para compor a chave do detalhe, da forma:
table_detail:
table_detail2:
Após isto o Update ocorre normalmente, sem erros. Ainda esta semana vou anexar o código fonte completo do projeto de exemplo.
Considere as seguintes tabelas numa relação Master-Detail:

Tenho um DataModule com 3 DataSets (no meu caso, usei TADOQuery) contendo os seguintes comandos SQL:
- qryMaster:
SELECT * FROM table_master
- qryDetail:
SELECT * FROM table_detail WHERE id_master = :id_master
- qryDetail2:
SELECT * FROM table_detail2 WHERE id_detail = :id_detail
Existem ainda 2 componentes TDataSource ligando o qryDetail ao qryMaster (dsMaster) pela propriedade DataSource, e também um ligando o qryDetail2 ao qryDetail (dsDetail). Desta forma, tenho um DataSetProvider ligado ao DataSource dsMaster que irá prover os dados aos meus ClientDataSet. Eis meu DataModule:

O cdsMaster está ligado ao DataSetProvider, prvMaster, o cdsDetail ligado ao DataSetField cdsMasterqryDetail, e o cdsDetail2 está ligado ao DataSetField cdsDetailqryDetail2. Uma construção comum quando se trata de relação master-detail usando DataSnap.
O fato de estar tudo junto (apenas 2 camadas) facilita o entendimento e o debug. Mas poderia estar distribuído, com as queries e o provider num servidor de aplicação e os ClientDataSets na aplicação cliente.
ProviderFlags dos campos configurados corretamente (incluído pfInKey para id_master, id_detail e id_detail2 nas 3 queries), um form simples com 3 DBNavigators e 3 DBGrids ligados aos 3 ClientDataSets. Pois bem, era de se esperar que tudo funcionasse as mil maravilhas na primeira tentativa, certo? Errado!
Ao rodar o programa, inseri dados na tabela master e salvei, chamando então cdsMaster.ApplyUpdates. Tudo Ok. Inseri também registros no cdsDetail, tudo Ok. Ao rodar a aplicação e inserir registros no cdsDetail2 (o detalhe de segundo nível) e chamar o cdsMaster.ApplyUpdates, kabooommmm! Lá vem o erro "Link Fields to detail must be unique". Não fazia o menor sentido uma vez que os FK's são corretamente preenchidos automaticamente pelo DataSnap e os ID's são preenchidos com valores únicos negativos. Não havia nada errado!
O erro acontece ao aplicar os updates. Mas nem chega ao Provider. O erro acontece mesmo no ClientDataSet ANTES de enviar o Update ao Provider.
Aí fui debugar usando o DBClient.pas. O erro ocorre no método TCustomClientDataSet.InternalPost, na linha:
Check(FDSCursor.InsertRecord(ActiveBuffer));
Ou seja... Como isto ocorre dentro do Midas.dll (ou MidasLib.dcu), não há como debugar além deste ponto.
Usei o Google atrás de uma solução, fiz todas as alterações possíveis que pude imaginar para ver se o erro cessava, mas nada funcionava.
Depois de um tempo pensando, eu me lembrei que esta construção onde a PK da tabela master vira FK da tabela detalhe não me é usual. Eu sempre propago as chaves do mestre para compor a chave do detalhe, da forma:
table_detail:
- id_master (PK)
- id_detail (PK)
- desc_detail
table_detail2:
- id_master (PK)
- id_detail (PK)
- id_detail2 (PK)
- desc_detail
Após isto o Update ocorre normalmente, sem erros. Ainda esta semana vou anexar o código fonte completo do projeto de exemplo.
Friday, April 17, 2009
Hierarquia de Classes do Intraweb
Toda vez que preciso determinar a hierarquia de controles Intraweb ou bibliotecas de terceiros, como a da ArcanaTech, preciso fazer uma de duas coisas:
1) Obter a hierarquia via código
2) Procurar por muito tempo usando a documentação, ou o Google ou no código fonte (o IW não vem com código fonte completo, logo não é muito eficaz).
Entãou vou documentar a hierarquia de algumas classes aqui. À medida que for tendo tempo, pretendo incluir a maioria das classes do Intraweb no diagrama de classes.

1) Obter a hierarquia via código
2) Procurar por muito tempo usando a documentação, ou o Google ou no código fonte (o IW não vem com código fonte completo, logo não é muito eficaz).
Entãou vou documentar a hierarquia de algumas classes aqui. À medida que for tendo tempo, pretendo incluir a maioria das classes do Intraweb no diagrama de classes.

Monday, April 13, 2009
Configurações "secretas" do BDS 2006
Ontem fui tentar criar um controle ActiveX no meu BDS 2006 e para minha surpresa não havia mais o respectivo Wizard. Abri o Delphi 6 e lá estava ele, então onde está o do BDS 2006? Como não tinha alternativa, crei o controle usando o Delphi 6, e depois recompilei-o no BDS 2006. Mas queria saber porquê esta opção não estava mais diponível. Pesquisando encontrei as chaves responsáveis pela habilitação ou não de várias opções de criação de novos itens. Segue abaixo o arquivo .REG contendo as entradas que estão faltando.
Windows Registry Editor Version 5.00
[HKEY_CURRENT_USER\Software\Borland\BDS\4.0\Type Library] "ActiveXWizard"="True"
"TransactionalWizards"="True"
"AxRegMenuCheckFile"="True"
"EmbeddedTypeLibraryEditor"="True"
"InteropCheck"="True"
"DefaultPersonality"="Delphi.Personality"
Wednesday, March 4, 2009
Patching TDataSetProvider
Observação: Se aplica ao Delphi 6 e 7.
Depois de gastar muitas horas implementando alguma "mágica" e novas funcionalidades em um descendente direto do TDataSetProvider que uso em meus sistemas, usando o BDS 2006, parti para implementar as mesmas funcionalidades no TDataSetProvider do Delphi 6 (a empresa na qual trabalho possui sistemas em Delphi 6 cuja migração imediata para um compilador superior é inviável). Simplesmente abri minha unit no Delphi 6 e fui compilar em um projeto vazio e POWWWW!!!
Problema: Não existe o método DoBeforeUpdateRecord no TBaseProvider (ancestral do TDataSetProvider).
Toda a nova funcionalidade estava baseada em um novo DoBeforeUpdateRecord do TDataSetProvider. O que eu queria fazer é relativamente simples: Fazer algo que o TDataSetProvider padrão não faz, antes do update de cada registro, mais precisamente nos inserts.
O evento DoBeforeUpdateRecord do Provider é chamado pela classe Resolver durante o processo de update (no BDS 2006 em diante). Sem o método DoBeforeUpdateRecord virtual, eu teria que arrumar outra alternativa.
Tentei o InternalApplyUpdates, sem chance! Não tem como fazer o que queria por lá. ApplyUpdates então? Sem chance de novo! O método é estático e mesmo que fosse dinâmico eu teria que desviar a chamada para o evento BeforeUpdateRecord original, uma coisa que não me agradou.....
Tentei durante um bom tempo e sempre esbarrava em métodos estáticos que deveriam ser dinâmicos, protegidos que deveriam ser públicos, propriedades que deviam ser públicas e eram privadas...
Resultado: Não é viável fazer no Delphi 6!!! E então?
Solução: Bem, não gosto de modificar o fonte da VCL, mas neste caso é bem justificável e imprescindível. De quebra ainda corrigiria um bug antigo (http://www.distribucon.com/midasbug/index.aspx).
As modificações são simples, retiradas da própria unit Provider.pas porém da versão BDS 2006. Não têm absolutamente nenhum impacto no funcionamento e abrem grandes possibilidades de customização dos DataSetProviders. O mesmo pode ser feito no Delphi 7, e após o resultado que obtive, eu aconselho.
Segue a lista de modificações que fiz. As linhas adicionadas ou modificadas estão marcadas em azul:
Após a alteração no código fonte da VCL (você fez backup do original, certo?) basta salvá-lo, incluí-lo em um projeto e compilar o projeto.
A melhor forma de utilizar patches deste tipo para substituir o código original da DCU que geralmente é linkada ao executável é criar um diretório de patches para o seu Delphi, colocar lá os arquivos fontes modificados (neste caso Provider.pas) e incluir este caminho no LibraryPath do seu IDE.
Em um próximo post vou escrever sobre as modificações que fiz no TDataSetProvider, ou melhor, no descendente dele que uso.
Depois de gastar muitas horas implementando alguma "mágica" e novas funcionalidades em um descendente direto do TDataSetProvider que uso em meus sistemas, usando o BDS 2006, parti para implementar as mesmas funcionalidades no TDataSetProvider do Delphi 6 (a empresa na qual trabalho possui sistemas em Delphi 6 cuja migração imediata para um compilador superior é inviável). Simplesmente abri minha unit no Delphi 6 e fui compilar em um projeto vazio e POWWWW!!!
Problema: Não existe o método DoBeforeUpdateRecord no TBaseProvider (ancestral do TDataSetProvider).
Toda a nova funcionalidade estava baseada em um novo DoBeforeUpdateRecord do TDataSetProvider. O que eu queria fazer é relativamente simples: Fazer algo que o TDataSetProvider padrão não faz, antes do update de cada registro, mais precisamente nos inserts.
O evento DoBeforeUpdateRecord do Provider é chamado pela classe Resolver durante o processo de update (no BDS 2006 em diante). Sem o método DoBeforeUpdateRecord virtual, eu teria que arrumar outra alternativa.
Tentei o InternalApplyUpdates, sem chance! Não tem como fazer o que queria por lá. ApplyUpdates então? Sem chance de novo! O método é estático e mesmo que fosse dinâmico eu teria que desviar a chamada para o evento BeforeUpdateRecord original, uma coisa que não me agradou.....
Tentei durante um bom tempo e sempre esbarrava em métodos estáticos que deveriam ser dinâmicos, protegidos que deveriam ser públicos, propriedades que deviam ser públicas e eram privadas...
Resultado: Não é viável fazer no Delphi 6!!! E então?
Solução: Bem, não gosto de modificar o fonte da VCL, mas neste caso é bem justificável e imprescindível. De quebra ainda corrigiria um bug antigo (http://www.distribucon.com/midasbug/index.aspx).
As modificações são simples, retiradas da própria unit Provider.pas porém da versão BDS 2006. Não têm absolutamente nenhum impacto no funcionamento e abrem grandes possibilidades de customização dos DataSetProviders. O mesmo pode ser feito no Delphi 7, e após o resultado que obtive, eu aconselho.
Segue a lista de modificações que fiz. As linhas adicionadas ou modificadas estão marcadas em azul:
TBaseProvider = class(TCustomProvider)
protected
procedure DoBeforeUpdateRecord(SourceDS: TDataSet; DeltaDS: TCustomClientDataSet;
UpdateKind: TUpdateKind; var Applied: Boolean); virtual;
procedure DoAfterUpdateRecord(SourceDS: TDataSet; DeltaDS: TCustomClientDataSet;
UpdateKind: TUpdateKind); virtual;
end;
TUpdateTree = class(TObject)
public
procedure Clear;
function DoUpdates: Boolean;
procedure RefreshData(Options: TFetchOptions);
procedure InitErrorPacket(E: EUpdateError; Response: TResolverResponse);
procedure InitData(ASource: TDataSet);
procedure InitDelta(const ADelta: OleVariant); overload;
procedure InitDelta(ADelta: TPacketDataSet); overload;
property Data: Pointer read FData write FData;
property Delta: TPacketDataSet read FDeltaDS;
property DetailCount: Integer read GetDetailCount;
property Details[Index: Integer]: TUpdateTree read GetDetail;
property ErrorDS: TPacketDataSet read GetErrorDS;
property HasErrors: Boolean read GetHasErrors;
property Name: string read FName write FName;
property Parent: TUpdateTree read FParent;
property Source: TDataSet read FSourceDS;
property IsNested: Boolean read GetIsNested;
end;
TCustomResolver = class(TComponent)
public
property Provider: TBaseProvider read FProvider;
property UpdateTree: TUpdateTree read FUpdateTree;
end;
// Implementation
procedure TBaseProvider.DoBeforeUpdateRecord(SourceDS: TDataSet;
DeltaDS: TCustomClientDataSet; UpdateKind: TUpdateKind; var Applied: Boolean);
begin
if Assigned(FBeforeUpdateRecord) then
FBeforeUpdateRecord(Self, SourceDS, DeltaDS, UpdateKind, Applied);
end;
procedure TBaseProvider.DoAfterUpdateRecord(SourceDS: TDataSet;
DeltaDS: TCustomClientDataSet; UpdateKind: TUpdateKind);
begin
if Assigned(FAfterUpdateRecord) then
FAfterUpdateRecord(Self, SourceDS, DeltaDS, UpdateKind);
end;
procedure TDataSetProvider.SetDataSet(ADataSet: TDataSet);
begin
FDataSet := ADataSet;
if Assigned(FDataSet) then
FDataSet.FreeNotification(Self);
end;
function TCustomResolver.InternalUpdateRecord(Tree: TUpdateTree): Boolean;
var
RecNoSave: Integer;
Applied: Boolean;
UpdateKind: TUpdateKind;
E: Exception;
PrevErr, Err: EUpdateError;
begin
PrevErr := nil;
Err := nil;
Tree.Delta.UseCurValues := False;
while True do
try
UpdateKind := Tree.Delta.UpdateKind;
if ((UpdateKind = ukInsert) and (FPrevResponse in [rrMerge, rrApply])) or
((FPrevResponse = rrMerge) and Tree.Delta.HasMergeConflicts) then
DatabaseError(SInvalidResponse);
Applied := False;
RecNoSave := Tree.Delta.RecNo;
try
Provider.DoBeforeUpdateRecord(Tree.Source, Tree.Delta, UpdateKind, Applied); (* ACM patch *)
finally
if Tree.Delta.RecNo <> RecNoSave then
Tree.Delta.RecNo := RecNoSave;
end;
if not Applied then
case UpdateKind of
ukModify:
begin
if poDisableEdits in Provider.Options then
raise Exception.CreateRes(@SNoEditsAllowed);
DoUpdate(Tree);
end;
ukDelete:
begin
if poDisableDeletes in Provider.Options then
raise Exception.CreateRes(@SNoDeletesAllowed);
DoDelete(Tree);
end;
ukInsert:
begin
if poDisableInserts in Provider.Options then
raise Exception.CreateRes(@SNoInsertsAllowed);
DoInsert(Tree);
end;
end;
Provider.DoAfterUpdateRecord(Tree.Source, Tree.Delta, UpdateKind); (* ACM patch *)
if (poPropogateChanges in Provider.Options) and Tree.Delta.NewValuesModified then
LogUpdateRecord(Tree);
Break;
except
E := AcquireExceptionObject;
PrevErr.Free;
PrevErr := Err;
Err := IProviderSupport(Tree.Source).PSGetUpdateException(E, PrevErr);
if HandleUpdateError(Tree, Err, FMaxErrors, FErrorCount) then
begin
Tree.Delta.UseCurValues := True;
Continue;
end else
break;
end;
PrevErr.Free;
Err.Free;
FPrevResponse := rrSkip;
Result := FErrorCount <= FMaxErrors;
end;
Após a alteração no código fonte da VCL (você fez backup do original, certo?) basta salvá-lo, incluí-lo em um projeto e compilar o projeto.
A melhor forma de utilizar patches deste tipo para substituir o código original da DCU que geralmente é linkada ao executável é criar um diretório de patches para o seu Delphi, colocar lá os arquivos fontes modificados (neste caso Provider.pas) e incluir este caminho no LibraryPath do seu IDE.
Em um próximo post vou escrever sobre as modificações que fiz no TDataSetProvider, ou melhor, no descendente dele que uso.
Labels:
DataSetProvider,
DataSnap,
Delphi
Subscribe to:
Posts (Atom)