My Delphi project has a TAdoQuery accesssing data on an MS Sql Server 2014 server, and TClientDataSet that receives the AdoQuery data via a TDataSetProvider. This is created from a Project Template I set up.
Normally, I've found this set-up to work faultlessly, but with this particular project I'm having a problem: ApplyUpdates() fails silently and the Sql Server data is not updated. In my stripped down debugging project, the only code I have, apart from a button-click handler which calls it, is:
procedure TForm1.ApplyUpdates;
var
Errors : Integer;
begin
Errors := ClientDataSet1.ApplyUpdates(0);
Caption := IntToStr(Errors) + '/' + IntToStr(ClientDataSet1.ChangeCount);
end;
After this executes, the form's caption should be 0/0 of course but what it actually says is 0/1. So on the face of it, no errors occurred but the CDSs ChangeCount hasn't been reset to zero as it should be. My q is, how can ApplyUpdates return no errors but the server dataset doesn't get updated.
Fwiw, I added the ChangeCount display as part of my effort to debug the problem. But I'm afraid I haven't been able to follow what's supposed to be going on in the details of the "conversation" between the DataSetProvider and its DataSet to apply the updates on the server.
I ran into this problem recently on a quick project I rustled up without the precaution of setting an OnReconcileError handler, as queried by #mjn.
Once I'd set up the OnReconcileError handler, it was obvious that the problem was that the provider's TSqlResolver wasn't able to identify the row to update. Iirc, the message on the ReconcileError pop-up form was words to the effect of "Unable to locate record. No key specified."
So, first thing I tried was to include this in my CDS's AfterOpen:
CDS1.Fields[0].ProviderFlags := [pfInKey];
(CDS1.Fields[0] is the PK field of the dataset)
Contrary to my expectations, that didn't fix it. After scratching my head for a while, I had a careful look on the server and discovered that the recently-recreated table I was using did not have a primary key index.
Once I'd created the primary key index on the server, the ApplyUpdates problem went away.
However, what puzzles me about this is that prompted by your q, I dropped the primary key index on my server table and the problem hasn't started occurring again (!). I'm guessing this is due to some kind of cacheing effect on my machine but I don't really want to reboot it right now to investigate.
I need an alternative way to prevent someone from accessing a particular piece of code.
I'll explain the scenario.
There are two programs.
In the first program an end-user creates a proforma invoice. When he/she then views the details on the invoice. The code displays the details with the main table's record in EXCLUSIVE-LOCK. This is to prevent other end-users from changing anything while the first user is busy viewing the details. So even when the proforma invoice is completed and can no longer be changed. The main table's record is still in EXCLUSIVE-LOCK. which is wrong, but it prevents other users from messing with it while the first user is still busy updating it. However, the people who work in this program leave the program in the detail view. They don't go out.
The problem is when the second program is used to dispatch the items on the proforma invoice. It uses the same main table's record. And therefore can't do anything because the first program still has it in EXCLUSIVE-LOCK.
My question is...
How can I prevent users changing data in the first program as if the main table's record was in EXCLUSIVE-LOCK, but without actually having it in exclusive-lock? Over multiple sessions...
This might be better a comment, but I don't have enough reputation points to make comments. Sorry.
Some notes:
Optimistic locking -- if it is viable for your situation -- is almost certainly the best solution.
If you are going to add an isLocked field to the table, you will probably want several other fields:
Date/Time the record was locked -- or else an expiration timestamp
LockHolder -- so you know whether or not you've got it.
The expiration can be automatic (as with a cron sweep), or can be ignored unless someone else wants the record. The program which sets the lock must also be smart enough to check to see if it still holds it. It gets complicated.
There are times when it is not convenient to make schema changes to a table, or there are too many tables that need changes. In those cases, you can add these fields to a separate LockIt table. One table can handle these locks for all your other tables.
Aside:
We also use our LockIt table for another purpose: to make sure that only one copy of a given program can run at a time. (Usually this is for cron jobs or a batch daemon.) The program exclusive-locks a particular record in the LockIt table (but DOES NOT start a transaction!), and it holds that lock as long as the program is running.
Most likely, your transaction scope is wrong. I'll go ahead and assume you are trying to dispatch with the invoicing program still open. Then you can't, because the record is still locked. Chances are your whole program is a transaction, and the record will keep locked for as long as the screen is running. Try and revisit your updates, enclose your real update operations in a DO TRANSACTION block, put some MESSAGE TRANSACTION statements around in different places and see what the results are. This will help you find the points in which Progress is being led to "believe" the record still needs to be locked.
I assume you're not using appserver since this behaviour most likely wouldn't be a problem in that case.
One solution could be to change into a "optimistic locking" approach. This means that you start out with a "NO-LOCK" and once you need to change the record you upgrade the lock to EXCLUSIVE-LOCK. This approach will work but you will need to make sure that the record still exists and isn't changed by some other user.
Depending on how often your invoices actually change this might (or might not) be a solution. If a mishap happens "once in a while" this might be a viable solution. If it happens often (every day or so) you need to do something else.
Basic pseudo code for an optimistic approach:
FIND FIRST record WHERE somethingsomething NO-LOCK.
/* Here goes code for displaying the record */
/* .... */
/* Here's the updates */
IF userWantsToSave THEN DO:
FIND CURRENT record EXCLUSIVE-LOCK NO-ERROR NO-WAIT.
IF AVAILABLE record THEN DO:
IF CURRENT-CHANGED record THEN DO:
MESSAGE "Changed!" VIEW-AS ALERT-BOX ERROR.
/* Your code goes here */
END.
ELSE DO:
/* Your code for updating goes here */
MESSAGE "Success!" VIEW-AS ALERT-BOX INFORMATION.
END.
END.
ELSE IF NOT AVAILABLE record THEN DO:
IF LOCKED record THEN DO:
MESSAGE "Locked!" VIEW-AS ALERT-BOX ERROR.
/* Your code goes here */
END.
ELSE DO:
MESSAGE "Deleted!" VIEW-AS ALERT-BOX ERROR.
/* Your code goes here */
END.
END.
END.
Here's an example from the knowledgebase that goes more into depth with this.
Is it possible to see the list of watches with the realtime value when I am not at a breakpoint?
I have defined an integer Mycounter in the public section of a TDatamodule.
I add it to the Watch list.
I can see its value being updated in the watch list only as I am debugging line by line or I am at a rbeakpoint.
Is there a way to keep seeing its value in the watch list even when the control goes back to the VCL thread (=when I press F9)?
As David said, a watch is not possible but you could get close by adding a data breakpoint to your variable and have it dump it's value to the Event Log each time it changes.
Steps
Get the address of the variable you like to track.
Add a databreakpoint
Open the Event Log debugging window
Get the address of a variable
I have used the variable I in my example. As per your example, this should be #Mycounter
Adding the Data Breakpoint
Use the address of the variable
Uncheck the Break checkbox
Evaluate the expression PInteger($45622C)^
Log the result
Viewing the results
There is no way to do this from the IDE. Watch evaluation requires all the threads in the process to be suspended so that the debugger can read the memory and perform the evaluation.
If you want to see values being refreshed without the debugger breaking, you would most likely have to add code to your application to instrument the values of interest. For example, add code to your application output debug messages (e.g. OutputDebugString(), CodeSite etc.) whenever the value changed. You would of course need to view the information in a separate viewer.
got an ADOQuery that has OnNewRecord event.
on the procedure i try to add data automaticaly to another table. the data is a few rows that are needed and handled in clientDataSet in case of cancellation.
at the loc
OtherAdoQuery.insert;
I get error that ADOQuery failed to insert null into a non null field. I am in insert mode, however I NEVER ASKED DELPHI TO POST! i dont find why it posts.
Edit: could you help me find a hint on this problem?
some more clarification:
at
ADOQuery.onNewRecord();
begin
CliendDataSet.insert; //here goes to post for ADOQueryPost. where ClientDataSet was in Browse State
end;
Edit:
this bug does not make sense! look at the stack trace:
beforePost
newRecord
myFunc
where myFunc does cause NewRecord with the Insert.
I'm not too familiar with TAdoQuery, but I know how to track down an error like this. First, if you don't already have it set, go into Project Options and turn on Use Debug DCUs under the Compile tab, then run a full build and run it. When you get that exception report in the debugger, hit Break and you should end up inside the code for the TAdoQuery or one of its sub-objects. Try examining the call stack. If you look up a few calls you'll probably find something that you did is calling something else that's calling Post. Follow the stack trace back until you reach your code and you'll get an idea of what's going on, and if you analyze it a little you should find some way to prevent the problem.
Having said that, let me make a quick guess as to the cause of your problem: When you call Insert on a dataset, if the dataset is already in appending mode because you previously called Insert or Append and didn't follow up with a Post, it will call Post itself before setting up a new row for you to work on. Maybe this is what's happening to you?
the answer was from a connection between the tables.
the ADOQuery.dataSource was set the DataSet of the ClientDataSet.
this mad so much damage, and no hint by the delphi.
I know how to create a .map file to track down access violation errors when the error message includes an actual address.
But what if the error message says
Access violation at address 00000000. Read of address 00000000.
Where do I start looking for the cause of this problem... ?
The accepted answer does not tell the entire story.
Yes, whenever you see zeros, a NULL pointer is involved. That is because NULL is by definition zero. So calling zero NULL may not be saying much.
What is interesting about the message you get is the fact that NULL is mentioned twice. In fact, the message you report looks a little bit like the messages Windows-brand operating systems show the user.
The message says the address NULL tried to read NULL. So what does that mean? Specifically, how does an address read itself?
We typically think of the instructions at an address reading and writing from memory at certain addresses. Knowing that allows us to parse the error message. The message is trying to articulate that the instruction at address NULL tried to read NULL.
Of course, there is no instruction at address NULL, that is why we think of NULL as special in our code. But every instruction can be thought of as commencing with the attempt to read itself. If the CPUs EIP register is at address NULL, then the CPU will attempt to read the opcode for an instruction from address 0x00000000 (NULL). This attempt to read NULL will fail, and generate the message you have received.
In the debugger, notice that EIP equals 0x00000000 when you receive this message. This confirms the description I have given you.
The question then becomes, "why does my program attempt to execute the NULL address." There are three possibilities which spring to mind:
You have attempt to make a function call via a function pointer which you have declared, assigned to NULL, never initialized otherwise, and are dereferencing.
Similarly, you may be calling an "abstract" C++ method which has a NULL entry in the object's vtable. These are created in your code with the syntax virtual function_name()=0.
In your code, a stack buffer has been overflowed while writing zeros. The zeros have been written beyond the end of the stack buffer, over the preserved return address. When the function later executes its ret instruction, the value 0x00000000 (NULL) is loaded from the overwritten memory spot. This type of error, stack overflow, is the eponym of our forum.
Since you mention that you are calling a third-party library, I will point out that it may be a situation of the library expecting you to provide a non-NULL function pointer as input to some API. These are sometimes known as "call back" functions.
You will have to use the debugger to narrow down the cause of your problem further, but the above possiblities should help you solve the riddle.
An access violation at anywhere near adress '00000000' indicates a null pointer access. You're using something before it's ever been created, most likely, or after it's been FreeAndNil()'d.
A lot of times this is caused by accessing a component in the wrong place during form creation, or by having your main form try and access something in a datamodule that hasn't been created yet.
MadExcept makes it pretty easy to track these things down, and is free for non-commercial use. (Actually, a commercial use license is pretty inexpensive as well, and well worth the money.)
You start looking near that code that you know ran, and you stop looking when you reach the code you know didn't run.
What you're looking for is probably some place where your program calls a function through a function pointer, but that pointer is null.
It's also possible you have stack corruption. You might have overwritten a function's return address with zero, and the exception occurs at the end of the function. Check for possible buffer overflows, and if you are calling any DLL functions, make sure you used the right calling convention and parameter count.
This isn't an ordinary case of using a null pointer, like an unassigned object reference or PChar. In those cases, you'll have a non-zero "at address x" value. Since the instruction occurred at address zero, you know the CPU's instruction pointer was not pointing at any valid instruction. That's why the debugger can't show you which line of code caused the problem — there is no line of code. You need to find it by finding the code that lead up to the place where the CPU jumped to the invalid address.
The call stack might still be intact, which should at least get you pretty close to your goal. If you have stack corruption, though, you might not be able to trust the call stack.
If you get 'Access violation at address 00000000.', you are calling a function pointer that hasn't been assigned - possibly an event handler or a callback function.
for example
type
TTest = class(TForm);
protected
procedure DoCustomEvent;
public
property OnCustomEvent : TNotifyEvent read FOnCustomEvent write FOnCustomEvent;
end;
procedure TTest.DoCustomEvent;
begin
FOnCustomEvent(Self);
end;
Instead of
procedure TTest.DoCustomEvent;
begin
if Assigned(FOnCustomEvent) then // need to check event handler is assigned!
FOnCustomEvent(Self);
end;
If the error is in a third party component, and you can track the offending code down, use an empty event handler to prevent the AV.
When I've stumbled upon this problem I usually start looking at the places where I FreeAndNil() or just xxx := NIL; variables and the code after that.
When nothing else has helped I've added a Log() function to output messages from various suspect places during execution, and then later looked at that log to trace where in the code the access violation comes.
There are ofcourse many more elegant solutions available for tracing these violations, but if you do not have them at your disposal the old-fashioned trial & error method works fine.
It's probably because you are directly or indirectly through a library call accessing a NULL pointer. In this particular case, it looks like you've jumped to a NULL address, which is a b bit hairier.
In my experience, the easiest way to track these down are to run it with a debugger, and dump a stack trace.
Alternatively, you can do it "by hand" and add lots of logging until you can track down exactly which function (and possibly LOC) this violation occurred in.
Take a look at Stack Tracer, which might help you improve your debugging.
Use MadExcept. Or JclDebug.
I will second madExcept and similar tools, like Eurekalog, but I think you can come a good way with FastMM also. With full debugmode enabled, it should give you some clues of whats wrong.
Anyway, even though Delphi uses FastMM as default, it's worth getting the full FastMM for it's additional control over logging.
Here is a real quick temporary fix, at least until you reboot again but it will get rid of a persistent access. I had installed a program that works fine but for some reason, there is a point that did not install correctly in the right file. So when it cannot access the file, it pops up the access denied but instead of just one, it keeps trying to start it up so even searching for the location to stop it permanently, it will continue to pop up more and more and more every 3 seconds. To stop that from happening at least temporarily, do the following...
Ctl+Alt+Del
Open your Task Manager
Note down the name of the program that's requesting access (you may see it in your application's tab)
Click on your Processes tab
Scroll through until you find the Process matching the program name and click on it
Click End Process
That will prevent the window from persistently popping up, at least until you reboot. I know that does not solve the problem but like anything, there is a process of elimination and this step here will at least make it a little less annoying.