I have a dataset, and I'm using this to catch the errors:
try
FDataSource.DataSet.Post;
ShowMessage('success message!');
except
on E : EDatabaseError do
begin
if (Pos('duplicate value', E.Message) > 0) or (Pos('duplicate key', E.Message) > 0) then
ShowMessage('my custom error message')
else
ShowMessage('generic db error message');
end;
end;
This is a horrible solution cause it's relying on finding the string 'duplicate value' or 'duplicate key' on the error message.
I want to be able to get some error code.
Is there any way to get it?
You may be able to catch the error via your AdoConnection object.
The TAdoConnection has an Errors object (see definition in AdoInt.Pas). To
investigate it, I used a stored proc on the server defined as
create PROCEDURE [dbo].[spRaiseError](#AnError int)
AS
BEGIN
declare #Msg Char(20)
if #AnError > 0
begin
Select #Msg = 'MyError ' + convert(Char(8), #AnError)
RaisError(#Msg, 16, -1)
end
else
select 1
END
Then, in my Delphi code I have something like this:
uses [...] AdoInt, AdoDB, [...]
procedure TForm1.Button1Click(Sender: TObject);
var
S : String;
IErrors : Errors;
IError : Error;
ErrorCount : Integer;
i : Integer;
begin
S := 'exec spRaiseError ' + Edit1.Text;
AdoQuery1.SQL.Text := S;
try
AdoQuery1.Open;
except
IErrors := AdoConnection1.Errors;
ErrorCount := IErrors.Count;
for i := 0 to ErrorCount - 1 do begin
IError := IErrors.Item[i];
S := Format('error: %d, source: %s description: %s', [i, IError.Source, IError.Description]);
Memo1.Lines.Add(S);
end;
Caption := IntToStr(ErrorCount);
end;
end;
If I set AdoQuery1's Sql.Text to 'select * from anything' I get
error: 0, source: Microsoft OLE DB Provider for SQL Server description: Invalid object name 'anything'.
If you try it out, you should find that the contents of the Errors collection
is cumulative, but Errors has a Clear method to clear it.
See https://learn.microsoft.com/en-us/sql/ado/reference/ado-api/error-object?view=sql-server-2017
https://learn.microsoft.com/en-us/sql/ado/reference/ado-api/errorvalueenum?view=sql-server-2017
for more info (links courtesy of Remy Lebeau)
Related
What is wrong in this code ? I don't understend, if I remove the "Try" my app dont open, and if don't remove always appear "need login" ...
procedure TF_login.FormActivate(Sender: TObject);
var
Result: Integer;
TextFile: TStringList;
VarArquivo: string;
text: string;
dataI, dataF : string;
begin
TextFile := TStringList.Create;
VarArquivo := System.IOUtils.TPath.GetDocumentsPath + PathDelim + 'Limit.txt';
try
TextFile.LoadFromFile(VarArquivo);
text := TextFile.Text;
// ShowMessage(TextFile.Text); // there is the text
// ShowMessage(text); // there is the text
dataI := FormatDateTime('dd/mm/yyyy', Now);
dataF := FormatDateTime('dd/mm/yyyy', StrToDate(text));
Result := CompareDate(StrToDate(dataI), StrToDate(dataF));
ShowMessage(dataF +' data f');
ShowMessage(dataI +' data I');
if ( Result = LessThanValue ) then
begin
ShowMessage('data F low');
end
else
begin
ShowMessage('data F high');
F_inicio.Show;
end;
FreeAndNil(TextFile);
except on E:
Exception do ShowMessage('An error happened!' + sLineBreak + '[' +
E.ClassName + '] ' + E.Message);
end;
end;
The error : [EConvertError] '09/11/2019' is not a valid date
to create the file, i do:
procedure TF_login.btn_entrarClick(Sender: TObject);
var
data : tdatetime;
Resposta, data_s: string;
begin
PathFile := System.IOUtils.TPath.GetDocumentsPath;
NameFile := 'Limit.txt';
data := Now; //data actual
data := IncMonth(data, 2);
data_s := FormatDateTime('dd/mm/yyyy', data);
TFile.WriteAllText(TPath.Combine(PathFile, NameFile), data_s );
F_inicio.Show;
end;
The file exists, because the first (and second) ShowMessage (what is commented) show me the "09/11/19" but the third and fourth not appear to me...
OBS: Delphi 10.3 (RIO), Plataform: Android
There are a couple of things that you should change in your code:
procedure TF_login.FormActivate(Sender: TObject);
var
TextFile: TStringList;
VarArquivo: string;
text: string;
dataI, dataF : string;
begin
// If an exception (unlikely, but on principle) happens in your VarArquivo
// assignment, then the original version will leak the allocated TStringList.
// Always place the TRY right after allocation of a memory block. That way
// you ensure that the FINALLY block will always release the allocated
// memory. Also, always include a FINALLY block to release the memory. Don't
// count on your code to reach the FreeAndNIL code (it doesn't in this
// instance, as you can see) to make sure that you actually release the
// memory.
VarArquivo := System.IOUtils.TPath.GetDocumentsPath + PathDelim + 'Limit.txt';
TextFile := TStringList.Create;
try // - except
try // - finally
TextFile.LoadFromFile(VarArquivo);
text := TextFile.Text;
// ShowMessage(TextFile.Text); // there is the text
// ShowMessage(text); // there is the text
dataI := FormatDateTime('yyyy/mm/dd', Date);
dataF := FormatDateTime('yyyy/mm/dd', StrToDate(text));
ShowMessage(dataF +' data f');
ShowMessage(dataI +' data I');
if ( dataF < dataI ) then
begin
ShowMessage('data F low');
end
else
begin
ShowMessage('data F high');
F_inicio.Show;
end;
finally
FreeAndNil(TextFile);
end
except
// NEVER just "eat" an exception. Especially not while developing the
// application.
// Always either log the exception or show it to the user.
on E:Exception do ShowMessage('Exception '+E.ClassName+': '+E.Message+#13#10+
'need login');
end;
end;
Now - if you do this, what exception and error message is shown. This is needed in order to properly diagnose the error. Perhaps you can even figure it out for yourself when you see what exactly goes wrong...
I have inherited an extensive (199 commands) Delphi 7 Indy 9 app that I am upgrading to Indy 10 (in D10.1). I have upgraded all the code, and it compiles and runs. The problem I have is that now in Indy 10 all the handlers also return a response code (and text) in addition to the coded response that they did under Indy 9.
For example:
// server
procedure TFormMain.IdCmdTCPServer1loginCommand(ASender: TIdCommand);
var
Rights: String;
begin
if BillingUserRegistered(ASender.Params[0], ASender.Params[1], Rights) then
begin
myClient := TClientData.Create;
myClient.ClientName := ASender.Params[0];
myClient.ClientHost := #32; // indy9 was .Thread.Connection.LocalName;
myClient.ID := Now;
ASender.Context.Data := myClient;
ListBox1.Items.AddObject(
PadR(myClient.ClientName,12,' ') + '=' +
FormatDateTime('yyyy-mm-dd hh:nn:ss', myClient.ID),
TDateTimeO.Create(myClient.ID));
ASender.Context.Connection.IOHandler.WriteLn('SUCCESS' + ' ' + Rights)
end
else
ASender.Context.Connection.IOHander.WriteLn('Login failed!');
end;
...
// client side
function TfrmLogin.VerifyUserNameAndPassword(username, password: String): Boolean;
var
response, response1: String;
begin
frmMain.IdTCPClient1.IOHandler.WriteLn('login' + ' ' +
username + ' ' + password)
response := frmMain.IdTCPClient1.IOHandler.ReadLn();
// I have to add this now to capture the response code too!
response1 := frmMain.IdTCPClient1.IOHandler.ReadLn(); // 200 OK
// ------------------------------------------------------
if Copy(response,1,7) = 'SUCCESS' then
begin
rights := Copy(response,9,4);
There are a lot of command handlers, and they all have their own custom responses. That's a lot of code to change at the client. Is there a way I can tell the IdCmdTCPServer to suppress the standard '200 Ok' response if the command handler already provides it's own? Or am I in for a long night?
Thanks
If you need to suppress the default command responses, you can either:
clear the TIdCommandHandler's ReplyNormal and ExceptionReply properties (this also works in Indy 9, except that ExceptionReply was ReplyExceptionCode in that version), and the server's CommandHandlers.ExceptionReply property (Indy 10 only).
set the TIdCommand.PerformReply property to false in your OnCommand handler (this also works in Indy 9):
procedure TFormMain.IdCmdTCPServer1loginCommand(ASender: TIdCommand);
var
...
begin
ASender.PerformReply := False;
...
end;
set the server's CommandHandlers.PerformReplies property to false (Indy 10 only - it will set TIdCommand.PerformReply to false by default):
IdCmdTCPServer1.CommandHandlers.PerformReplies := False;
On the other hand, you should consider using the command handler responses the way they are designed to be used, eg:
procedure TFormMain.IdCmdTCPServer1loginCommand(ASender: TIdCommand);
var
Rights: String;
begin
if ASender.Params.Count = 2 then
begin
if BillingUserRegistered(ASender.Params[0], ASender.Params[1], Rights) then
begin
...
ASender.Reply.SetReply('SUCCESS', Rights);
end
else
ASender.Reply.SetReply('ERROR', 'Login failed!');
end
else
ASender.Reply.SetReply('ERROR', 'Wrong number of parameters!');
end;
I would even go as far as saying that you should set the TIdCommandHandler.NormalReply.Code property to SUCCESS and the TIdCommandHandler.ExceptionReply.Code property to ERROR, and then you can do this inside your OnCommand handler:
procedure TFormMain.IdCmdTCPServer1loginCommand(ASender: TIdCommand);
var
Rights: String;
begin
if ASender.Params.Count <> 2 then
raise Exception.Create('Wrong number of parameters!');
if not BillingUserRegistered(ASender.Params[0], ASender.Params[1], Rights) then
raise Exception.Create('Login failed!');
...
ASender.Text.Text := Rights;
end;
With that said, any of these approaches should work fine without changing your existing client code. However, in Indy 10, I would suggest using SendCmd() instead of WriteLn()/ReadLn() directly:
function TfrmLogin.VerifyUserNameAndPassword(username, password: String): Boolean;
var
response: String;
begin
response := frmMain.IdTCPClient1.SendCmd('login ' + username + ' ' + password);
if response = 'SUCCESS' then
begin
rights := frmMain.IdTCPClient1.LastCmdResult.Text.Text;
...
end else begin
// error message in frmMain.IdTCPClient1.LastCmdResult.Text.Text ...
end;
end;
Alternatively, you can let SendCmd() raise an exception if it does not receive a SUCCESS reply:
function TfrmLogin.VerifyUserNameAndPassword(username, password: String): Boolean;
begin
try
frmMain.IdTCPClient1.SendCmd('login ' + username + ' ' + password, 'SUCCESS');
except
on E: EIdReplyRFCError do begin
// error message in E.Message ...
...
Exit;
end;
end;
rights := frmMain.IdTCPClient1.LastCmdResult.Text.Text;
...
end;
SendCmd() does exist in Indy 9, but it only supports numeric-based response codes, which you are not using. As you can see above, SendCmd() in Indy 10 supports string-based response codes as well as numeric ones.
On a side note: in your server code, the OnCommand handler runs in a worker thread, so your use of ListBox1.Items.AddObject() is not thread-safe. Any access to the UI must be synchronized with the main UI thread, using techniques like TThread.Synchronize(), TThread.Queue(), TIdSync, TIdNotify, etc, eg:
procedure TFormMain.IdCmdTCPServer1loginCommand(ASender: TIdCommand);
var
Rights: String;
myClient: TClientData;
begin
if ASender.Params.Count = 2 then
begin
if BillingUserRegistered(ASender.Params[0], ASender.Params[1], Rights) then
begin
myClient := TClientData(ASender.Context.Data);
if myClient = nil then
begin
myClient := TClientData.Create;
ASender.Context.Data := myClient;
end;
myClient.ID := Now;
myClient.ClientName := ASender.Params[0];
myClient.ClientHost := GStack.HostByAddress(ASender.Context.Binding.PeerIP, ASender.Context.Binding.IPVersion);
// In Indy 9, this would be:
// myClient.ClientHost := GStack.WSGetHostByAddr(ASender.Thread.Connection.Socket.PeerIP);
// NOT ASender.Thread.Connection.LocalName!
TThread.Queue(nil,
procedure
begin
ListBox1.Items.AddObject(
PadR(myClient.ClientName,12,' ') + '=' + FormatDateTime('yyyy-mm-dd hh:nn:ss', myClient.ID),
TDateTimeO.Create(myClient.ID));
end
);
ASender.Reply.SetReply('SUCCESS', Rights);
end
else
ASender.Reply.SetReply('ERROR', 'Login failed!');
end
else
ASender.Reply.SetReply('ERROR', 'Wrong number of parameters!');
end;
Make sure your BillingUserRegistered() function is similarly thread-safe, if it is not already.
My code raises an exception:
No such Table : Table_Name
I spent a whole day trying to resolve it but had no success so far.
I used SQLite3.exe to create the database, create the table person and insert the data. When I query the content of table person it shows me the positive result.
Unfortunately, when I use this code to show the table content the exception is raised.
This is the code:
procedure TForm1.connectButtonClick(Sender: TObject);
begin
// Set the path of your database file.
// Replace "full_path_to_your_database_file" with the absolute path
// to your SQLite database file.
SQLConnection1.Params.Add('Database=D:\testdb.db');
try
// Establish the connection.
SQLConnection1.Connected := true;
executeButton.Enabled := true;
outputMemo.Text := 'Connection established!';
except
on E: EDatabaseError do
ShowMessage('Exception raised with message' + E.Message);
end;
end;
procedure TForm1.executeButtonClick(Sender: TObject);
var
results: TDataSet;
query: String;
begin
outputMemo.Clear;
// A random query
query := 'SELECT * FROM person;';
try
// Execute the query on the database.
SQLConnection1.Execute(query, nil, results);
except
on E: Exception do
outputMemo.Text := 'Exception raised with message: ' + E.Message;
end;
// Show the results of the query in a TMemo control.
ShowSelectResults(results);
end;
procedure TForm3.ShowSelectResults(results: TDataSet);
var
names: TStringList;
i: Integer;
currentField: TField;
currentLine: string;
begin
if not results.IsEmpty then
begin
results.First;
names := TStringList.Create;
results.GetFieldNames(names);
while not results.Eof do
begin
currentLine := '';
for i := 0 to names.Count - 1 do
begin
currentField := results.FieldByName(names[i]);
currentLine := currentLine + ' ' + currentField.AsString;
end;
outputMemo.Lines.Add(currentLine);
results.Next;
end;
end;
end;
Either your database and/or table is corrupt or there is a mistake in some part of your code which is executing but you are not showing us.
The following code, which creates and populates a new table, runs perfectly on Delphi Seattle and produces the expected result, namely one data row in outputMemo.
I suggest you close & restart Delphi and close any other app which might be using the same Sqlite3.Dll as DB does, before trying it.
(I have moved outputMemo and ShowSelectResults to Form1)
procedure TForm1.executeButtonClick(Sender: TObject);
var
results: TDataSet;
query: String;
begin
outputMemo.Clear;
query := 'CREATE TABLE TESTTABLE (ID BIGINT, NAME NVARCHAR(80) )';
SQLConnection1.Execute(query, nil, Nil);
query := 'INSERT INTO TESTTABLE(ID, NAME) VALUES(1, ''One'')';
SQLConnection1.Execute(query, nil, Nil);
query := 'SELECT * FROM TESTTABLE';
try
// Execute the query on the database.
SQLConnection1.Execute(query, nil, results);
except
on E: Exception do
outputMemo.Text := 'Exception raised with message: ' + E.Message;
end;
// Show the results of the query in a TMemo control.
ShowSelectResults(results);
query := 'DROP TABLE TESTTABLE';
SQLConnection1.Execute(query, nil, Nil);
end;
procedure TForm1.ShowSelectResults(results: TDataSet);
var
names: TStringList;
i: Integer;
currentField: TField;
currentLine: string;
begin
if not results.IsEmpty then
begin
results.First;
names := TStringList.Create;
try
results.GetFieldNames(names);
while not results.Eof do
begin
currentLine := '';
for i := 0 to names.Count - 1 do
begin
currentField := results.FieldByName(names[i]);
currentLine := currentLine + ' ' + currentField.AsString;
end;
outputMemo.Lines.Add(currentLine);
results.Next;
end;
finally
names.Free;
end;
end;
end;
I am consuming a WSDL and when I try to execute one of the methods i am getting the error
.. raised exception class EPropertyConvertError with message 'Invalid
property element: System'
Any ideas what causes this?
Here is the code I am running (cEPS_* are constants defined earlier in the code):
procedure TForm1.Button1Click(Sender: TObject);
var
Headers : ISOAPHeaders;
aResult: c_ExpressPSAPI.Response;
begin
try
FEPS_SoapService := c_ExpressPSAPI.GetExpressSoap();
FEPS_Headers := (FEPS_SoapService as ISOAPHeaders);
FEPS_Application := c_ExpressPSAPI.Application.Create();
FEPS_Application.ApplicationID := cEPS_ApplicationID;
FEPS_Application.ApplicationName := cEPS_ApplicationName;
FEPS_Credentials := c_ExpressPSAPI.Credentials.Create();
FEPS_Credentials.AccountID := cEPS_AccountID;
FEPS_Credentials.AccountToken := cEPS_AccountToken;
FEPS_Credentials.AcceptorID := cEPS_AcceptorID;
FEPS_Credentials.NewAccountToken := '';
aResult := c_ExpressPSAPI.Response.Create;
aResult := FEPS_SoapService.HealthCheck(FEPS_Credentials, FEPS_Application);
except
on E : ERemotableException do
ShowMessage(E.ClassName + ' error raised, with message : ' + E.FaultDetail + ' :: '
+ E.Message);
end;
end;
And here is the WSDL code:
ExpressSoap = interface(IInvokable)
['{83D77575-DBDE-3A05-D048-60B2F6BCDFE6}']
function HealthCheck(const credentials: Credentials; const application: Application): Response; stdcall;
How do I get the message from any pacs server Delphi and display this message ASCII format in memo1
is it possible to use could this indy component.
This is an example code from http://sourceforge.net/projects/indy10clieservr/
Send C-ECHO Command from any Modality Emulator or Any PACS Server. Connected Ok but cant see incoming message in memo1. But Chamelon HL7 component display to message on Delphi
procedure TServerMainForm.IdTCPServerConnect(AContext: TIdContext);
begin
memo1.Lines.Add('Connection from ..PeerIP/IP' + AContext.Binding.PeerIP + ' // ' + AContext.Binding.IP + ' # ' + dateToStr(now) + '->' + TimeToStr(now) );
AContext.Connection.IOHandler.WriteLn('C-ECHO-RSP');
end;
procedure TServerMainForm.IdTCPServerExecute(AContext: TIdContext);
var CommBlock, NewCommBlock : TINDYCMD;
buf : TIdBytes;
line : String;
i : integer;
begin
memo1.Lines.Add('server execute start' );
with AContext.Connection do
begin
IOHandler.Readln(line);
end;
try
////////////// This line = 0 and cant see anything memo1. ////////////
if length(line) > 0 then
begin
memo1.Lines.Add(line );
i:= strToInt(Line);
end
else
i:=-1;
except
end;
case i of
0: begin
TCPServerExecuteExchangeStrings(AContext);
end;
1 : begin
TCPServerExecuteExchangeRecords(AContext);
end;
2: begin
end;
else
//
end;
LEDShape.brush.Color := clgreen;
memo1.Lines.Add('server execute done' );
end;
I don't quite understand the question... But I did quickly see a problem:
Any Internet Server needs to be validate input. Not doing so is a security risk.
In this case you are expecting to be sent a valid integer. If you don't get a valid integer you raise an exception. This may be desired behavior but I doubt it.
specifically this line: i:= strToInt(Line);
Instead you might try..
if TryStrToInt(line,i) then
// Handle valid integer sent
else
// Handle Invalid integer sent