Error Reporting in Transactions

By Jonathan and Bert.

Reporting on errors in a Transaction 

As well as introducing support for Transactional scripting, Claris has added two new functions to help report script errors: alongside the venerable get(LastError) we now have Get(LastErrorLocation) and Get(LastErrorDetail).  

As we discussed in our blog on practical Transactional scripting, a Transaction can be reverted by an explicit script step (Revert Transaction), or it can revert automatically if it encounters certain errors. When a Transaction reverts, any data modified within the Transaction returns to its original state, thereby obliterating any direct evidence of why it failed. You can use some tricks to track the progress of a Transaction, such as using sub-scripts to open a new window (outside of the Transaction) to record information. However, this can be cumbersome to code and maintain and, if the Transaction reverts automatically, may not be able to catch the error.  

Hence the new functions allow us to look back and see what went wrong. They can be really useful, but implementing them successfully can be a bit tricky—this blog looks at how to get the best out of them. 

Using the functions 

The first thing to note is that you can’t call the functions in sequential steps: 

Set Variable [ $error ; Value : Get(LastError) ] 
Set Variable [ $errorLocation ; Value : Get(LastErrorLocation) ]  
Set Variable [ $errorDetail; Value : Get(LastErrorDetail) ] 

In the first line, the Set Variable step captures the error code, but of course it runs successfully, itself generating an error code of 0, and thus the Get(LastErrorLocation) and Get(LastErrorDetail)  functions will return nothing, as there was no error. 

Rather, you should use a Let statement to capture them all at the same time: 

Set Variable [ $error ; Value :  
Let([ 
$errorLocation = Get(LastErrorLocation) ; 
$errorDetail = Get(LastErrorDetail)  
]; 
Get(LastError) 
) 

What do they return? 

Confusingly, the functions can return slightly different information depending on whether the error causes an immediate, automatic revert, or if the error causes an automatic reversion at the Commit Transaction step, or if you used an explicit Revert Transaction step. 

Get(LastErrorLocation) 

The Get(LastErrorLocation) function returns a list comprising the script name, the script step name (Set Field, Open Record, etc.), and the line in the script where the error occurred.  

If the error caused an immediate revert, your script will skip straight to the Commit Transaction step. Otherwise, you have to use an explicit Revert Transaction step to do the same thing.  

In both cases, after the Commit Transaction step, the Get(LastErrorLocation) function will return the step name and line number of the Commit Transaction step itself — not entirely useful! 

If the error caused an immediate revert, then the Get(LastError) function will return the expected error code.  

Unfortunately, if you used an explicit Revert Transaction step, the Get(LastError) function will return 0 (because the Revert Transaction step was successful). To overcome this, the Revert Transaction step allows you to specify a custom error code (any number between 5000 and 5499), which will be returned by the Get(LastError) function after the Commit Transaction step. 

Importantly, if you specify a custom error code you can also specify a custom error message—we’ll return to this later. 

It’s also worth noting that, if the error does not cause an immediate revert, you can call the Get(LastErrorLocation) function before your Revert Transaction step, and it will correctly return the step name and line number where the error actually occurred—definitely useful, and again, we’ll return to this later. 

Get(LastErrorDetail) 

The Get(LastErrorDetail) step will only return data when called directly after the Commit Transaction step. In this case, it returns a list comprising the script name, the step name and the line in the script that triggered the revert, and any custom message on the fourth line.  

If the error triggered an immediate revert, then great, the step name and script line will match where your error occurred. However, if you used the Revert Transaction step, then the step name and script line will simply tell you where your Revert Transaction step is, as that’s what triggered the revert. 

Integrated error reporting 

So, it looks like the Get(LastErrorDetail) function returns useful information after an immediate revert, but less useful information after an explicit Revert Transaction step.  

Not to worry—remember, if the error does not cause an immediate revert, we can use the Get(LastErrorLocation) to get a list including the actual step name and code line where the error occurred. We can then use this as the custom error message, as long as we specify a custom error code. 

For efficiency, we use a JSON array to store the location details and the error code, as it makes it easy to retrieve the information later. We then just specify the custom error code 5000 to allow us to use it as the custom error message in the Revert Transaction step. 

After the Commit Transaction step, we use the Get(LastError) and Get(LastErrorDetail) functions.  

If we get a regular error code, we know that it must have been an immediate revert, and we use the first three lines of the Get(LastErrorDetail) list to get the script name, step name, and code line.  

If we get the error code 5000, we know that it must have been an explicit revert, and we use the fourth line of the Get(LastErrorDetail) list to get the custom error message. This is the JSON that we set immediately after the error, and we use this to get the script name, step name, and code line where the error actually occurred, and most importantly the actual error code (not the custom code 5000 that Get(LastError) returned).  

In either case, we now have some useful information to present to the user (in a custom dialogue or as an error log) on why the Transaction failed. 

Error reporting in Transactions: Error 301 (record is in use by another user), picked up on line 57 (Open Record/request)

The attached file demonstrates the various ways that the functions operate, and our method of catching both immediate and explicit reversions. 

Caveat — validation errors. 

Alas, nothing is perfect, and there are two circumstances where our error reporting method won’t behave quite as intended.  

This first case is if your script calls one of the small number of steps that cause an immediate, but silent, Commit Transaction. The most common offender is the Re-login step. As we describe in our previous blog, if used within a Transaction block, the Transaction will immediately be committed, but the script will continue to execute the subsequent steps within the Transaction block. This does not generate an error, and usually, the only sign of trouble is when the Commit Transaction step is reached — this results in an error 3 (as you are trying to commit a Transaction that has already been committed). In this case, our method will still pick up on the error, but the Get(LastErrorDetail) function will not return any information, and so our error report will be limited. 

This second case involves validation rules. With the exception of strict data types, FileMaker does not check any validation rules during the execution of steps within the Transaction block—all the validation is done at the end when the Commit Transaction step is reached. Here a violation of a validation rule can cause the Transaction to revert with an error 509. This makes sense as some validation rules, such as unique value, can be hard to assess within the scope of a Transaction. Also, custom validation rules might require related values (which Transactions cannot assess). However, it does mean that no error is raised at the point of setting the data, even if followed by a Commit Records/Requests step. As with the first case, our method will pick up on the 509 error, but Get(LastErrorDetail) will not return any information, and so we cannot report where in the Transaction block the error occurred. 

Did you read our previous blog already? Practical guide to Transactions