Many Clarion applications where developed decades ago, and have lots of code like this (comments show what can be improved):
CLEAR(ORD:Record) !1 OK
ORD:OrderDate = DATE(10,12,1996) !2 It should be together with line 7
SET(ORD:KeyOrderDate,ORD:KeyOrderDate) !3 No need to write the key name twice
LOOP !4 UNTIL NEXT would be better
NEXT(Orders) !5 NEXT could be smarter
IF ERRORCODE() THEN BREAK. !6 If it's always there, it shouldn't be there
IF ORD:OrderDate > DATE(10,28,1996) THEN BREAK. !7 See 2, it can't be optimized for a SQL backend
IF ORD:ShipState <> 'FL' THEN CYCLE. !8 It can't be optimized for a SQL backend
CLEAR(DTL:Record) !9 OK
DTL:CustOrderNumbers = ORD:CustOrderNumbers !10 OK
SET(DTL:KeyDetails,DTL:KeyDetails) !11 See 3
LOOP !12 See 4
NEXT(Detail) !13 See 5
IF ERRORCODE() THEN BREAK. !14 See 6
IF DTL:CustOrderNumbers <> ORD:CustOrderNumbers THEN BREAK. !15 See 8, it's just the opposite of 10
!Some code
.
.
Using ABC classes, the loop can be changed to:
LOOP UNTIL Access:Orders.Next()
...
But this wonāt handle ranges and filters.
A VIEW
can be used to handle filters and ranges for both TPS and SQL, but you must declare the view structure, and it needs a string with code that wonāt be verified by the compiler:
OrdersView{PROP:Filter} = 'ORD:Shipstate = ''FL'''
For SQL tables, PROP:Where
can be used before the first NEXT
to send ranges and filters to the backend, but it also needs a string with code, and you must use the fieldsā SQL names:
Orders{PROP:Where} = 'SHIPSTATE = ''FL'''
Putting all this together, I developed a class that can be used to write the code above like this:
tm TableManager
CODE
tm.Init(ORD:Record)
tm.AddRange(ORD:OrderDate,DATE(10,12,1996),DATE(10,28,1996))
tm.AddFilter(ORD:ShipState,'FL')
tm.SET(ORD:KeyOrderDate)
LOOP UNTIL tm.NEXT(Orders)
tm.Init(DTL:Record)
tm.AddRange(DTL:CustOrderNumbers,ORD:CustOrderNumbers)
tm.SET(DTL:KeyDetails)
LOOP UNTIL tm.NEXT(Detail)
!Some code
.
.
Now, all variable names used in ranges and filters are validated by the compiler, and are converted to a WHERE
clause when using SQL tables.
This is the SQL code generated by Clarion without the class:
WHERE (ORDERDATE >= ? AND (ORDERDATE > ? OR (CUSTNUMBER >= ? AND (CUSTNUMBER > ? OR (ORDERNUMBER >= ? )))))
And this is what the class generates:
WHERE (ORDERDATE BETWEEN '19961012' AND '19961028' AND SHIPSTATE = 'FL')
Method tm.NEXT
calls Clarionās NEXT
instruction and returns ERRORCODE()
if an error is posted. With TPS tables it also skips records not matching the filters, and returns 1 (tm:Record:OutOfRange
) at the end of the range. For SQL tables, all filters and ranges are handled by the back end.
Filters that need to evaluate more than a variable and a value, like this one:
IF ORD:ShipZip = '33012' OR ORD:ShipZip = '33015' THEN CYCLE.
Can be coded as a logical expression using the class:
tm.AddFilter('NOT ('&tm.V(ORD:ShipZip)&' = '&tm.S(33012)&' OR '&tm.V(ORD:ShipZip)&' = '&tm.S(33015)&' )')
Method tm.V()
returns an internal pointer to the variable; there is no need to BIND
it. Method tm.S()
formats the value for string comparison (adds quotes). For dates and times tm.D()
and tm.T()
can be used to format values.
For TPS tables the class will generate and EVALUATE
this string:
NOT ( '33064' = '33012' OR '33064' = '33015' )
Where 33064 is the value of field ORD:ShipZip
in the record currently being processed.
For SQL, the expression will be added to WHERE
using the fieldsā SQL names:
AND NOT ( SHIPZIP = '33012' OR SHIPZIP = '33015' )
The class can also be used with queues. This code:
LOOP IQTest = 1 TO RECORDS(QTest)
GET(QTest,IQTest)
IF NOT INRANGE(QTest.Number,2000,4000) THEN CYCLE.
!Some code
.
Can be changed to:
tm.AddFilter(QTest.Number,2000,4000)
tm.SET(QTest)
LOOP UNTIL tm.NEXT(QTest)
!Some code
.
In tests, the class has performed correctly. As expected, processing TPS tables and queues adds a few hundredths of second to the execution time compared to traditional Clarion code, but when using SQL tables, time was reduced by over 30% in a real-world data base.
Since the code modified to use the class is very similar to the original, an IDE AddIn or text editor macro can be developed to simplify migrating apps with lots of hand coded loops.
The class uses the TUFO interface, published by Oleg Rudenko and Mike Duglas.
Source code and a test application are available at GitHub.
Feedback is welcome.