14.8 Callback Functions
Aubit4GL has three extensions to the classic 4GL grammar which allow the programmer to write callback functions:
-
CONSTRUCT ... VIA viafunc
-
SORT arrayvar USING sortfunc [LIMIT n]
-
LOAD FROM file USING FILTER filterfunc
INSERT INTO tabname [(col, ...)]
In the above statements viafunc, sortfunc, and filterfunc are callback functions - i.e programmer supplied functions which the Aubit4GL program will call to perform lower level operations.
The callback functions allow you to modify at low level the behaviour of the CONSTRUCT, SORT, and LOAD statements.
14.8.1 CONSTRUCT VIA
Aubit4GL now allows you to write code like this:
CONSTRUCT lv_str ON a,b
FROM a,b
VIA viafunc
The VIA clause is an Aubit4GL extension.
The viafunc is a user supplied function which takes 5 parameters and returns the required SQL boolean expression.
The parameters which Aubit4GL’s CONSTRUCT statement will pass to the callback function are:
-
Table Name: char(18)
-
Column Name: char(18)
-
String: char(300)
-
Type: integer
-
Length: integer
You use these however you wish to generate an appropriate SQL boolean expression which will be PREPAREd and EXECUTED (usually in a FOREACH loop).
In standard CONSTRUCT statements, Aubit4GL builds the boolean for each field using its builtin function:
aclfgl_get_construct_element()
which takes the same 5 parameters as above.
MAIN
DEFINE a RECORD
a CHAR(10),
b CHAR(20)
END RECORD
DEFINE lv_str CHAR(200)
OPEN WINDOW w1 AT 1,1 WITH FORM "f1"
# Here - we want to use a callback function in the
# construct to allow us to amend the CONSTRUCT string
# before the construct returns it.
#
# This can be used (for example) to automatically add
# ’*’ around the string, or to search an address
# (by doing something like
# ’( addr1 MATCHES .. OR addr2 MATCHES ...)’ etc etc
#
CONSTRUCT lv_str ON a,b from a,b VIA viafunc
DISPLAY lv_str
CLOSE WINDOW w1
END MAIN
FUNCTION viafunc(lv_tabname, lv_colname,
lv_string,lv_dtype, lv_dtypelength)
DEFINE lv_tabname CHAR(18)
DEFINE lv_colname CHAR(18)
DEFINE lv_string CHAR(300)
DEFINE lv_dtype,lv_dtypelength INTEGER
# Normally - we’d want to generate the construct portion
# - but with maybe different column names
# In this callback we want to map ’a’ to be ’blah’...
#
IF lv_colname="a" THEN
# aclfgl_get_construct_element takes 5 parameters
# tablename, column name, search string,
# datatype and datatype length
# (eg for decimal or character length)
LET lv_string=aclfgl_get_construct_element(
lv_tabname , "blah", lv_string ,
lv_dtype, lv_dtypelength)
ELSE
LET lv_string=aclfgl_get_construct_element(
lv_tabname , lv_colname , lv_string ,
lv_dtype, lv_dtypelength)
END IF
RETURN lv_string
END FUNCTION
This gives total flexibility, because we need to return the string for each field. Normally we can just call the default aubit4gl function to generate that string (aclfgl_get_construct_element()), but we dont need to use that, or we can call it with different parameters from what the CONSTRUCT would use.
In the following example, the programmer wants to select data from a different column depending on whether the length of the vin value is more than 8 chars or not (assuming the column is called vin on the form) :
FUNCTION viafunc(lv_tabname, lv_colname,
lv_string,lv_dtype, lv_dtypelength)
DEFINE lv_tabname CHAR(18)
DEFINE lv_colname CHAR(18)
DEFINE lv_string CHAR(300)
DEFINE lv_dtype,lv_dtypelength INTEGER
IF lv_colname="vin" THEN
IF length(lv_string)=8 THEN
LET lv_colname="vin8"
END IF
IF length(lv_length)>8 THEN
LET lv_colname="vinunq"
END IF
END IF
RETURN aclfgl_get_construct_element(
lv_tabname , lv_colname, lv_string ,
lv_dtype, lv_dtypelength)
END FUNCTION
14.8.2 SORT ... USING sortfunc
In classical 4GL, sorting arrays is clumsy, partly because you cannot pass arrays to and from functions, and partly because there is no builtin sort function. Aubit4GL improves on this with 3 extensions:
-
SORT arrayvar USING sortfunc [LIMIT n] statement which allows the programmer to sort an array relying on the callback function to apply the appropriate ordering of successive rows in the array. If you suppy the LIMIT n clause, the sort will apply only to the first n rows.
-
COPYOF operator which can bulk-copy (using the C library function memcpy()) an array or record to a function argument
-
COPYBACK operator which reverses the action of COPYOF by bulk-copying back to its source variable
sortfunc() is a user supplied function which takes 2 identical COPYOF arguments:
sortfunc(COPYOF arg1, COPYOF arg2)
with arg1 and arg2 DEFINEd as a record to match each row of the array to be sorted and returning -1 or 0 or +1 meaning less than, equal to, or greater than respectively. Aubit4GL will pass rows, 2 at a time, to the function and rely on the return value to rank them.
COPYOF is an operator for function arguments which tells the Aubit4GL compiler to use the C-library function memcpy() to bulk-copy a large mass of data in one operation to the 4GL function stack. In standard 4GL all the elements of a record are pushed individually onto the stack and then pulled off individually. This not efficient in a data and memory intensive operation such as sorting where there will be very many such calls to the callback function. The use of the COPYOF operator allows the fastest possible function call and return. Another benefit of COPYOF (not used in the context of SORT ... USING callback) is that this operator takes the curse off the passing of arrays to a function (an operation which results in a compile-time error in standard 4GL).
COPYBACK is the reciprocal of COPYOF and can be used to return large local data structures (e.g. records or arrays) using the C-library function memcpy(). The syntax is:
COPYBACK varname
which will overwrite the original source variable with the current data in varname local to the callback function. (Aubit4GL remembers where it copied it from at function invocation and performs memcpy() with the original arguments reversed.)
This example shows how to use the Aubit4GL statement: SORT ... USING sortfunc
DATABASE test1
DEFINE lv_array ARRAY[100] OF RECORD LIKE systables.*
FUNCTION qsort_tabname(COPYOF lv_sys1, COPYOF lv_sys2)
DEFINE lv_sys1,lv_sys2 RECORD LIKE systables.*
IF lv_sys1.tabname>lv_sys2.tabname THEN RETURN 1 END IF
IF lv_sys1.tabname=lv_sys2.tabname THEN RETURN 0 END IF
RETURN -1
END FUNCTION
MAIN
DEFINE lv_cnt integer
DEFINE lv_a integer
DECLARE c1 CURSOR FOR
SELECT * FROM systables WHERE tabid<99
LET lv_cnt=1
FOREACH c1 INTO lv_array[lv_cnt].*
LET lv_cnt=lv_cnt+1
END FOREACH
FOR lv_a=1 TO 100
IF lv_array[lv_a].tabname IS NOT NULL THEN
DISPLAY "BEFORE:",lv_array[lv_a].tabname CLIPPED
END IF
END FOR
SORT lv_array USING qsort_tabname
FOR lv_a=1 TO 100
IF lv_array[lv_a].tabname IS NOT NULL THEN
DISPLAY "AFTER :",lv_array[lv_a].tabname CLIPPEd
END IF
END FOR
END MAIN
This example shows how you can (in Aubit4GL) use COPYOF to pass arrays to and from functions saving a lot of time if passing identical large structures. Omit the COPYOF in the example, and the 4GL compiler will produce a compile time error.
DEFINE cnt INTEGER
DEFINE a ARRAY[20] OF RECORD
b CHAR(20),
c INTEGER
END RECORD
MAIN
DEFINE b ARRAY[20] OF RECORD
b CHAR(20),
c INTEGER
END RECORD
FOR cnt=1 TO 20
LET a[cnt].b=cnt
LET a[cnt].c=cnt
END FOR
CALL bibble(COPYOF(a))
FOR cnt=1 TO 20
DISPLAY ">", a[cnt].b," ", a[cnt].c," <"
END FOR
END MAIN
FUNCTION bibble(COPYOF lv_a)
DEFINE lv_a array[20] OF RECORD
b CHAR(20),
c INTEGER
END RECORD
FOR cnt=1 TO 20
DISPLAY ">", a[cnt].b," ",
a[cnt].c," <", lv_a[cnt].b," ",
lv_a[cnt].c
LET lv_a[cnt].b=99-cnt
END FOR
COPYBACK lv_a
END FUNCTION
Notice that both the CALL and the FUNCTION definition need the COPYOF operator applied to their arguments.
Here lv_a is a local copy of the array a. Instead of passing in 40 separate parameters (two for each element in the array), we just pass in a single block of memory. Internally Aubit4GL uses the C function memcpy() to copy this over the lv_a when the function is called, so it is a byte for byte copy.
(The sizes of the COPYOF parameter must match - or an error is flagged up)
Now, the normal behaviour (if the COPYBACK is commented out) would be that, at the end of the function, the changes made to lv_a would be lost. But as it was a COPYOF parameter, we can copy those details back to the original array a using the COPYBACK command(which basically does a memcpy() the other way).
14.8.3 LOAD ... USING FILTER fname ...
Aubit4GL has added an optional USING FILTER filterfunc clause to the 4GL LOAD command.
Syntax:
LOAD FROM filename
USING FILTER filterfunc
INSERT into tabname[(col, ...)]
The filter function needs to return the data to insert (one value per column) and can return 0 values, in which case the line is ignored and no insert performed.
There is a builtin CSV parser filter function available:
aclfgl_parse_csv()
which you can use to load a CSV file. e.g. :
LOAD FROM myfile.csv
USING FILTER aclfgl_parse_csv
INSERT INTO mytab
The callback function takes a string which is the entire line from the load file and returns the individual columns to insert. Returning no values means that that line in the file will be skipped. This can be very useful for data validation (only load certain rows) or ensuring foreign keys exist etc. ( or possibly inserting blank ones).
There is a new builtin function
aclfgl_split_on_delimiter()
which can be used to split the fields. e.g. :
CALL aclfgl_split_on_delimiter ("A|B|C|")
RETURNING lv_rec.*
CALL aclfgl_split_on_delimiter ("#","A#B#C#")
RETURNING lv_rec.*
Obviously, this adds possibilities over and above inserting data. For example, you could LOAD the /etc/passwd with a callback which calls the above split function but returns nothing so that it won’t try to do any inserts but you will have captured the values in an array of 4GL RECORDs.
There is a variant of the LOAD command which takes a variable holding the INSERT statement:
LOAD FROM file USING FILTER filtfunc varname
The variable varname would contain something like:
INSERT into mytab