I am using Delphi 2010 with FIB Components like TpFIBDataset, TpFIBTransaction and TpFIBDataset with Firebird database.
I have already set TpFIBDataset's 'AutoCommit' property to 'False', then also when I execute below statement in the try..finally block and rollback the transaction data still get posted.
Code:
FIBDataset.Post;
Below is the sample code.
Code:
try
FIBDatabase.StartTransaction;
....
Block of Code;
...
finally
if saveALL then
FIBDatabase.CommitRetaining
else
FIBDatabase.RollbackRetaining;
end;
The Transaction on the dataset must also be checked and changed
FIBDataset.AutoCommit := false;
You need to Close the query as well. In this case
FIBDataset.Close;
FIBDatabase.Rollback;
EDIT
I would also advise you to allocate the one transaction component to all the datasets (rather than the database). And use the start, commit, rollback methods of the transaction component. Further, you must assign the transaction component before you do any operations.
Related
I have been using ZeosLib Components with Delphi and Lazarus for the past 10 years or so. I'm mostly using it to access MySQL Databases.
Now I have run into a strange Problem with TZReadOnlyQuery.
In my MariaDB database I have a pretty complex stored procedure that takes some input parameters, one inout param and one out param. Because of the error I get, when I try to call it with TZReadOnlyQuery, I have made a very simple procedure to test it.
This is my test procedure:
CREATE PROCEDURE MyProc(INOUT a VARCHAR(255), OUT r VARCHAR(255))
BEGIN
set a = 'inparam changed';
set r = 'outparam set';
END
I tried to call this procedure from different MySQL query/managment tools with this statement:
set #x = 'inputparam';
call MyProc(#x, #y);
select #x, #y;
I get the expected answer: a dataset with one record and two fields like this:
But when I add the same query statement to a TZReadOnlyQuery and try to open it I get an error message:
Any ideas why this happens and how to work around it?
Thanks!
I write this code for button click event . the first time I click the button every thing work correctly but when click for the second time on button it raise an error. whats the problem?
procedure TfrmMain.Button1Click(Sender: TObject);
var
B : Boolean;
begin
DM.tblTemp.DisableControls;
B:= DM.tblTemp.Locate('FoodName', DM.tblAsli.FieldByName('FoodName').AsString,[]) ;
if B then
begin
DM.tblTemp.Edit;
DM.tblTemp.FieldByName('Number').AsInteger:= DM.tblTemp.FieldByName('Number').AsInteger + 1;
DM.tblTemp.Post;
end
else
begin
DM.tblTemp.insert;
DM.tblTemp.FieldByName('FoodName').AsString := DM.tblAsli.FieldByName('FoodName').AsString;
DM.tblTemp.FieldByName('UnitPrice').AsInteger := DM.tblAsli.FieldByName('FoodPrice').AsInteger;
DM.tblTemp.FieldByName('Number').AsInteger := 1;
DM.tblTemp.Post;
end;
TotalPrice:= TotalPrice + DM.tblTemp.FieldByName('TotalPrice').AsInteger;
DM.tblTemp.EnableControls;
end;
the Error is
Row cannot be located for updating. Some values may have been changed
since it was last read
DM is data madual
tmbTbl is ADOTable
It is a pity you haven't said which DBMS you are using (e.g. Sql Server or MS Access,
nor told us a full list of the table's column types and indexes, if any.
The most likely answer to your q is that the variable B is set to False the first time
because the call to tblTemp.Locate fails to locate the Food name in question, so the Insert branch executes and the food's data is
added to the table but the second time the Edit branch executes and the error occurs,
though you have not said where, exactly. My guess would be on the call to .Post, because
the error message is one the ADO layer, which sits between the DBMS provider and your app,
emits when it attempts to post a change to the table but cannot identify which table row to update.
As I mentioned in a comment, the fix to this is usually at add a primary key index to the table, and I gather from your latest comment that this has succeeded for you. For the record and benefit of future readers it would be helpful if you could confirm which DBMS you are using and which Ole Driver you are using in your connection string.
Fwiw, I've tested your code using a table on MS Sql Server and MS Access and do not get the
error with either database.
Btw there is an obvious q, "How is it that tblTemp.Locate succeeds, but ADO is unable to
identify the correct record to post the update?" The answer is that tblTemp.Locate works in
a different way than identifying the relevant row to post the update.
I have a bunch of triggers on tables in DB2 for the iseries. They are directly writing to other tables, but I've been asked to change them so that the triggers instead call something else to do the writing.
This something else part is a little vague I know. I would imagine I can't call a trigger from another trigger (correct me if I am wrong), so I imagine it has to be a stored procedure. Is there such a thing as something like a "program" in DB2 that I could call instead?
Assuming it is a stored procedure, how do I pass in the OLDROW and NEWROW to be handled in the SP?
For example, the trigger could look something like this (stripped down of course):
CREATE TRIGGER MYSCHEMA.WHATEVERTABLE_AFTER_INSERT_TRIGGER
AFTER INSERT ON MYSCHEMA.MYTABLE
REFERENCING NEW AS NEWROW
FOR EACH ROW
MODE DB2ROW
SET OPTION ALWBLK = *ALLREAD ,
ALWCPYDTA = *OPTIMIZE ,
COMMIT = *CS ,
DECRESULT = (31, 31, 00) ,
DFTRDBCOL = *NONE ,
DYNDFTCOL = *NO ,
DYNUSRPRF = *USER ,
SRTSEQ = *HEX
WHEN ( 1 = 1 )
BEGIN ATOMIC
INSERT INTO NEWSCHEMA.NEWTABLE
(
AAA,
BBB,
CCC
)
VALUES
(
NEWROW.AAA,
NEWROW.BBB,
NEWROW.CCC
);
END;
The trigger just need to replicate a table most of the time, so I need access to OLDROW sometimes, and NEWROW other times, and sometimes both. However, I believe these only make sense in context of the trigger, so how do I pass this info to the SP?
On DB2 for IBM i...
Every stored procedure IS a program and vice versa. One of the many benefits of having an OS that is a DB.
I'm pretty sure you can't call a stored procedure from an SQL trigger and pass the entire NEWROW implicitly. You could try
CALL MYSP (NEWROW.*);
You might be able to use dynamic SQL to query the meta data and build a dynamic call
But even if one of those works, you'd probably shouldn't do it. Because now you've got a dependency between the called SP and the structure of the table (like you'd have with old fashioned RPG). One of the reasons to use SQL is to avoid that dependency.
Now, if you where using RPG or COBOL to define your trigger, then it's pretty easy to pass around the
pointer to the trigger buffer the DB provides to the program. But RPG & COBOL are lower level languages compared to SQL.
What's the reason for asking for the "something else" to do the writing?
I had a multi tier project in which i would collect data from a microsoft sql 2005 through a FDStoredProc with a function and the function would return a dataset to the client. When the server assigns the dataset to the result of the function and the function tries to send it to the client i get this error. Project etctec.exe raised exception class TDBXError with message 'TDBXTypes.BLOB value type cannot be accessed as TDBXTypes.Bytes value type'.
In another project i used the StoredProc of a different database with TFDStoredProc in exactly the same way and it works fine. Any ideas what would raise this error?
This is what i do in the server.
function TServerMethods1.getCategories(): TDataSet;
begin
FDStoredProc1.ParamByName('#val1').AsInteger:= 1;
FDStoredProc1.ParamByName('#val2').AsInteger:= 0;
FDStoredProc1.ParamByName('#val3').AsInteger:= 1;
FDStoredProc1.ParamByName('#val4').AsInteger:= 1;
FDStoredProc1.Open();
result:= FDStoredProc1;
end;
and the client calls it like this...
dataset:=ClientModule1.ServerMethods1Client.getCategories();
Problem comes from some fields that are of type NVARCHAR(max), anyone knows a workaround to this error without changing the field type?
I tried changing the dataset's field type to a string or something with no success. The only thing i can temporarily do is get these fields separately, put them in a stringlist or something like that and pass it to the client.
I think you should use some of similar methods below:
http://docwiki.embarcadero.com/Libraries/XE4/en/Data.DB.TDataSet.CreateBlobStream
http://docwiki.embarcadero.com/Libraries/XE4/en/Data.DB.TDataSet.GetBlobFieldData
You should define you field as a blob field and then put data in/out with the functions described in the links.
Here is also example how to copy data:
http://docwiki.embarcadero.com/Libraries/XE4/en/Data.DB.TDataSet.CreateBlobStream
I am trying to create a BEFORE INSERT trigger that will check the incoming value of a field, and replace it with the same field in another row if that the field is null. However, when I add the CALL statement to my trigger, an error is returned "The trigger "ORGSTRUCT.CSTCNTR_IN" is defined with an unsupported triggered SQL statement". I checked the documentation and saw that cursors weren't supported in the BEFORE (part of the reason for making the stored procedure in the first place), but even when I remove the cursor declaration from the stored procedure the call still generates the same error.
Trigger:
CREATE TRIGGER orgstruct.cstcntr_IN
NO CASCADE
BEFORE INSERT ON orgstruct.tOrgs
REFERENCING NEW AS r
FOR EACH ROW MODE DB2SQL
BEGIN ATOMIC
DECLARE prnt_temp BIGINT;
DECLARE cstcntr_temp CHAR(11);
SET prnt_temp = r.prnt;
SET cstcntr_temp = r.cstcntr;
CALL orgstruct.trspGetPrntCstCntr(prnt_temp,cstcntr_temp);
SET r.cstcntr = cstcntr_temp;
END
Stored procedure:
CREATE PROCEDURE orgstruct.trspGetPrntCstCntr (
IN p_prnt BIGINT,
OUT p_cstcntr CHAR(11)
)
SPECIFIC trGetPrntCstCntr
BEGIN
IF p_prnt IS NULL THEN
RETURN;
END IF;
BEGIN
DECLARE c1 CURSOR
FOR
SELECT cstcntr
FROM orgstruct.tOrgs
WHERE id = p_prnt
FOR READ ONLY;
OPEN c1;
FETCH FROM c1 INTO p_cstcntr;
CLOSE c1;
END;
END
According to the documentation, CALL is allowed in a BEFORE trigger, so I don't understand what the problem is.
A before trigger can call a stored procedure, but the stored proc can't do anything not allowed in the trigger.
In your case, the default level of data access for a SQL stored proc is MODIFIES SQL DATA, which is not allowed in the trigger. You could recreate your stored procedure, changing the data access level to READS SQL DATA; this will allow you to create the trigger.
However: There is no reason to call a stored procedure for something this simple; You can do it using a simple inline trigger:
create trigger orgstruct.cstcntr_IN
no cascade
before insert on orgstruct.tOrgs
referencing new as r
for each row
mode db2sql
set r.cstcntr = case
when r.p_prnt is not null
then (select cstcntr from tOrgs where id = r.p_prnt fetch first 1 row only)
else r.cstcntr
end;
This will be a LOT more efficient because it eliminates both the stored procedure call and the cursor processing inside the stored proc. Even if you wanted to use the stored proc, you could eliminate the cursor inside the stored proc and improve performance.
FYI: the logic that you posted contains an error, and will always set CSTCNTR to NULL. The trigger posted in this answer not do this. :-)