Hi,
the last part of the documentation for the :FIND-FIRST() method of a buffer handle says:
If FIND-FIRST succeeds, it returns TRUE, otherwise it returns FALSE.
If FIND-FIRST fails, it does not raise an error but displays a message. You can suppress this message by using NO-ERROR on the statement containing the method.
Am I the only one who get totally mad about these messages? I want to be able to use this directly in an IF statement like:
IF hBuffer:FIND-FIRST() THEN DO:
ELSE DO:
END.
I can't use no error here - so I always need to create a logical variable and use that as that's the only chance to suppress the error message. As the result of the message (yes/no) already tell if I can get the record, why isn't it possible to use it directly in an IF statement?
Mike
Setting aside my feeling about FIND-FIRST() in particular...
No, you're not alone. I agree that it should not behave that way.
On a whim I tried session:suppress-warnings. No impact. (Not that I was surprised.)
Too bad the FIND-FAILED event can't be used in a normal trigger like so:
on "FIND-FAILED" of buffer customer do:
return no-apply.
end.
Am I the
only one who get totally mad about these messages? I
want to be able to use this directly in an IF
statement like: IF hBuffer:FIND-FIRST()
THEN DO: ELSE DO: END.
you mean you want hBuffer:CAN-FIND()...
I can't use
no error here - so I always need to create a
logical variable and use that as that's the only
chance to suppress the error message. As the result
of the message (yes/no) already tell if I can get the
record, why isn't it possible to use it directly in
an IF statement?
Mike
Then do it this way:
hBuffer:FIND-FIRST() NO-ERROR.
IF hbuffer:AVAILABLE THEN
The current structure of dynamic FF is exactly like how static FINDs work, so Progress got this right.
1. Static FINDs aren't coded as functions either.
2. If FF doesn't raise an error condition in the current block, then maybe that was an oversight on PSC's part. I wouldn't take it as an indicator they made a mistake by not providing a way to no set ERROR-STATUS on a failed FIND.
(I can't quote because of Jive's buggy behavior with FF mucks up the resulting post - sorry).
It's documented not to raise an error condition:
>> If FIND-FIRST fails, it does not raise an error but displays a message.
I'll grant that they didn't break anything that previously worked and that the FF() method is consistent with past programming practice.
But it's a new feature and thus needs to work in new ways. One obvious way to use it is as Mike is attempting to use it. Any method -- be it FF() or XYZZY() should work inside an expression. That's kind of the point of returning a value. And it does "work", it just has this unfortunate side-effect
Throwing an error message to the UI is, IMHO, antithetical to modern programming practice. It was a nice convenient thing to do in the 80s when we didn't know any better just as UPDATE EDITING was.
SESSION:SUPPRESS-NAGGING might be the answer
Any method -- be it FF() or XYZZY() should work inside an expression.
There's some of the OO stuff that violates that rule, as does UDF's inside of WHERE clauses.
It's too bad
handle:FF(expression NO-ERROR)
isn't supported, as that would be a viable workaround.
Tim, I'd like to see it like that.
Or hBuffer:FIND-FIRST(expression, NO-ERROR).
Mike
Anybody from PSC listening, who may explain why these messages have been implemented?
Any method -- be it FF() or XYZZY() should work
inside an expression.
There's some of the OO stuff that violates that rule,
I guess that I believe you but I'm at a loss to understand why that would be anything but a bug.
as do UDF's inside of WHERE clauses.
I used a weasel word -- "method". And I'm not sure that a WHERE clause ought to be thought of as an "expression" in this context.
I do like the thought of simply permitting "no-error" in the method call. It was the 2nd thing that I tried but, of course, it didn't actually work.
I guess that I believe you but I'm at a loss to understand why that would be anything but a bug.
Consider
class-variable = NEW class-name(signature).
While "NEW" is written similar to BUFFER table-name:HANDLE, it doesn't act like that, so that's why I suppose it's not supposed to be used where a value is required.
Well, this gets a bit off topic, but the NEW phrase of the assignment (=) statement is not just there to qualify the type of a static widget/object like the BUFFER or FRAME option.
For me that's a different language element than an expression.
Also if the NEW fails, it raises an error condition. It does not display silly messages without raising an error condition.
Also if the NEW fails, it raises an error condition
So do other function calls, like DATE() and STRING().
Frankly,
" x= NEW classname()"
should've been structured as
CREATE OBJECT class-variable-name
FOR class-name (constructor parameter sig).
and be consistent with the other dynamic object instantiation structures. However, since the OOABL architects decided to follow the C++ model / terminology (including adding "VOID" as a FUNCTION return type), we have what we have.
Well - the ABL is grown and with some experience it's not hard to guess at which area of the language some statements have been added to the language.
But the decision for x = NEW classname () or CREATE OBJECT class-variable-name FOR class-name does not affect the runtime behaviour of the application. Not being able to suppress informational (if ever) messages from a built in method call affects the runtime behaviour of the application. Also the overhead of needing to declare a result variable and using lRet = hBuffer:FIND-FIRST () NO-ERROR has slight effects on the runtime behaviour of the application.
The more I think about this I agree with Tom, that this has to be considered a bug.
Mike
By the way, I think it would be desirable to be able to pass a new object instance to a function/procedure/method call: xobjRef:SomeMethod(NEW someotherclass()). And yes, I know, that this makes it harder for the caller to control the lifetime of the new instance without having a handle.
What is the use case for wanting to instantiate a new object in the parameter? I don't see how one would ever clean it up sensibly, unless it was done in the called procedure and what does it accomplish that instantiation in the called procedure wouldn't handle.
It seems to me equivalent to xobjRef:SomeMethod(X as character), where X is being defined on the fly and thus has nothing but a default value assigned to it.
I might just be spoiled by C#, but it's used there all over.
OK, for what?
I can see it as an output parameter ... sort of an in-line define ... maybe, although I would typically prefer for it to be the value returned by the method, but what possible use is it as an input parameter, which it appears to be in your example.
"Spoiled by C#" ... could be taken at least two different ways!
In C# code things like (ds is of type DataSet):
ds.ReadXmlSchema(new System.Xml.XmlTextReader(new System.IO.StringReader(strSchema)));
strSchema is a string variable that contains an xml schema. The StringReader provides an input stream for that string, the XmlTextReader uses that stream and reads the xml document which then is used by the ReadXmlSchema method.
xmlTextReader adn StringReader are only required for this single call. I simple forget them and the runtime removes them.
If you don't like the above code, don't blame me. Blame Microsoft. This code has been generated by the xsd.exe in order to provide a static dataset class for an xml schema file.
Well, I won't blame you ... but I can't say that I see this accomplishes anything that isn't accomplished by passing in strSchema as a simple parameter.
Well, actually I like this method not raising the error. The negative return value should be enough indication that the row is not available.
All I want to get rid of, are the error messages in the AppServer logfile and still use :FIND-FIRST() as an expression.
Evan, for those of us denied access to the beta, could you satisfy our curiosity and clarify in your example how the association is made?
I.e, in your example code, what is it that alters the behavior of the built-in function from the presence of a catch block? Is it that they are in the same containing block and that all builtin functions in that containing block are affected? Or is it that the catch block follows the block with the builtin function and thus that function and any functions within that block are covered by the catch?
If I am reading Evan correctly, adding a catch block that does nothing should suppress the message in favor of putting the message in the error object and you can then decide to ignore that message.
Yes, not quite as simple as allowing no-error, but ultimately more flexible.
Evan - thanks for your response, but I don't think the CATCH example you've provided solves the problem at hand in this example, namely the ability to do IF handle:FF() THEN w/out an error condition being raised -or- an error message being displayed.
If using CATCH in this example results in the TX being undone, then it makes the
IF handle:FF() THEN
structure rather - well - useless.
Also, Evan, would you mind saying a few words about the lifecycle of the e object in your example? Does one then have to clean it up explicitly? Is it created whether or not there are any errors?
It might not actually do something or display a message, but I'm pretty sure, that a CATCH block that does nothing still has a much higher price and so impact to the runtime of the code than a simple negative return value (as documented for the :FIND-FIRST method).
My expectation would be that you can do whatever you want in the catch block, including ignore the fact that an error occurred at all, so nothing would need to be undone unless that was the appropriate thing to do.
Quote from Evan: "Please note, that becuase I have added a CATCH block, this built-in method will now raise error and therefore UNDO the block on a failure, prior to running the CATCH block."
So the CATCH block is not the place to simply ignore the error...
But, if your construct is as in the example, you don't want the block executed if the find first fails, so there actually shouldn't be anything to undo at that point.
Obviously, some clarification would be helpful, though.
My expectation would be that you can do whatever you
want in the catch block, including ignore the fact
that an error occurred at all, so nothing would need
to be undone unless that was the appropriate thing to
do.
not entirely, because as Evan's written, the TX has already been undone by the time the first line of code in the CATCH block is executed.
So,
a catch block would normally include a delete object e as a end
step of handling any error which it chose to handle locally?
"Rethrow" sounds like what I am asking above. Syntax? Is there any
kind of "resume", i.e., leaping back into the code following where
the error occurred? Message was edited by: Thomas Mercer-Hursh
So, if Evan verifies my DO...END scenario, one of the questions is what, if anything, is automatically undone and what is left to the CATCH to undo. I.e., in my pseudocode, I could decide that, if an error arose in the third statement, I might want to let the first two stand ... of course, I'm not sure that isn't just a question of being explicit about transaction scope.
My expectation is that nothing is undone unless and until it is explicitly undone or the error goes unhandled at the local level.
This would mean that something was not executed if the find first
errored. And, if there is something in front of the if find first,
then one can use usual transaction scoping to decide what does or
does not get undone.
But imaging if if have:
DO:
IF hContextBuffer:FIND-FIRST("WHERE ContextName = 'BatchSize'") THEN
hBuffer:BATCH-SIZE = INTEGER(hContextBuffer::ContextValue) .
IF hContextBuffer:FIND-FIRST("WHERE ContextName = 'SomeThingElse'") THEN
IF hContextBuffer:FIND-FIRST("WHERE ContextName = 'SomeThingMore'") THEN
CATCH e AS ...
DELETE OBJECT e.
END.
I'm pretty sure, that if the first FIND-FIRST fails the other two FIND-FIRSTs won't get executed. And I don't want to write a CATCH block for every statement.
Well, since you aren't going to get what you want, then you probably aren't going to get what you want!
But, I would think that you could probably handle this fairly well by doing something like creating a general purpose function or procedure and put the catch in that. Then you write it once for a whole category of similar needs.
p.s., consider
andfor citing code to make it easier to see the block structure. Leave out the spaces, of course.
I've met this same issue and was frustrated that I had to separate the method into two statements. If it really bugs you (pun intended) and you did a lot of find-first's then you could have your own function in a super e.g.
if can-find-first(hBuffer,'----') then
In a session super
FUNCTION can-find-first RETURNS LOG (hBuffer AS HANDLE, cWhere as CHAR):
DEF VAR lFound AS LOG NO-UNDO.
lFound = hBuffer:FIND-FIRST(cWhere) NO-ERROR.
RETURN lFound.
END FUNCTION.
Miles
Thomas stated: Well, we need clarification on what is undone when, but in Mike's sample code, I would guess that the answer is ...
For Structured Error Handling - I assume you have a CATCH - we used the existing infrastructure / rules for undoing a block of code. So, if a statement raises error (or if a built-in method raises error), the first thing OpenEdge will do, is undo the associated block. That is, all UNDO program variables are undone. Once this completes, we execute the CATCH block and then the FINALLY block.
This thread was started discussing how to suppress messages from a built-in method without raising ERROR. There is no mechanism for accomplishing that in the ABL other than to use the process identified at the start of this thread which is to use an intermediate logical variable to capture the result of the built-in method, using NO-ERROR. And then to test this logical variable in an IF statement.
So, in Mike's case, where he has a series of FIND-FIRST actions and he wants those that work to complete and those that don't to be silently ignored, he is going to have to put each FIND-FIRST as its own block with its own catch. Correct?
Very ugly without using a standard function to implement, but should be OK using such a function.
Your assertion is correct.
But, if one does have a set of actions scoped to a transaction (not the original request) then:
would be the appropriate structure, yes?
Just out of curiosity, what is the syntax for the throw to a higher level?
Is one of the implications here that one can (and should) create a top level block for the application and provide it with a catch block which does something graceful with any uncaught errors. Presumably, one would want to do that at the top level of any functional unit as well.
I'll indicate this question as answered - well I'm not happy with the answer so far, so I have just added enhancement request no. 0000003730 to add an NO-ERROR option to the FIND-FIRST and similar methods.
Please feel free to vote :-)
Thanks everybody for the discussion so far!
Why don't you create a FindFirst(...) user defined function that handles the no-error. Seems like a reasonable workaround, pass in a buffer handle and an expression string and do the assign-no-error in the function...
As you've said, that's just a workaround, causing an extra function call on the stack. In a AFTER-ROW-FILL event handler of a Prodataset Buffer that might be executed pretty often.
If you use OERA, AutoEdge or similar type service there are several layers with many procedure and function calls and in my case I don't see any performance problems. I also have a number of function calls in an afterowfill with no issues. Ensure the function is supered.
While I agree, Mike, that this is a reasonable enhancement to get on the list and one that doesn't seem hard, I think that you might consider being glad that there is a fairly reasonable workaround. If you create the function, the code should be fairly clean in line and probably not hard to refactor if you get your wish. I would start there and, if it turns out that you do have a performance issue in certain places, then do an in-line version in those cases. Yes, there will still be a nominal performance hit from the catch, but I would wait for it to bite you before I reached for the muzzle.
I've already written, that I accept the workaround and I've used it before - either inline or using a user defined function (in a super or elsewhere). And I'm also sure, that the runtime overhead is absolutely minimal. But in the end it's still just a workaround and requires extra coding.
But my initial question was, if others dislike the "functionality" of the messages that (at least in 10.1B) don't raise an error. I've only received a few answers, so I accept the fact, that this will not become an item very high on the priority list.
No, there I would agree with you. Untrappable messages is an ugly feature and the more we can do to trap them, handle them gracefully, and make it easy to do so, the better.