Exception caused by TDataSetProvider poUseQuoteChar and lowercase table names does not surface - delphi

The default TDataSetProvider.Options.poUseQuoteChar is true.
I was (again) bitten by this when my SQL statement used a lower case table name and my TClientDataSet.ApplyUpdates(0) did not do any updates without raising an exception.
In DataSnap.Provider the code in function TCustomResolver.InternalUpdateRecord(Tree: TUpdateTree): Boolean; traps the exception:
except
if ExceptObject is Exception then
begin
E := Exception(AcquireExceptionObject);
PrevErr.Free;
PrevErr := Err;
Err := (Tree.Source as IProviderSupportNG).PSGetUpdateException(E, PrevErr);
if HandleUpdateError(Tree, Err, FMaxErrors, FErrorCount) then
begin
Tree.Delta.UseCurValues := True;
Continue;
end else
break;
end else
raise;
end;
and I see that E.Message is
[FireDAC][Phys][FB]Dynamic SQL Error'#$D#$A'SQL error code = -204'#$D#$A'Table unknown'#$D#$A'tt_calendar'#$D#$A'At line 1, column 8
I have no ReconcileErrorHandler, and in the above code HandleUpdateError returns false, but for some reason the exception does not surface.
My setup is:
New events created in DevExpress TcxSchedulerStorage, connected to TDataSource -> TClientDataSet -> TDataSetProvider -> TFDQuery -> TFDConnection, in this case to a Firebird database. Everything default settings, simple select * from tablename in TFDQuery.SQL.Text,
using Delphi Tokyo 10.2.3.
Is there a single setting that I can change that would force the exception visible and that solves this once and for all (for any database type)?
I'm even willing to patch a Delphi file.
I have now 'solved' this with runtime code:
procedure TDMTT.DataModuleCreate(Sender: TObject);
var i: integer;
begin
for i := 0 to ComponentCount-1 do
if Components[i] is TDataSetProvider then
(Components[i] as TDataSetProvider).Options := (Components[i] as TDataSetProvider).Options - [poUseQuoteChar];
end;
but would prefer not having to think of this every time.

As Sertac Akyuz suggested, modifying the TDataSetProvider constructor is one option.
The drawback is that you are patching Delphi code, but I'm fine with that.
In Datasnap.Provider, add the indicated line:
constructor TDataSetProvider.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FResolveToDataSet := False;
UpdateMode := upWhereAll;
FDSWriter := nil;
FConstraints := True;
FRecordsSent := 0;
Options := Options - [poUseQuoteChar]; // Line added
end;

Related

Left side cannot be assigned for a record type

I am trying to upgrade my application from Delphi 2007 to Delphi 10 Seattle. I understand that a record needs to be copied to a local variable before changing and then assigned back. I am trying the same but I still get the error that I cannot assign to a left side. Could someone please help.
procedure TMydlg.WMGetMinMaxInfo(var Msg:TMessage);
var
MinMaxInfo: TMinMaxInfo;
begin
inherited;
MinMaxInfo := (PMinMaxInfo(Msg.LParam)^);
with MinMaxInfo do
begin
ptMinTrackSize.X := MinWidth;
ptMinTrackSize.Y := MinHeight;
ptMaxTrackSize.X := MinWidth;
end;
// Error here. Left side cannot be assigned to
(PMinMaxInfo(Msg.LParam)^) := MinMaxInfo;
TMinMaxInfo is from Winapi.windows
The compiler error is emitted because the compiler rejects the outermost parens on the left hand side of the final assignment. In essence, your code is akin to the following:
type
TMyRecord = record
end;
procedure Foo;
var
rec1, rec2: TMyRecord;
begin
rec1 := rec2; // compiles
(rec1) := rec2; // E2064 Left side cannot be assigned to
end;
Writing it in this simplified manner brings the issue into very sharp relief.
I'm not sure why the compiler rejects these parens. I suspect that the formal grammar of the language renders your left hand side invalid. Serg provides a plausible explanation in the comments, that is that (...) is an expression, and an expression is not valid as the left hand side of an assignment. I'm inclined to believe that is accurate.
Anyway, it is simple to fix your code. Instead of
(PMinMaxInfo(Msg.LParam)^) := MinMaxInfo;
write
PMinMaxInfo(Msg.LParam)^ := MinMaxInfo;
Note that it is not necessary to make a copy of the record, modify it, and then copy it back. You can modify the record directly, once you have cast LParam to a pointer to the record.
I would do so like this:
procedure TMydlg.WMGetMinMaxInfo(var Msg:TMessage);
var
pmmi: PMinMaxInfo;
begin
inherited;
pmmi := PMinMaxInfo(Msg.LParam);
pmmi.ptMinTrackSize.X := MinWidth;
pmmi.ptMinTrackSize.Y := MinHeight;
pmmi.ptMaxTrackSize.X := MinWidth;
end;
I've omitted the ^ pointer dereference operator since it is optional in this scenario. If you prefer you might write the assignments like this:
pmmi^.ptMinTrackSize.X := MinWidth;
pmmi^.ptMinTrackSize.Y := MinHeight;
pmmi^.ptMaxTrackSize.X := MinWidth;
It is because you do not use a Record type and not a pointer type.
Change your code to this:
procedure TMydlg.WMGetMinMaxInfo(var Msg: TMessage);
begin
with pMinMaxInfo(Msg.LParam)^ do
begin
ptMinTrackSize.X := MinWidth;
ptMinTrackSize.Y := MinHeight;
ptMaxTrackSize.X := MinWidth;
end;
end;
I've created a dummy test program:
procedure TForm9.FormCreate(Sender: TObject);
var
MinMaxInfo: pMinMaxInfo;
Msg: TMessage;
begin
MinMaxInfo := new(pMinMaxInfo);
Msg.LParam := integer(MinMaxInfo);
WMGetMinMaxInfo(Msg);
Assert( pMinMaxInfo(Msg.LParam)^.ptMinTrackSize.X = 10);
end;
procedure TForm9.WMGetMinMaxInfo(var Msg: TMessage);
var
MinMaxInfo: pMinMaxInfo;
begin
MinMaxInfo := pMinMaxInfo(Msg.LParam);
with MinMaxInfo^ do
begin
ptMinTrackSize.X := 10;
ptMinTrackSize.Y := 10;
ptMaxTrackSize.X := 10;
end;
end;

Paradox error on CreateTable

I was creating table during execution, however at the creation of the table, (see my code)
procedure CreateTempTable(pDataBaseName,pSessionName:String);
begin
//-------create "TempTable"
TempTable:=TTable.Create(application);
With TempTable Do
begin
//-------set false in "Active"
Active := False;
//-------name of Session
SessionName:=pSessionName;
//-------name of DataBase.
DatabaseName :=pDataBaseName;
Filtered := True;
//-------name of table
TableName := 'TempTabl.DB';
//-------paradox type
TableType := ttParadox;
//-------if the table is already exists
if TempTable.Exists then
//-------delete the table
TempTable.DeleteTable;
//-------create 2 fields "Field1" & "Field2"
with FieldDefs do
begin
Clear;
with AddFieldDef do
begin
DataType := ftFloat;
Name := 'Field1';
end;
with AddFieldDef do
begin
DataType := ftFloat;
Name:='Field2';
end;
end;
//-------Create table
CreateTable; // Here where the exception bursts off
end;
end;
an exception is raised, which is: "Table is open, Table does not exist".
so what is exactly the problem, is it open or doesnot exist?
This is the exception:
(inside With TempTable Do) There is no need to delete the TempTabl.DB explicit. Is overwritten by CreateTable anyway.
To test if the table is not being used by other components in the IDE, you can try to delete the file TempTabl.DB.
CreateTempTable is now a function CreateTempTable(pDataBaseName,pSessionName:String):Boolean;
So you can better handle errors.
Tested with delphi 5 and RAD Studio 2007.
function CreateTempTable(pDataBaseName,pSessionName:String):Boolean;
begin
result:=false;
// assume pDataBaseName=directory, or change it
if FileExists(pDataBaseName+'TempTabl.DB') then begin
if NOT DeleteFile(pDataBaseName+'TempTabl.DB') then begin
showMessage('Table opened by another part of the IDE');
exit;
end;
end;
TempTable:=TTable.Create(application);
With TempTable Do
begin
Active := False;
SessionName:=pSessionName;
DatabaseName :=pDataBaseName;
//Filtered := True;
TableName := 'TempTabl.DB';
TableType := ttParadox;
with FieldDefs do
begin
Clear;
with AddFieldDef do
begin
DataType := ftFloat;
Name := 'Field1';
end;
with AddFieldDef do
begin
DataType := ftFloat;
Name:='Field2';
end;
end;
CreateTable;
result:=true;
end;
end;

Why do I get an access violation when I access a TValueListEditor found with FindControl?

I have dynamically created TValueListEditor VCL component on a TForm. The code is located in nested procedure of one of the main form's methods. I have set:
ValueListEditor.KeyOptions := [keyEdit, keyAdd, keyUnique];
It looks like this:
TMainForm.Method();
Method has a nested procedure that contains code that creates the components mentioned above.
Then, I have helper function:
function GetMenuListData(XMLNode: TXMLNode; const XNMLDoc: string = '') : string;
In this helper I use this code to load an XML file and then retrieve its nodes and insert them into ValueListEditor.
XMLDoc := TXMLDocument.Create(Self);
XMLDoc.ParseOptions := [poPreserveWhiteSpace];
try
XMLDoc.LoadFromFile(XNMLDoc);
try
Control := FindControl(FindWindow('TForm',PChar('(' + ExtractFileExt(Form1.Edit1.Text) + ')')));
if Control <> nil then
begin
TValuelistEditor(Control).Keys[TValuelistEditor(Control).RowCount-1] := XMLDoc.DocumentElement.NodeName;
if XMLDoc.DocumentElement.ChildNodes.First.AttributeNodes.Count > 0 then
TValuelistEditor(Control).Values[TValuelistEditor(Control).Keys[TValuelistEditor(Control).RowCount-1]] := String(XMLDoc.DocumentElement.Attributes['id'])
else
TValuelistEditor(Control).Values[TValuelistEditor(Control).Keys[TValuelistEditor(Control).RowCount-1]] := '<Empty>';
end else begin
MessageBeep(0);
FlashWindow(Application.Handle, True);
ShowMessagePos('...');
end;
finally
XMLDoc.Active := False; Result := 'Forced ' + Form1.RAWInputBtn.Caption + ' in ' + DateTimeToStr(Now);
end;
except
on E : EXMLDocError do
begin
Result := 'Forced ' + Form1.RAWInputBtn.Caption + ' in ' + DateTimeToStr(Now);
end;
end;
The problem is that I get access violations every time code goes into the line:
TValuelistEditor(Control).Keys[TValuelistEditor(Control).RowCount-1] := XMLDoc.DocumentElement.NodeName;
I have tried various typecasts, values, parameters .. nothing does the trick.
What is my mistake?
I'm using Delphi XE.
As Ken commented your problem is, instead of finding the value list editor, you are finding your form and then typecasting it to a value list editor, hence the AV.
First, you're passing 'TForm' as 'lpClassName' to FindWindow. Assuming 'TForm' is the class name of your form, it will of course find the form - not a child window on it. Second, you cannot use FindWindow to find a child window, see its documentation, it searches top-level windows.
If you had tested the return of FindControl, the code raising the AV would never run:
if (Control <> nil) and (Control is TValueListEditor) then
You can use FindWindowEx to search in child windows, if you don't know the handle of your form find it first as you've done already:
FormHandle := FindWindow('TForm',PChar('(' + ExtractFileExt(Form1.Edit1.Text) + ')'));
if FormHandle <> 0 then
begin
Control := FindControl(FindWindowEx(FormHandle, 0, 'TValueListEditor', nil));
or better yet, test the return of FindWindowEx first to avoid passing '0' to FindControl:
ValueListEditorHandle := FindWindowEx(FormHandle, 0, 'TValueListEditor', nil);
if Win32Check(ValueListEditorHandle <> 0) then
begin
Control := FindControl(ValueListEditorHandle);
if Assigned(Control) then
begin
...
If your dynamically created form is part of the same application, you don't need all the noise of the incorrect FindControl(FindWindow()). Just create your form, giving it a name, and making Application the owner:
MyForm := TMyForm.Create(Application);
MyForm.Name := 'MyDynamicForm';
When you want to get a new reference to it:
var
TheForm: TMyForm;
i: Integer;
begin
TheForm := nil;
for i := 0 to Screen.FormCount - 1 do
if Screen.Forms[i] is TMyForm then
// Could also use Screen.Forms[i].Caption
if Screen.Forms[i].Name = 'MyDynamicForm' then
TheForm := TMyForm(Screen.Forms[i]);
if Assigned(TheForm) then
TheForm.MethodThatLoadsXML(XMLFileName); // or whatever
end;
TheForm.MethodThatLoadsXML can now access the TValueListEditor directly:
procedure TMyForm.MethodThatLoadsXML(const XMLFileName: string);
begin
// Load xml as before, using XMLFileName
with TValueListEditor.Create(Self) do
begin
Options := [Whatever];
Parent := Self;
Left := SomeNumber;
Top := SomeNumber;
// Create items for value list from XML and other stuff
end;
end;

Delphi: Clientdataset: EDatabaseError on .Open; with ProviderName set

So I'm having this code that processes what the client sends on a pattern. If he sends 'getBENUds', the server sends the DataSet for this table back using the SaveToString method.
Then, this is sent to the client. (I'm using Synapse).
procedure TTCPSocketThrd.Execute;
var s: String;
strm: TMemoryStream;
ADO_CON: TADOConnection;
ADO_QUERY: TADOQuery;
DS_PROV: TDataSetProvider;
DS_CLIENT: TClientDataSet;
begin
CoInitialize(nil);
Sock := TTCPBlockSocket.Create;
try
Sock.Socket := CSock;
Sock.GetSins;
with Sock do
begin
repeat
if terminated then break;
s := RecvTerminated(60000,'|');
if s = 'getBENUds' then
begin
//ini ADO_CON
ADO_CON := TADOConnection.Create(Form1);
ADO_CON.ConnectionString := 'not for public';
ADO_CON.LoginPrompt := false;
ADO_CON.Provider := 'SQLOLEDB.1';
ADO_CON.Open;
//ini ADO_QUERY
ADO_QUERY := TADOQuery.Create(ADO_CON);
ADO_QUERY.Connection := ADO_CON;
//ini DS_PROV
DS_PROV := TDataSetProvider.Create(ADO_CON);
DS_PROV.DataSet := ADO_QUERY;
//ini DS_CLIENT
DS_CLIENT := TClientDataSet.Create(ADO_CON);
DS_CLIENT.ProviderName := 'DS_PROV';
//SQLQUERY Abfrage
ADO_QUERY.SQL.Clear;
ADO_QUERY.SQL.Add('SELECT * FROM BENU');
ADO_QUERY.Open;
//DSCLIENTDATASET bauen
strm := TMemoryStream.Create;
DS_CLIENT.Open;
DS_CLIENT.SaveToStream(strm);
end
else if s = 'getBESTEds' then
...
The line it says: DS_CLIENT.Open an exception is thrown:
An exception has been thrown: class EDatabaseError. Text: 'missing data-provider or data package'.
The data-provider has been set as can be seen above to 'DS_PROV', so it has to be the missing data package.
But shouldn't the ClientDataSet get its data from the DataSetProvider which in turn gets it from the ADOQuery that gets the data from the database?
This is as far as I get with my level of knowledge. I hope its not too difficult, because in my eyes, everything I did was correct.
Use
DS_CLIENT.SetProvider(DS_PROV);
or after DS_PROV creation: (at this time your component has really no name)
DS_PROV.name := 'DS_PROV';

Coverting a TMyQuery dataset to TClientDataSet in Delphi

I make use of the Mydac components by devart (corelab) to access MySql from Delphi (2006)
Very often I need to work with data in a TClientDataSet
What is the best way to convert the dataset of a TMyQuery to TClientDataSet
Currently I am using
var
MyQuery : TMyQuery;
Dsp : TDataSetProvider;
Cds : TClientDataSet;
begin
MyQuery := nil;
Dsp := nil;
Cds := nil;
try
MyQuery := TMyQuery.Create(nil);
Dsp := TDataSetProvider.Create(nil);
Cds := TClientDataSet.Create(nil);
MyQuery.Connection := TheConnection;
MyQuery.SQL.Text := CmdStr;
Dsp.DataSet := MyQuery;
Cds.SetProvider(Dsp);
Cds.Open;
////////////////////////////////////////////////////////////////////////
/// MAKE USES OF THE CDS //
////////////////////////////////////////////////////////////////////////
finally
FreeAndNil(Cds);
FreeAndNil(Dsp);
FreeAndNil(MyQuery);
end;
end;
Is there a better way of doing this ?
If you really do need this very often, then make it a function, like so:
function CreateAndOpenClientDataset(AOwner: TComponent;
AConnection: TConnection; ACommand: string): TClientDataSet;
var
MyQuery: TMyQuery;
Dsp: TDataSetProvider;
begin
Result := TClientDataSet.Create(AOwner);
try
MyQuery := TMyQuery.Create(Result);
MyQuery.Connection := AConnection;
MyQuery.SQL.Text := ACommand;
Dsp := TDataSetProvider.Create(Result);
Dsp.DataSet := MyQuery;
Result.SetProvider(Dsp);
Result.Open;
except
Result.Free;
raise;
end;
end;
This function you can use in all places instead of TClientDataSet.Create(), and unless an exception is raised you will be given an open TClientDataSet which owns and that way also frees the two helper objects.
(Note: I only use the DevArt components for MS Sql Server, so I can't test. The code may well contain errors, but the general idea works.)

Resources