Forum > Databases

TBufDataset is behaving in a very odd way from Release to Debug, and going boom!

(1/4) > >>

Gustavo 'Gus' Carreno:
TBufDataset is behaving in a very odd way from Release to debug, and going boom!

Hey Y'all!!

I'm playing around with in memory tables.
Had a wee Google and found this: How to write in-memory database applications in Lazarus/FPC.

So I went and started by playing with TMemDataset: Test Memory Dataset.

So, with confidence from the above Lazarus Wiki entry, I proceeded to do the exact same test app but using TBufDataset.

Immediately I found myself in trouble because:

* Build Mode Default: After the first push of the Add Data button inserts 3 rows, no data shows and on a subsequent push of the same button we get an AV right on the first Post, as you can see on image 1.
* Build Mode Debug: The app does not give us an AV, but the data shown in the DBGrid is rather suspicious, as you can see on image 2. And in image 3 you see the very top of the humongous heaptrc log produced.
* Build Mode Release: Behaves the same way as Build Mode Default.
I'm completely baffled by this whole thing and I come here, very humbled, to ask for your, very appreciated, help.

I'm attaching the code of the buggy TestBufferDataset attempt.

Many, oh so, many thanks in advance!!

EDIT: I forgot to mentions that I tested this on an Ubuntu 20.04 64b with both Lazarus 2.2.6 and main(aka trunk)

Cheers,
Gus
--
Image 1:
(https://pasteboard.co/EzyWaNkWt11K.png)

Image 2:
(https://pasteboard.co/CpAFTaL63Dlc.png)

Image 3:
(https://pasteboard.co/etyXISEgRjVt.png)

rvk:
Creating the TBufDataset in code seems to work better.
I suspect that defining the fields in the Object Inspector is the problem.


--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---procedure TfrmMain.FormCreate(Sender: TObject);//...  bdsAccounts := TBufDataset.Create(nil);  bdsAccounts.FieldDefs.Add('HASH', ftString, 50);  bdsAccounts.FieldDefs.Add('Alias', ftString, 50);  bdsAccounts.FieldDefs.Add('Label', ftString, 50);  bdsAccounts.FieldDefs.Add('Pending', ftCurrency, 0, 15, false, false, 4, 0);  bdsAccounts.FieldDefs.Add('Balance', ftCurrency, 0, 15, false, false, 5, 0);  bdsAccounts.CreateDataset;  bdsAccounts.Open;   dsAccounts.DataSet := bdsAccounts;  dsAccounts.Enabled := true;
(On Windows it also gives strange results when doing it design-time.)

wp:
Is there a reason why you did not set the BufDataset's Filename? I am not sure if it is possible at all to use TBufdataset without a file, buf even if it is you will lose all records when the application terminates.

Second issue: persistent fields. Once you have persistent fields you cannot access fields by FieldByName[fieldname], but only by their variable name, e.g. bdsAccountsBalance rather than bdsAccounts.FieldByName['Balance']. An annoyance which I already hated in my Delphi times...

I deleted the persistent fields from the lfm file, added a Filename to the bdsAccounts dataset and made sure that the dataset is created properly - now the application does not crash any more:


--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---procedure TfrmMain.FormCreate(Sender: TObject);begin  Caption:= Format('%s v%s', [ Application.Title, cVersion ]);  InitShortcuts;  Application.OnHint:= @DisplayHint;  bdsAccounts.FileName := 'test.db';  if not FileExists(bdsAccounts.FileName) then    bdsAccounts.CreateDataset;  bdsAccounts.Open;end;
Still... Clear does not seam to work. It clears the dataset in the current session, but when the app starts another time the data are back again.

Filtering seems to crash the application still. Well - I would not execute the filter in the OnChange event of the edit control, I would add a button to toggle bdsAccounts.Filtered and use the edit filter text once it is completed. This works, but I don't understand why your code report "Index based on unknown field" while typing the condition...

paweld:
As @rvk wrote define dataset in code and:
1. for DBGrid, you don't need to call BeginUpdate and EndUpdate - everything happens automatically when you change DataSet. when doing a lot of data editing, it is recommended to use DisableControls and EnableCotrols for DataSet   
2. using the Clear medtode for a DataSet will clear not only the data but also the field definitions
3. currency has precision to 4 decimal places

--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---unit Forms.Main; {$mode objfpc}{$H+} interface uses  Classes, SysUtils, BufDataset, DB, Forms, Controls, Graphics, Dialogs, Menus, ActnList, ComCtrls, StdActns, ExtCtrls, StdCtrls, DBGrids; type   { TfrmMain }   TfrmMain = class(TForm)    actAccountsAddData: TAction;    actAccountsClearData: TAction;    alMain: TActionList;    actFileExit: TFileExit;    btnAccountsAddData: TButton;    btnAccountsClearData: TButton;    bdsAccounts: TBufDataset;    dsAccounts: TDataSource;    dgAccounts: TDBGrid;    edtAccountsFilterByAlias: TEdit;    gbFilter: TGroupBox;    lblFilterByAlias: TLabel;    mnuFile: TMenuItem;    mnuFileExit: TMenuItem;    mmMain: TMainMenu;    panButtons: TPanel;    sbMain: TStatusBar;    procedure FormCreate(Sender: TObject);    procedure alMainUpdate(AAction: TBasicAction; var Handled: Boolean);    procedure actAccountsAddDataExecute(Sender: TObject);    procedure actAccountsClearDataExecute(Sender: TObject);    procedure edtAccountsFilterByAliasChange(Sender: TObject);    procedure FormDestroy(Sender: TObject);  private    procedure InitShortcuts;    procedure DisplayHint(Sender: TObject);    procedure CreateDBDef;  public   end; var  frmMain: TfrmMain; implementation uses  LCLType; const  cVersion = '0.1.0'; {$R *.lfm} { TfrmMain } procedure TfrmMain.FormCreate(Sender: TObject);begin  Caption:= Format('%s v%s', [ Application.Title, cVersion ]);  InitShortcuts;  Application.OnHint:= @DisplayHint;  CreateDBDef;  bdsAccounts.Open;end; procedure TfrmMain.InitShortcuts;begin{$IFDEF UNIX}  actFileExit.ShortCut := KeyToShortCut(VK_Q, [ssCtrl]);{$ENDIF}{$IFDEF WINDOWS}  actFileExit.ShortCut := KeyToShortCut(VK_X, [ssAlt]);{$ENDIF}end; procedure TfrmMain.DisplayHint(Sender: TObject);begin  sbMain.SimpleText:= GetShortHint(Application.Hint);end; procedure TfrmMain.CreateDBDef;var  i: Integer;begin  bdsAccounts.Clear;  bdsAccounts.FieldDefs.Add('HASH', ftString, 50);  bdsAccounts.FieldDefs.Add('Alias', ftString, 50);  bdsAccounts.FieldDefs.Add('Label', ftString, 50);  bdsAccounts.FieldDefs.Add('Pending', ftCurrency);  bdsAccounts.FieldDefs.Add('Balance', ftCurrency);  bdsAccounts.IndexDefs.Add('idx_hash', 'HASH', [ixCaseInsensitive]);  bdsAccounts.CreateDataset;  bdsAccounts.IndexFieldNames := 'HASH';  for i := 0 to bdsAccounts.Fields.Count - 1 do  begin    if bdsAccounts.Fields[i].DataType = ftCurrency then      TNumericField(bdsAccounts.Fields[i]).DisplayFormat:= '#,##0.0000 N';  end;end; procedure TfrmMain.alMainUpdate(AAction: TBasicAction; var Handled: Boolean);begin  if AAction = actAccountsClearData then  begin    actAccountsClearData.Enabled:= bdsAccounts.RecordCount > 0;    Handled:= True;  end;end; procedure TfrmMain.actAccountsAddDataExecute(Sender: TObject);begin  bdsAccounts.Insert;  bdsAccounts.FieldByName('HASH').AsString:= 'N2983096817236';  bdsAccounts.FieldByName('Alias').AsString:= 'Alias 1';  bdsAccounts.FieldByName('Label').AsString:= 'Label 1';  bdsAccounts.FieldByName('Pending').AsCurrency:= 0.0;  bdsAccounts.FieldByName('Balance').AsCurrency:= 1000.0;  bdsAccounts.Append;   bdsAccounts.Insert;  bdsAccounts.FieldByName('HASH').AsString:= 'N2983096817696';  bdsAccounts.FieldByName('Alias').AsString:= 'Alias 2';  bdsAccounts.FieldByName('Label').AsString:= 'Label 2';  bdsAccounts.FieldByName('Pending').AsCurrency:= 0.0;  bdsAccounts.FieldByName('Balance').AsCurrency:= 2000.0;  bdsAccounts.Append;   bdsAccounts.Insert;  bdsAccounts.FieldByName('HASH').AsString:= 'N2983696817236';  bdsAccounts.FieldByName('Alias').AsString:= 'Alias 3';  bdsAccounts.FieldByName('Label').AsString:= 'Label 3';  bdsAccounts.FieldByName('Pending').AsCurrency:= 0.0;  bdsAccounts.FieldByName('Balance').AsCurrency:= 3000.0;  bdsAccounts.Append;   bdsAccounts.Last;   dgAccounts.AutoAdjustColumns;end; procedure TfrmMain.actAccountsClearDataExecute(Sender: TObject);begin  bdsAccounts.DisableControls;  bdsAccounts.First;  while not bdsAccounts.EOF do    bdsAccounts.Delete;  bdsAccounts.EnableControls;end; procedure TfrmMain.edtAccountsFilterByAliasChange(Sender: TObject);begin  if Trim(edtAccountsFilterByAlias.Text) = '' then    bdsAccounts.Filtered := False  else  begin    bdsAccounts.FilterOptions:= [foCaseInsensitive];    bdsAccounts.Filter:= Format('Alias=''*%s*''', [Trim(edtAccountsFilterByAlias.Text)]);    bdsAccounts.Filtered:= True;  end;end; procedure TfrmMain.FormDestroy(Sender: TObject);begin  bdsAccounts.Close;end; end.   

wp:
Trying to keep the persistent fields... When they exist they must be used in the following way:

--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---procedure TfrmMain.actAccountsAddDataExecute(Sender: TObject);begin  actAccountsAddData.Enabled:= False;  Application.ProcessMessages;   dgAccounts.BeginUpdate;   bdsAccounts.Append;  bdsAccountsHASH.AsString:= 'N2983096817236';  // instead of bdsAccounts.FieldByName('HASH').AsString := ...  bdsAccountsAlias.AsString:= 'Alias 1';  bdsAccountsLabel.AsString:= 'Label 1';  bdsAccountsPending.AsCurrency:= 0.0;  bdsAccountsBalance.AsCurrency:= 1000.0;  bdsAccounts.Post;   bdsAccounts.Append;  bdsAccountsHASH.AsString:= 'N2983096817696';  bdsAccountsAlias.AsString:= 'Alias 2';  bdsAccountsLabel.AsString:= 'Label 2';  bdsAccountsPending.AsCurrency:= 0.0;  bdsAccountsBalance.AsCurrency:= 2000.0;  bdsAccounts.Post;   bdsAccounts.Append;  bdsAccountsHASH.AsString:= 'N2983696817236';  bdsAccountsAlias.AsString:= 'Alias 3';  bdsAccountsLabel.AsString:= 'Label 3';  bdsAccountsPending.AsCurrency:= 0.0;  bdsAccountsBalance.AsCurrency:= 3000.0;  bdsAccounts.Post;   dgAccounts.AutoAdjustColumns;  dgAccounts.EndUpdate(True);   Application.ProcessMessages;  actAccountsAddData.Enabled:= True;end;  
After having added a filename to bdsAccounts and creating the dataset at first run (like in my other reply), and pressing "Add data" I do get the same empty rows and the crash at the end, as before.

For the next test, I ran the application and closed it immediately afterwards (of course, after having deleted the created database file for a new fresh start). This way an empty database file is created. When I now run it again and click "Add data", the records are added correctly, and the crash at the end does not occur either.

Navigation

[0] Message Index

[#] Next page

Go to full version