In a stored procedure, I can have a continue handler that handles errors. Can I somehow access the details of the respective error inside the handler?
Small example:
DROP PROCEDURE IF EXISTS `DO_SOMETHING_WRONG`;
DELIMITER $$
CREATE PROCEDURE `DO_SOMETHING_WRONG`()
BEGIN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Hey, this is an error!';
END $$
DELIMITER ;
(That's obviously the failing part of the example)
DROP PROCEDURE IF EXISTS `CALL_SOMETHING_WRONG`;
DELIMITER $$
CREATE PROCEDURE `CALL_SOMETHING_WRONG`()
BEGIN
DECLARE ERROR_OCCURRED BOOLEAN DEFAULT FALSE;
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET ERROR_OCCURRED := TRUE;
CALL DO_SOMETHING_WRONG();
IF ERROR_OCCURRED IS TRUE THEN
SELECT 'An error occurred!' AS Output;
ELSE
SELECT 'No error occurred!' AS Output;
END IF;
END $$
DELIMITER ;
When I call CALL CALL_SOMETHING_WRONG(); it gives me An error occurred! as it should, but can I let it tell me, what error? So, get hold of the 45000 and the Hey, this is an error! in this example?
I tried to run a SELECT SQL to retrieve some data from MySql database. For this, I had created a TZQuery inside a TThreadn to avoid freezing the GUI of the application.
But, after the end of each consult (tested with 100,000+ rows, just to "force" a freeze) the memory was increasing instead of returning to the initial values (even after FreeAndNil(ZQuery) and FreeAndNil(TThread)) increasing ~2mb to each query executed (verified using Windows taskman.)
The test:
1 - Click button1 to start thread and query
2 - Wait callback
3 - Click button2 to finish and free query
{ TForm }
procedure TForm1.Button1Click(Sender: TObject);
begin
//DBQuery: TSubThreadTest;
DBQuery:= TSubThreadTest.Create(SQLStatement, true, ZCon, #onQueryCallBack);
DBQuery.Start;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
FreeAndNil(DBQuery);
end;
procedure TForm1.onQueryCallBack(fail: boolean; query: TZQuery);
begin
lLastExec.Caption:= FormatDateTime('hh:mm:ss', Now);
end;
{ TSubThreadTest }
constructor TSubThreadTest.Create(SQLStatement: string; toExec: boolean;
connector: TZConnection; EOnConclude: TSubThreadTestCallBack);
begin
inherited Create(true);
//main: TZQuery;
main:= TZQuery.Create(nil);
main.Connection:= connector;
main.SQL.Text:= SQLStatement;
isToExec:= toExec;
OnConclude:= EOnConclude;
FreeOnTerminate:= False;
end;
procedure TSubThreadTest.callback;
begin
if assigned(OnConclude) then
OnConclude(true, main);
end;
procedure TSubThreadTest.Execute;
begin
if isToExec then
main.ExecSQL
else
main.Open;
Synchronize(#callback);
FreeAndNil(main);
end;
First thread create and call: 24mb memory usage
Second thread create and call: 26,5mb memory usage
Etc...
Here's the case:
uses
System.JSON;
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
dd:Double;
aJsonObj:TJSONObject;
begin
dd := 100.0;
aJsonObj := TJSONObject.Create(TJSONPair.Create('DecimalValue',TJSONNumber.Create(dd)));
ShowMessage(aJsonObj.ToString);
end;
which shows
{"DecimalValue":100}
What I need is
{"DecimalValue":100.0}
I've tried to change JsonFormatSettings but I couldn't find a way to add the fractional part.
One of TJSONNumber constructors accepts a raw string value that will be used in resulting JSON string without further processing. It allows you to pre-format the value yourself:
procedure TForm1.Button1Click(Sender: TObject);
var
dd: Double;
aJsonObj: TJSONObject;
begin
dd := 100.0;
aJsonObj := TJSONObject.Create(
TJSONPair.Create('DecimalValue', TJSONNumber.Create(FormatFloat('0.0###', dd, GetJSONFormat))));
try
ShowMessage(aJsonObj.ToString);
finally
aJsonObj.Free;
end;
end;
Using the above snippet the value shows as:
{"DecimalValue":100.0}
You can even create 'whatever' as number:
aJsonObj := TJSONObject.Create(TJSONPair.Create('DecimalValue', TJSONNumber.Create('whatever')));
to get this (invalid) JSON:
{"DecimalValue":whatever}
Side note: you are responsible for releasing root JSON objects you create as they have no parent object to manage their lifetime. Use try..finally to Free the root object when you're done with it as you can see in the first code snippet. Too bad that the documentation itself tempts you not to cleanup.
Currently I have a form that captures login data, a form with a TIWDBGrid that is supposed to return any hosts that are associated with the user_id that is created when I login from my mysql database, and a shared data module.
Below is my code for my login page
unit login_unit;
interface
uses
Classes, SysUtils, IWAppForm, IWApplication, IWColor, IWTypes, IWCompButton,
IWCompLabel, Vcl.Controls, IWVCLBaseControl, IWBaseControl, IWBaseHTMLControl,
IWControl, IWCompEdit;
type
Tlogin_form = class(TIWAppForm)
enter_usermame_TIWEdit: TIWEdit;
enter_password_TIWEdit: TIWEdit;
Username: TIWLabel;
Password: TIWLabel;
login_TIWButton: TIWButton;
returned_user_id_TIWEdit: TIWEdit;
procedure login_TIWButtonClick(Sender: TObject);
public
end;
implementation
{$R *.dfm}
uses email_data, host_lookup_unit;
procedure Tlogin_form.login_TIWButtonClick(Sender: TObject);
var
host_lookup_form:Thost_lookup_Form;
begin
email_data_DataModule.Login_userProc.Prepare;
email_data_DataModule.Login_userProc.ParamByName('user_name_').Value := enter_usermame_TIWEdit.Text;
email_data_DataModule.Login_userProc.ParamByName('pass_word_').Value := enter_password_TIWEdit.Text;
email_data_DataModule.Login_userProc.Execute;
email_data_DataModule._user_id := email_data_DataModule.Login_userProc.ParamByName('user_id_').Value;
returned_user_id_TIWEdit.Text := email_data_DataModule.Login_userProc.ParamByName('user_id_').Value;
email_data_DataModule.Hosts_requested_frm_user_idProc.Prepare;
email_data_DataModule.Hosts_requested_frm_user_idProc.ParamByName('user_id_').Value := email_data_DataModule._user_id;
email_data_DataModule.Hosts_requested_frm_user_idProc.Execute;
thost_lookup_form.Create(Self).Show;
end;
initialization
Tlogin_form.SetAsMainForm;
end.
My host_look up page has a TIWDBGrid component and it looks like the following:
unit host_lookup_unit;
interface
uses
Classes, SysUtils, IWAppForm, IWApplication, IWColor, IWTypes, Vcl.Controls,
IWVCLBaseControl, IWBaseControl, IWBaseHTMLControl, IWControl, IWCompGrids,
IWDBGrids, IWCompButton;
type
Thost_lookup_Form = class(TIWAppForm)
IWDBGrid1: TIWDBGrid;
public
end;
implementation
{$R *.dfm}
uses email_data;
end.
I made sure that the DataSource option was set to email_data_DataModule.hosts_table_requested_TDataSource
And finally the code for my data module is as follows:
unit email_data;
interface
uses
Forms,
SysUtils, Classes, FireDAC.Stan.Intf, FireDAC.Stan.Option, FireDAC.Stan.Error,
FireDAC.UI.Intf, FireDAC.Phys.Intf, FireDAC.Stan.Def, FireDAC.Stan.Pool,
FireDAC.Stan.Async, FireDAC.Phys, FireDAC.Phys.MySQL, FireDAC.Phys.MySQLDef,
FireDAC.VCLUI.Wait, FireDAC.Stan.Param, FireDAC.DatS, FireDAC.DApt.Intf,
FireDAC.DApt, Data.DB, FireDAC.Comp.DataSet, FireDAC.Comp.Client;
type
Temail_data_DataModule = class(TDataModule)
EmaildbConnection: TFDConnection;
Login_userProc: TFDStoredProc;
hosts_table_requested_TDataSource: TDataSource;
Hosts_requested_frm_user_idProc: TFDStoredProc;
private
public
_user_id : integer;
end;
function email_data_DataModule:Temail_data_DataModule;
implementation
{$R *.dfm}
uses ServerController;
function email_data_DataModule:Temail_data_DataModule;
begin
result := UserSession.email_data_DataModule;
end;
end.
How can I make sure to populate the data from the Hosts_requested_frm_user_idProc to my IWDBGrid?
Thanks in advance and sorry for the long post
There are a few issues with your application:
First, all your forms need to have WebApplication object as the owner, so change your line where you create your IWForm to
procedure Tlogin_form.login_TIWButtonClick(Sender: TObject);
var
host_lookup_form:Thost_lookup_Form;
begin
...
thost_lookup_form.Create(WebApplication).Show;
end;
Second, I think you shouldn't be using a stored proc to retrieve data, but a query object. If you are using FireDAC consider changing it to a TFDQuery which will retrieve a result set.
Also, you must put your TDataSource in the same form where the IWDBGrid is, and connect them via IWDBGrid.DataSource property.
Add the unit where the DataModule is declared to the uses clause of the IWForm (in the interface section), and declare a field of the form. So you should have something like this:
unit host_lookup_unit;
interface
uses
Classes, SysUtils, IWAppForm, IWApplication, IWColor, IWTypes, Vcl.Controls,
IWVCLBaseControl, IWBaseControl, IWBaseHTMLControl, IWControl, IWCompGrids,
IWDBGrids, IWCompButton,
email_data; // <- Include your DM here
type
Thost_lookup_Form = class(TIWAppForm)
IWDBGrid1: TIWDBGrid;
hosts_table_requested_TDataSource: TDataSource;
private
FDataModule: Temail_data_DataModule;
public
end;
implementation
{$R *.dfm}
procedure Thost_lookup_Form .host_lookup_FormCreate(Sender: TObject);
begin
FDataModule := email_data_DataModule; // set your field referencing the DM here and use it within your form. Don't use the email_data_DataModule() function anymore!
hosts_table_requested_TDataSource.DataSet := FDataModule.Login_userQuery; // Connect your DataSource and your DataSet, via code. This is the best way to do it!
end;
I also strongly suggest that you remove the logic that deals with setting parameters and opening the query from your form. This code belongs to the DataModule!
You should have something like this:
unit email_data;
interface
uses
Forms,
SysUtils, Classes, FireDAC.Stan.Intf, FireDAC.Stan.Option, FireDAC.Stan.Error,
FireDAC.UI.Intf, FireDAC.Phys.Intf, FireDAC.Stan.Def, FireDAC.Stan.Pool,
FireDAC.Stan.Async, FireDAC.Phys, FireDAC.Phys.MySQL, FireDAC.Phys.MySQLDef,
FireDAC.VCLUI.Wait, FireDAC.Stan.Param, FireDAC.DatS, FireDAC.DApt.Intf,
FireDAC.DApt, Data.DB, FireDAC.Comp.DataSet, FireDAC.Comp.Client;
type
Temail_data_DataModule = class(TDataModule)
EmaildbConnection: TFDConnection;
Login_userQuery: TFDQuery;
Hosts_requested_frm_user_idQuery: TFDQuery;
private
F_user_id: Integer;
public
function SetUserNameAndPassword(const AUserName, AUserPassword: string): Integer;
end;
function email_data_DataModule:Temail_data_DataModule;
implementation
{$R *.dfm}
uses ServerController;
function email_data_DataModule: Temail_data_DataModule;
begin
result := UserSession.email_data_DataModule;
end;
function Temail_data_DataModule.SetUserNameAndPassword(const AUserName, AUserPassword: string): Integer;
begin
Login_userQuery.Prepare;
Login_userQuery.ParamByName('user_name_').Value := AUserName;
Login_userQuery.ParamByName('pass_word_').Value := AUserPassword;
Login_userQuery.Open;
F_user_id := Login_userQuery.ParamByName('user_id_').Value;
Result := Login_userQuery.ParamByName('user_id_').Value;
Hosts_requested_frm_user_idQuery.Prepare;
Hosts_requested_frm_user_idQuery.ParamByName('user_id_').Value := F_user_id;
Hosts_requested_frm_user_idQuery.Open;
end;
end.
all you need to do now is build and run the application. It should work.
Note: All code above is untested (I wrote it in Notepad, not in Delphi IDE) so it might have typos and some other errors.
I'm trying to update and delete my record.
I'm using dbgrid as to show the database and i use uniquery to do the query.
I managed to do the insert query but not with the update and delete.
Here is my code :
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Grids, Vcl.DBGrids, Data.DB,
DBAccess, Uni, UniProvider, MySQLUniProvider, MemDS, Vcl.StdCtrls, DAScript,
UniScript;
type
TForm1 = class(TForm)
UniConnection1: TUniConnection;
MySQLUniProvider1: TMySQLUniProvider;
UniDataSource1: TUniDataSource;
Button1: TButton;
Button2: TButton;
Button3: TButton;
Button4: TButton;
Button5: TButton;
Button6: TButton;
Label1: TLabel;
Edit1: TEdit;
Label2: TLabel;
Label3: TLabel;
Edit2: TEdit;
Edit3: TEdit;
Label4: TLabel;
DBGrid1: TDBGrid;
UniQuery1: TUniQuery;
UniScript1: TUniScript;
procedure Button1Click(Sender: TObject);
procedure DBGrid1CellClick(Column: TColumn);
procedure Button5Click(Sender: TObject);
procedure Button4Click(Sender: TObject);
procedure Button6Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
Application.Terminate();
end;
procedure TForm1.Button4Click(Sender: TObject);
begin
UniQuery1.Edit;
UniQuery1.SQL.Add('UPDATE barang SET id:=i, name:=nam, stock:=st where id=:i');
UniQuery1.ParamByName('i').AsString := Edit1.Text;
UniQuery1.ParamByName('nam').AsString := Edit2.Text;
UniQuery1.ParamByName('st').AsString := Edit3.Text;
UniQuery1.ExecSQL;
end;
procedure TForm1.Button5Click(Sender: TObject);
begin
UniQuery1.Insert;
UniQuery1.FieldByName('ID').AsString := Edit1.Text;
UniQuery1.FieldByName('Name').AsString := Edit2.Text;
UniQuery1.FieldByName('Stock').AsString := Edit3.Text;
UniQuery1.Post;
end;
procedure TForm1.Button6Click(Sender: TObject);
begin
UniQuery1.Edit;
UniQuery1.SQLdelete('DELETE FROM barang where id=:i');
UniQuery1.ParamByName('i').AsString:=edit1.Text;
UniQuery1.ExecSQL;
end;
procedure TForm1.DBGrid1CellClick(Column: TColumn);
begin
edit1.Text := DBGrid1.Fields[0].asstring;
edit2.text := DBGrid1.Fields[1].asstring;
edit3.Text := DBGrid1.Fields[2].asstring;
end;
end.
Thanks!
You're using the wrong syntax for your query.
The query does not use Delphi syntax and := does not make sense in that context.
Change the query to:
UniQuery1.SQL.Add('UPDATE barang SET id= :i, name= :nam, stock = :st where id= :i');
The : is a prefix that tells TQuery that these are named parameters.
Furthermore it makes little sense to set id = :i where id = :i that's a no-op.
So you can simplify the query to:
UniQuery1.SQL.Add('UPDATE barang SET name= :nam, stock = :st where id= :i');
In addition you don't have to insert/edit the queries.
These methods do not do what you think they do.
The insertion and editing is already being done by your SQL statements.
Don't use SQL.Add. It's slow and error prone, because if there is already text in your SQL the added text will clash with the SQL that's already there.
Never use SQL.Add ever again.
Change the first method like so:
procedure TForm1.Button4Click(Sender: TObject);
begin
UniQuery1.SQL.Text:= 'UPDATE barang SET name= :nam, stock = :st where id=:i';
UniQuery1.ParamByName('i').AsString := Edit1.Text;
UniQuery1.ParamByName('nam').AsString := Edit2.Text;
UniQuery1.ParamByName('st').AsString := Edit3.Text;
UniQuery1.ExecSQL;
end;
This method does not make any sense.
procedure TForm1.Button5Click(Sender: TObject);
begin
UniQuery1.Insert; //insert what? A query is not a table.
UniQuery1.FieldByName('ID').AsString := Edit1.Text;
UniQuery1.FieldByName('Name').AsString := Edit2.Text;
UniQuery1.FieldByName('Stock').AsString := Edit3.Text;
UniQuery1.Post; //makes no sense here.
end;
Just replace this with a INSERT INTO.... sql statement.
Finally the last method should look like:
procedure TForm1.Button6Click(Sender: TObject);
begin
UniQuery1.SQL.Text:= 'DELETE FROM barang where id=:i';
UniQuery1.ParamByName('i').AsString:=edit1.Text;
UniQuery1.ExecSQL;
end;
Surely you've figured out the there is no method called SQLdelete?
You need to rethink the concept.
It's the SQL statement that does the work.
The Query only cares if the statement is a select -> if so so Query.Open.
Or if it will change the data (delete/insert/update) -> so Query.ExecSQL.
All the rest is done in the SQL.Text.
Query.Edit etc
Yes you can do Query.Edit.
This puts the dataset in edit mode and allows the user to change fields in the query. The database layer will then transmit these changes to the underlying database tables.
However this only works if the query is simple. If not it will silently break and not update your tables.
Only use edit/insert/delete/post/cancel etc with Tables.