Quick summary:
I want to evaluate dynamically constructed conditionals by inserting them into a query, and letting the WHERE clause figure out whether the result is true or false. It works pretty well; code is below. But
Here are the details:
We are using OE 11.7. (Some sites are on 11.3, but they can be upgraded if necessary.)
I want to allow user-defined conditions to determine default values in our user interface. For instance, in a simple case the result might depend upon the time of day, the day of the week, or the day of the month. But as I began making the list of all possibilities, the job of writing the parser began to get unpleasant, if not daunting.
These will be evaluated once at the start of a program, so performance is not a critical issue. Compiling on the fly is not an option. I came up with the idea of putting the condition into a dynamic query, and developed the following demo code:
/*
** dynamicif.p
**
** Facility for evaluating dynamic conditional statements
*/
DEFINE VARIABLE hQryDummyRec AS HANDLE NO-UNDO.
DEFINE TEMP-TABLE TT-DummyRec NO-UNDO
FIELD tt-dummyField AS CHARACTER.
CREATE TT-DummyRec.
DEFINE QUERY qryDummyRec FOR TT-DummyRec.
hQryDummyRec = QUERY qryDummyRec:HANDLE.
FUNCTION DynamicLogical RETURNS LOGICAL (
cLogicalExpr AS CHARACTER
):
DEFINE VARIABLE bResult AS LOGICAL.
DEFINE VARIABLE cQuery AS CHARACTER.
DEFINE VARIABLE i AS INTEGER.
cQuery = "FOR EACH TT-DummyRec WHERE " + cLogicalExpr.
hQryDummyRec:QUERY-PREPARE( cQuery) NO-ERROR.
/* This is not production-friendly error handling! */
/* Cannot use ERROR-STATUS:ERROR with QUERY-PREPARE */
IF ERROR-STATUS:NUM-MESSAGES > 0 THEN
DO:
BELL.
MESSAGE "** ERROR preparing query" cQuery.
DO i = 1 TO ERROR-STATUS:NUM-MESSAGES:
MESSAGE ERROR-STATUS:GET-NUMBER(i) ERROR-STATUS:GET-MESSAGE(i).
END.
PAUSE.
STOP.
END.
hQryDummyRec:QUERY-OPEN().
bResult = hQryDummyRec:GET-FIRST().
hQryDummyRec:QUERY-CLOSE().
return bResult.
END FUNCTION. /* DynamicLogical() */
/* Test Suite */
FORM WITH DOWN FRAME F-TestDynamicLogical.
PROCEDURE DynLogTest:
DEFINE INPUT PARAMETER cLogExpr AS CHARACTER NO-UNDO.
DISPLAY DynamicLogical( cLogExpr) cLogExpr FORMAT "X(60)"
WITH FRAME F-TestDynamicLogical.
DOWN WITH FRAME F-TestDynamicLogical.
END PROCEDURE. /* DynLogTest */
RUN DynLogTest( "2 > 1").
RUN DynLogTest( "1 > 2").
DEFINE VARIABLE i AS INTEGER NO-UNDO.
DO i = 0 TO 6:
RUN DynLogTest( "WEEKDAY(" + STRING( TODAY + i) + ") = 2" ).
END.
Does anyone have a better approach than this?
The code works pretty well. But I'd like to be able to include user-defined functions in the query, and QUERY-PREPARE() seems to balk at that. I didn't find any such restriction in the ABL Reference, but I might have missed it.
I know that UDFs behave oddly in a WHERE clause, since they are not evaluated each time through the loop. But it doesn't matter in this case, since we are only ever retrieving a single record.
Here is a simple example that doesn't work:
FUNCTION IsWeekend RETURNS LOGICAL (
dDate AS DATE
):
DEFINE VARIABLE iDayNum AS INTEGER NO-UNDO.
iDayNum = WEEKDAY( dDate).
RETURN iDayNum = 1 OR iDayNum = 7.
END.
RUN DynLogTest( "IsWeekend(" + STRING( TODAY, "99/99/9999") + ")" ).
Is this a real restriction? Any ideas on how I might work around it?
Thanks,
-- Phil
Put the function into a session:super-procedure and
RUN DynLogTest( "LOGICAL(DYNAMIC-FUNCTION (~"IsWeekend~", " + STRING( TODAY, "99/99/9999") + "))" ).
Put the function into a session:super-procedure and
RUN DynLogTest( "LOGICAL(DYNAMIC-FUNCTION (~"IsWeekend~", " + STRING( TODAY, "99/99/9999") + "))" ).
Clever! I never thought of DYNAMIC-FUNCTION().
Thanks, Mike.
Incidentally (though I'm sure you know this), there is no need to put this into a super-procedure -- though that idea has merit for other reasons. But my tentative plan is to create an object around it -- mostly so that I can overload the interface. I want to see, for instance, if I can pass a table buffer and use fields from it in the query.
Incidentally (though I'm sure you know this), there is no need to put this into a super-procedure -- though that idea has merit for other reasons. But my tentative plan is to create an object around it -- mostly so that I can overload the interface. I want to see, for instance, if I can pass a table buffer and use fields from it in the query.