Evaluating dynamically constructed conditional statements in

Posted by PhilF on 11-Apr-2019 12:25

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

  • Does anybody have a better approach to this?
  • Is there anyway to incorporate user-defined functions in this?  QUERY-PREPARE doesn't seem to like them.

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

Posted by Mike Fechner on 11-Apr-2019 15:21

Put the function into a session:super-procedure and

RUN DynLogTest( "LOGICAL(DYNAMIC-FUNCTION (~"IsWeekend~", " + STRING( TODAY, "99/99/9999") + "))" ).

All Replies

Posted by Mike Fechner on 11-Apr-2019 15:21

Put the function into a session:super-procedure and

RUN DynLogTest( "LOGICAL(DYNAMIC-FUNCTION (~"IsWeekend~", " + STRING( TODAY, "99/99/9999") + "))" ).

Posted by PhilF on 11-Apr-2019 17:22

Clever!   I never thought of DYNAMIC-FUNCTION().

Thanks, Mike.  

Posted by PhilF on 11-Apr-2019 17:38

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.

Posted by PhilF on 11-Apr-2019 17:38

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.

This thread is closed