:FIND-FIRST method and the message(s) displayed

Posted by Admin on 17-Jan-2008 08:44

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

All Replies

Posted by ChUIMonster on 17-Jan-2008 10:11

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.)

Posted by Admin on 17-Jan-2008 10:16

Posted by ChUIMonster on 17-Jan-2008 10:20

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.

Posted by Tim Kuehn on 17-Jan-2008 11:10

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.

Posted by Admin on 17-Jan-2008 11:15

Posted by Tim Kuehn on 17-Jan-2008 11:21

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).

Posted by Admin on 17-Jan-2008 12:51

It's documented not to raise an error condition:

>> If FIND-FIRST fails, it does not raise an error but displays a message.

Posted by ChUIMonster on 18-Jan-2008 02:52

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

Posted by Tim Kuehn on 18-Jan-2008 08:43

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.

Posted by Admin on 18-Jan-2008 08:48

Tim, I'd like to see it like that.

Or hBuffer:FIND-FIRST(expression, NO-ERROR).

Mike

Posted by Admin on 18-Jan-2008 08:51

Anybody from PSC listening, who may explain why these messages have been implemented?

Posted by ChUIMonster on 18-Jan-2008 08:56

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.

Posted by Tim Kuehn on 18-Jan-2008 09:02

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.

Posted by Admin on 18-Jan-2008 09:12

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.

Posted by Tim Kuehn on 18-Jan-2008 10:23

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.

Posted by Admin on 18-Jan-2008 10:32

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.

Posted by Thomas Mercer-Hursh on 18-Jan-2008 11:15

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.

Posted by Admin on 18-Jan-2008 12:09

I might just be spoiled by C#, but it's used there all over.

Posted by Thomas Mercer-Hursh on 18-Jan-2008 12:21

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!

Posted by Admin on 18-Jan-2008 12:35

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.

Posted by Thomas Mercer-Hursh on 18-Jan-2008 12:57

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.

Posted by Evan Bleicher on 18-Jan-2008 13:35

Posted by Admin on 18-Jan-2008 13:55

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.

Posted by Thomas Mercer-Hursh on 18-Jan-2008 14:03

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?

Posted by Thomas Mercer-Hursh on 18-Jan-2008 14:05

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.

Posted by Tim Kuehn on 18-Jan-2008 14:07

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.

Posted by Thomas Mercer-Hursh on 18-Jan-2008 14:07

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?

Posted by Admin on 18-Jan-2008 14:09

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).

Posted by Thomas Mercer-Hursh on 18-Jan-2008 14:29

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.

Posted by Admin on 18-Jan-2008 14:34

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...

Posted by Evan Bleicher on 18-Jan-2008 14:38

Posted by Thomas Mercer-Hursh on 18-Jan-2008 14:47

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.

Posted by Admin on 18-Jan-2008 14:54

Posted by Tim Kuehn on 18-Jan-2008 15:18

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.

Posted by Thomas Mercer-Hursh on 18-Jan-2008 15:34

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

Posted by Thomas Mercer-Hursh on 18-Jan-2008 15:38

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.

Posted by Thomas Mercer-Hursh on 18-Jan-2008 15:44

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.

Posted by Admin on 18-Jan-2008 15:49

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.

Posted by Thomas Mercer-Hursh on 18-Jan-2008 16:22

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

 and 
 for citing code to make it easier to see the block structure.  Leave out the spaces, of course.

Posted by Admin on 18-Jan-2008 16:48

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

Posted by Evan Bleicher on 22-Jan-2008 10:49

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.

Posted by Thomas Mercer-Hursh on 22-Jan-2008 12:28

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.

Posted by Evan Bleicher on 22-Jan-2008 15:39

Your assertion is correct.

Posted by Thomas Mercer-Hursh on 22-Jan-2008 15:59

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.

Posted by Admin on 23-Jan-2008 08:16

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!

Posted by Admin on 26-Jan-2008 06:11

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...

Posted by Admin on 27-Jan-2008 01:30

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.

Posted by Admin on 27-Jan-2008 14:59

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.

Posted by Thomas Mercer-Hursh on 27-Jan-2008 15:22

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.

Posted by Admin on 27-Jan-2008 15:45

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.

Posted by Thomas Mercer-Hursh on 27-Jan-2008 15:55

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.

This thread is closed