I'm using TFIBDataSet (Firebird dataset component) and I'm trying to automatically create all fields at runtime, without loading any record into the dataset.
Here is the code of my test:
uses
FIBDatabase, FIBDataSet, Dialogs;
...
var
Db : TFIBDataBase;
Tr : TFIBTransaction;
Dst : TFIBDataSet;
begin
//connection
Db := TFIBDatabase.Create(Self);
Db.ConnectParams.UserName := 'SYSDBA';
Db.ConnectParams.Password := 'masterkey';
Db.DatabaseName := 'localhost:mydatabase.fdb';
Db.SQLDialect := 3;
Db.Connected := True;
//transaction
Tr := TFIBTransaction.Create(Self);
Tr.DefaultDatabase := Db;
Tr.Active := True;
//dataset
Dst := TFIBDataSet.Create(Self);
Dst.Database := Db;
Dst.Transaction := Tr;
Dst.SelectSQL.Text := 'SELECT * FROM rdb$database';
//...
ShowMessage(IntToStr(Dst.FieldCount));
end;
The previous code produces '0' as output..
I've tried using Dst.Open() and it produces '5', but it also executes the SQL query.
I did it by calling FieldDefs.Update and creating the fields from the FieldDefs list.
uses
FIBDatabase, FIBDataSet, Dialogs;
...
var
Db : TFIBDataBase;
Tr : TFIBTransaction;
Dst : TFIBDataSet;
I : integer;
begin
//connection
Db := TFIBDatabase.Create(Self);
Db.ConnectParams.UserName := 'SYSDBA';
Db.ConnectParams.Password := 'masterkey';
Db.DatabaseName := 'localhost:mydatabase.fdb';
Db.SQLDialect := 3;
Db.Connected := True;
//transaction
Tr := TFIBTransaction.Create(Self);
Tr.DefaultDatabase := Db;
Tr.Active := True;
//dataset
Dst := TFIBDataSet.Create(Self);
Dst.Database := Db;
Dst.Transaction := Tr;
Dst.SelectSQL.Text := 'SELECT * FROM rdb$database';
//create fields
Dst.FieldDefs.Update();
for I := 0 to Dst.FieldDefs.Count - 1 do
Dst.FieldDefs[I].CreateField(Dst);
ShowMessage(IntToStr(Dst.FieldCount));
end;
Related
I have Delphi application that connected to SQL server using ADODB component like (TAdoQuery , TADOStoredProcedure , TADOConnection .....)
Now am trying to migrating from ADODB Connection to FireDac in delphi 10.3 Rio ,
but how I can passing Recordset from TFDQuery to TFDStoredProcedure like this :
enter code here
var
Qry: TAdoQuery;
List: TADOStoredProc;
i: Integer;
Item: TCurrencyVal;
begin
Qry := TAdoQuery.Create(nil);
Qry.Connection := Connection;
Qry.SQL.Add('SELECT * FROM Currency WHERE ID=' + IntToStr(ID));
Qry.SQL.Add('SELECT * FROM CurrencyValues WHERE CurrencyID=' + IntToStr(ID));
Qry.Open;
Result := nil;
if Qry.RecordCount = 1 then
begin
Result := TCurrency.Create(AOwner);
TCurrency(Result).ID := ID;
TCurrency(Result).FCode := Qry.FieldByName('Code').AsString;
TCurrency(Result).FName := Qry.FieldByName('Name').AsString;
TCurrency(Result).FDefaultPrice := Qry.FieldByName('DefaultPrice').AsFloat;
TCurrency(Result).FDefaultCurrency := Qry.FieldByName('DefaultCurrency')
.AsBoolean;
TCurrency(Result).FPartName := Qry.FieldByName('PartName').AsString;
TCurrency(Result).FDecimalDigits := Qry.FieldByName('DecimalDigits')
.AsInteger;
TCurrency(Result).FAccountID := Qry.FieldByName('AccountID').AsInteger;
TCurrency(Result).FMajorGender := Qry.FieldByName('MajorGender').AsInteger;;
TCurrency(Result).FPluralMajor := Qry.FieldByName('PluralMajor').AsString;
TCurrency(Result).FMinorGender := Qry.FieldByName('MinorGender').AsInteger;;
TCurrency(Result).FPluralMinor := Qry.FieldByName('PluralMinor').AsString;
TCurrency(Result).FFracsInUnit := Qry.FieldByName('FracsInUnit').AsInteger;;
TCurrency(Result).FCountryName := Qry.FieldByName('CountryName').AsString;
List := TADOStoredProc.Create(Result);
List.Recordset := Qry.NextRecordset(i);
for i := 0 to List.RecordCount - 1 do
begin
List.RecNo := i + 1;
Item := TCurrencyVal.Create(Result);
Item.ID := List.FieldByName('ID').AsInteger;
Item.Date := List.FieldByName('Date').AsDateTime;
Item.SellPrice := List.FieldByName('Value').AsFloat;
TCurrency(Result).AddItem(Item);
end;
List.Close;
List.Free;
end;
I have created a FDMemTable with following structure:
Cds_NaMenu := TFDMemTable.Create(nil);
Cds_NaMenu.FieldDefs.Add('ID', ftInteger);
Cds_NaMenu.FieldDefs.Add('MN_TELA_CODIGO', ftInteger);
Cds_NaMenu.FieldDefs.Add('MN_MENU_PESQUISA', ftString, 500);
Cds_NaMenu.FieldDefs.Add('DISPONIBILIDADE', ftInteger);
Cds_NaMenu.IndexDefs.Add('Ordem', 'MN_TELA_CODIGO', []);
Cds_NaMenu.CreateDataSet;
Cds_NaMenu.LogChanges := False;
Cds_NaMenu.IndexName := 'Ordem';
I put data in TFDMemTable like this:
Cds_NaMenu.Append;
Cds_NaMenu.FieldByName('DISPONIBILIDADE').AsInteger := 1;
Cds_NaMenu.Post;
Well... The problem ocurrs when a set filtered property to True and back to False. The RecordCount property goes to 0; None data has found in, even I use saveToFile procedure. Aparently the data was losted.
_recCount := Cds_NaMenu.RecordCount; // Result = 867;
Cds_NaMenu.Filter := 'DISPONIBILIDADE=1 AND MN_MENU_PESQUISA like ' + QuotedStr('%' + sTexto + '%');
Cds_NaMenu.Filtered := True;
_recCount := Cds_NaMenu.RecordCount; // Result = 0;
Cds_NaMenu.Filtered := False;
Cds_NaMenu.Filter := '';
_recCount := Cds_NaMenu.RecordCount; // Result = 0;
PS: With ClientDataSet, this code works perfectly
If you're working with a pure memory table, there should not be any problem to query record count by the RecordCount property. Maybe you expect having NULL and empty value records included in a filtered view when having filter Value LIKE '%%', but it's not so. When having dataset like this:
ID | Value
1 | NULL
2 | ''
3 | 'Some text'
And applying filter like this:
var
S: string;
begin
S := '';
FDMemTable.Filtered := False;
FDMemTable.Filter := 'Value LIKE ' + QuotedStr('%' + S + '%');
FDMemTable.Filtered := True;
{ ← FDMemTable.RecordCount should be 1 here for the above dataset }
end;
The empty and NULL value records should not be included in the view. Here is a short proof:
var
S: string;
MemTable: TFDMemTable;
begin
MemTable := TFDMemTable.Create(nil);
try
MemTable.FieldDefs.Add('ID', ftInteger);
MemTable.FieldDefs.Add('Value', ftString, 500);
MemTable.IndexDefs.Add('PK_ID', 'ID', [ixPrimary]);
MemTable.CreateDataSet;
MemTable.AppendRecord([1, NULL]);
MemTable.AppendRecord([2, '']);
MemTable.AppendRecord([3, 'Some text']);
S := '';
MemTable.Filtered := False;
MemTable.Filter := 'Value LIKE ' + QuotedStr('%' + S + '%');
ShowMessage(Format('Total count: %d', [MemTable.RecordCount])); { ← should be 3 }
MemTable.Filtered := True;
ShowMessage(Format('Filtered count: %d', [MemTable.RecordCount])); { ← should be 1 }
MemTable.Filtered := False;
ShowMessage(Format('Total count: %d', [MemTable.RecordCount])); { ← should be 3 }
finally
MemTable.Free;
end;
end;
I think this is just a minor FD quirk. The code below works as expected, with Cds_NaMenu declared as a TFDMemTable (though it would have been nice if you could have dropped the Cds_ to avoid confusion).
The key difference, I think, is the call to .Locate after the filter is cleared. The reason I put it there is because it causes the dataset to scroll and, I imagine, to recalculate its RecordCount as a result. Probably any other operation which causes a scroll would have the same effect, even MoveBy(0) - try it.
procedure TForm1.FormCreate(Sender: TObject);
var
_recCount : Integer;
ID : Integer;
sTexto : String;
begin
sTexto := 'xxx'; // added
Cds_NaMenu.FieldDefs.Add('ID', ftInteger);
Cds_NaMenu.FieldDefs.Add('MN_TELA_CODIGO', ftInteger);
Cds_NaMenu.FieldDefs.Add('MN_MENU_PESQUISA', ftString, 500);
Cds_NaMenu.FieldDefs.Add('DISPONIBILIDADE', ftInteger);
Cds_NaMenu.IndexDefs.Add('Ordem', 'MN_TELA_CODIGO', []);
Cds_NaMenu.CreateDataSet;
Cds_NaMenu.LogChanges := False;
Cds_NaMenu.IndexName := 'Ordem';
Cds_NaMenu.Append;
Cds_NaMenu.FieldByName('ID').AsInteger := 666; // added
Cds_NaMenu.FieldByName('DISPONIBILIDADE').AsInteger := 1;
Cds_NaMenu.Post;
_recCount := Cds_NaMenu.RecordCount; // Result = 1
ID := Cds_NaMenu.FieldByName('ID').AsInteger; // added
Cds_NaMenu.Filter := 'DISPONIBILIDADE=1 AND MN_MENU_PESQUISA like ' + QuotedStr('%' + sTexto + '%');
Cds_NaMenu.Filtered := True;
_recCount := Cds_NaMenu.RecordCount; // Result = 0;
Cds_NaMenu.Filtered := False;
Cds_NaMenu.Filter := '';
// Now force the dataset to scroll
if Cds_NaMenu.Locate('ID', ID, []) then; // added
_recCount := Cds_NaMenu.RecordCount; // Result = 1;
Caption := IntToStr(_recCount); // added
end;
My incoming data is loaded in a TFDMemTable, (the reader). The writer is a TFDQuery.
Incoming data should be inserted if not in the target, otherwise updated. Matches are based on the UUID field.
I am unable to properly define that the UUID field is the key.
Here is a code example - does not work. FBatchMove.Execute fails because cannot it find any key fields.
procedure TSubDB.FindDestRecord(ASender: TObject; var AFound: Boolean);
var
aSrc: TBytes;
begin
SetLength(aSrc, 16);
aSrc := FReader.DataSet.FieldByName('UUID').AsBytes;
AFound := FWriter.DataSet.Locate('UUID', aSrc, []);
end;
function TSubDB.LoadDB(const aFilename: string): boolean;
var
FQry: TFDQuery;
FBatchMove: TFDBatchMove;
FReader: TFDBatchMoveDataSetReader;
FWriter: TFDBatchMoveDataSetWriter;
FMemTable: TFDMemTable;
begin
FQry := TFDQuery.Create(nil);
FQry.Connection := dmFB.myDB;
FQry.FetchOptions.AssignedValues := [evItems];
FQry.FetchOptions.Items := [fiBlobs, fiDetails];
FBatchMove := TFDBatchMove.Create(nil);
FBatchMove.Analyze := [taDelimSep, taHeader, taFields];
FReader := TFDBatchMoveDataSetReader.Create(FBatchMove);
FWriter := TFDBatchMoveDataSetWriter.Create(FBatchMove);
FMemTable := TFDMemTable.Create(nil);
try
FMemTable.LoadFromFile(aFileName, sfBinary);
//Not sure how to make the BatchMove recognize that UUID is the key for OnFindDestRecord
FMemTable.IndexFieldNames := 'UUID';
with FMemTable.Indexes.Add do
begin
Name :='idxUUID';
Fields := 'UUID';
Active := true;
end;
FMemTable.IndexName := 'idxUUID';
FMemTable.IndexesActive := true;
FMemTable.FieldByName('UUID').ProviderFlags := FMemTable.FieldByName('UUID').ProviderFlags + [pfInKey];
FReader.DataSet := FMemTable;
FQry.SQL.Text := 'select * from test';
FWriter.DataSet := FQry;
FBatchMove.OnFindDestRecord := FindDestRecord;
FBatchMove.Mode := dmAppendUpdate;
//None of the above seems to keep the pfInKey in the UUID field's ProviderFlags
FBatchMove.Execute;
FQry.Open;
FQry.Close;
finally
FMemTable.Free;
FWriter.Free;
FReader.Free;
FBatchMove.Free;
FQry.Free;
end;
end;
I would really appreciate a working example of batch move (where the target has data, so the batch move mode is dmAppendUpdate).
The key here is that the writer needs to be a TFDBatchMoveSQLWriter with a TableName set. This way the destination had the primary key defined and it is then used to decide whether to insert or update.
function TSubDB.LoadDB(const aFilename: string): boolean;
var
FQry: TFDQuery;
FBatchMove: TFDBatchMove;
FReader: TFDBatchMoveDataSetReader;
FWriter: TFDBatchMoveSQLWriter;
FMemTable: TFDMemTable;
begin
FQry := TFDQuery.Create(nil);
FQry.Connection := dmFB.myDB;
FQry.FetchOptions.AssignedValues := [evItems];
FQry.FetchOptions.Items := [fiBlobs, fiDetails];
FBatchMove := TFDBatchMove.Create(nil);
FBatchMove.Analyze := [taDelimSep, taHeader, taFields];
FReader := TFDBatchMoveDataSetReader.Create(FBatchMove);
FWriter := TFDBatchMoveSQLWriter.Create(FBatchMove);
FMemTable := TFDMemTable.Create(nil);
try
FMemTable.LoadFromFile(aFileName, sfBinary);
FReader.DataSet := FMemTable;
FQry.SQL.Text := 'select * from test';
FWriter.Connection := dmFB.myDB;
FWriter.TableName := 'test';
FBatchMove.Mode := dmAppendUpdate;
FBatchMove.Execute;
FQry.Open;
FQry.Close;
finally
FMemTable.Free;
FWriter.Free;
FReader.Free;
FBatchMove.Free;
FQry.Free;
end;
end;
Seems like i need some help with a project.
I have a routine , that constructs run time multiple TabItems on to a page control in firemonkey, and i want to have a close button on the tab.
The new tab has a checkbox for the close button loading from the styler of the tabitems.
The page has a default tab, and within some button, i am adding run time the new tab items.
I have managed to apply the event for closing the default tab page, but doesn't work within the run time created tab pages. Any help would be appreciated.
This is the piece of code for the runtime tabitems
procedure TForm1.Button1Click(Sender: TObject);
var
t : TTabItem;
o : TFmxObject;
i : Integer;
c : TControl;
begin
t := TTabItem.Create(pgeControl);
t.Parent := pgeControl;
o := FindBinding('imgCloseTabPage');
if o<>nil then
begin
for i := 0 to ComponentCount - 1 do
begin
if Components[i] is TCheckBox then
begin
TCheckBox(Components[i]).OnClick := CheckBox1Click;
end;
end;
end;
if pgeControl.TabCount - 1 <= nTab then
begin
nTab := nTab + 1;
t.Index := nTab
end
else
begin
t.Index := pgeControl.TabCount - 1;
nTab := pgeControl.TabCount - 1;
end;
t.Tag := nTab;
t.Text := 'Some text...' + ' ' + IntToStr(nTab);
t.Name := 'tabPatient' + IntToStr(nTab);
t.Height := 35;
t.Width := 250;
t.Margins.Top := 0;
t.Margins.Left := 0;
t.Margins.Bottom := 0;
t.Margins.Right := 0;
t.Padding.Top := -5;
t.Padding.Left := 0;
t.Padding.Bottom := 0;
t.Padding.Right := 0;
t.TextAlign := TTextAlign.taLeading;
t.Width := (Length(t.Text) * 6 ) + 60;
t.Font.Size := 15;
t.StyleLookup := 'tabMainStyle1';
l := TLayout.Create(t);
l.Parent := t;
l.Align := TAlignLayout.alClient;
l.Margins.Top := -5;
l.Margins.Left := 5;
l.Margins.Right := 5;
l.Margins.Bottom := 5;
l.Padding.Top := 0;
l.Padding.Left := 0;
l.Padding.Bottom := 0;
l.Padding.Right := 0;
pgeControl.ActiveTab := pgeControl.Tabs[pgeControl.TabCount - 1];
end;
You shoud call FindBinding after having applyed the custom style. Currently you call this before, so it can't find the object. Additionally there was a mistake when you was looking for the object.
so put this
o := t.FindBinding('imgCloseTabPage');
if o<>nil then
begin
if o is TCheckBox then
TCheckBox(o).OnClick := CheckBox1Click;
end;
after
t.StyleLookup := 'tabMainStyle1';
and the event should assigned.
Using Delphi 2010, UniDAC components, Firebird 2.5 SuperServer.
Database character set is ISO_8559_1 (my Windows default).
I am writing a data transfer application to transfer data from an Access database to a Firebird database that has identical table structure. I am using a ADOQuery component to select all rows from source table, and then looping through that recordset, and using UniSQL component with an INSERT statement with parameters, assigning parameter values from the corresponding source dataset field values.
When running the insert command, it throws a 'Malformed string' exception.
I am stuck and need help to resolve the issue.
Code follows:
function TDataTransfer.BeginTransfer(AProgressCallback: TProgressCallback): Boolean;
var
slSQLSelect, slSQLInsert: TStringList;
i, f, z: Integer;
cmdS, cmdI: String;
adods: TADODataSet;
fbcmd: TUniSQL;
fbscript: TUniscript;
q: String;
s : WideString;
begin
FProgressCallback := AProgressCallback;
fbscript := TUniscript.Create(nil);
try
fbscript.Connection := FirebirdConnection;
FirebirdConnection.StartTransaction;
try
fbscript.Delimiter := ';';
fbscript.ExecuteFile(ExtractFilePath(ParamStr(0)) + 'Firebird_Script_0.txt');
FirebirdConnection.CommitRetaining;
slSQLSelect := TStringList.Create;
slSQLInsert := TStringList.Create;
adods := TADODataSet.Create(nil);
fbcmd := TUniSQL.Create(nil);
try
adods.Connection := AccessConnection;
fbcmd.Connection := FirebirdConnection;
slSQLSelect.LoadFromFile(ExtractFilePath(ParamStr(0)) + 'Access_Select.txt');
slSQLInsert.LoadFromFile(ExtractFilePath(ParamStr(0)) + 'Firebird_Insert.txt');
z := slSQLSelect.Count - 1;
for i := 0 to z do begin
cmdS := slSQLSelect[i];
cmdI := slSQLInsert[i];
adods.CommandText := cmdS;
fbcmd.SQL.Text := cmdI;
adods.Open;
while not adods.Eof do begin
for f := 0 to adods.FieldCount - 1 do
try
if adods.FieldDefs[f].DataType = ftWideString then begin
s := adods.Fields[f].AsAnsiString ;
q := '"';
// if AnsiStrPos(PAnsiChar(#s), PAnsiChar(q)) <> nil then
// s := StringReplace(s, '"', '""', [rfReplaceAll]);
fbcmd.Params[f].Value := s;
end
else
if adods.FieldDefs[f].DataType = ftWideMemo then
fbcmd.Params[f].SetBlobData(adods.CreateBlobStream(adods.Fields[f], bmRead))
else
fbcmd.Params[f].Value := adods.Fields[f].Value;
except
raise;
end;
try
fbcmd.Execute;
// FirebirdConnection.CommitRetaining;
except
raise;
end;
adods.Next;
end;
adods.Close;
FProgressCallback((i + 1) * 100 div (z + 1), 10);
end;
finally
slSQLSelect.Free;
slSQLInsert.Free;
adods.Free;
fbcmd.Free;
end;
fbscript.ExecuteFile(ExtractFilePath(ParamStr(0)) + 'Firebird_Script_1.txt');
FirebirdConnection.Commit;
Result := True;
except
FirebirdConnection.Rollback;
Result := False;
end;
finally
fbscript.Free;
end;
end;
TIA,
SteveL
If you try to replace s := StringReplace(s, '"', '""', [rfReplaceAll]); with s := StringReplace(s, '''''', '''', [rfReplaceAll]); and uncomment the line;