Aubit 4GL enhancements to standard I4GL language
In this chapter:
FIXME:
RPC calls Very sexy - Is this the same as RFC? Should we call ir RFC or RPC ?
Dynamic Library Interface Again - very useful - WHAT IS THIS in English please
Curses Windows handling Normally a bitch to use - HOW DOES THIS AFFECT a4gl?
Add new GUI related syntax in menu files, form files, menuhandler, etc...
A4GL enhanced syntax summary table
|
For this a4gl Syntax... |
...See this feature details... |
| LET Age<<"Fred">>=30 | Associative Arrays |
| DEFINE .... ASSOCIATE ... WITH ... | Associative Arrays |
| SET PAUSE MODE ON|OFF | PAUSE Screen Handling |
| SET SESSION | CURSOR OPTION | SET SESSION OPTION/SET CURSOR OPTION |
| OPEN SESSION ... TO DATABASE ...AS USER ... PASWORD ... | Multiple concurrent connections |
| SET SESSION TO ... | Multiple concurrent connections |
| USE SESSION ... FOR ... | Multiple concurrent connections |
| CLOSE SESSION | Multiple concurrent connections |
| DEFINE CONSTANT ... | Application Constants |
| _variable(x) | Variable IDs |
| code { ....} endcode | Embedded "C" code |
| HIDE | SHOW | MOVE WINDOW | Move/Show/Hide Window |
| WHENEVER SUCCESS | SQLSUCCESS | Whenever success/sqlsuccess |
| FGL_SET_ARRLINE | FGL_SET_SCRLINE | Extended Display Array Control |
| LOCAL FUNCTION ... | Local functions |
| CALL get_info(...) | get_info function |
| f000=..., dynamic size=x (.PER files) | Dynamic Screen Fields |
| START EXTERNAL FUNCTION[x] FOR ... | Remote Function Calls |
| CALL EXTERNAL ...[x] (y) | Remote Function Calls |
| DEFINE ... LINKED TO ...(...) | Select/delete/update using |
| SELECT | DELETE | UPDATE USING ... | Select/delete/update using |
| ON ANY KEY | On any key |
| TEMPLATE ... END TEMPLATE | A4GL Wizard and program templates |
| ENABLE/DISABLE fields | ENABLE/DISABLE fields |
Allows arrays and/or record arrays to be indexed by character strings :
|
Define
lv_str CHAR(20) |
Assoc. arrays let you use strings instead of integers for the subscripts - great if you have a lookup table, eg country codes. But then you cannot move trough them with, say
|
FOR x=1 to 20 |
Quite often you don't need to, but maybe this is something we need to look at.
With a4gl:
|
DEFINE lv_arr ASSOCIATE CHAR(10) WITH ARRAY [20] OF CHAR(80) |
Assoc. arrays let you use strings instead of integers for the subscripts - great if you have a lookup table, eg country codes
| LET desc<<"UK">>="United Kingdom" LET desc<<"NZ">>="New Zealand" LET lv_code="NZ" DISPLAY lv_code," ",desc<<lv_code>> |
Result:
NZ New Zealand
SET PAUSE MODE ON|OFF - pause screen updates
Use this to turn off screen redraws during a large number of screen updates, SET PAUSE MODE ON, then open all windows, forms etc. then SET PAUSE MODE OFF to redraw the screen in a single pass, great for displaying information on a window that is obscured slightly by the another window.
Screen drawing can be slow on some low speed links (eg. over a modem on a tty).If there are a lot of changes to be made to a screen, eg. opening a few windows, it may be advantagous to group the changes into a single block and then refresh the screen. This can be also be used to stop the screen being updated at all.
To stop the screen being updated:
SET PAUSE MODE ON
To refresh the screen
SET PAUSE MODE OFF
This can also be useful when you need to display something to a window which is currently overlapped:
| Window
w1
|
|||
|
Window w2
|
|||
|
SET PAUSE MODE ON |
Normally
| Window
w1
|
|||
|
Window w2
|
|||
| Window
w1
|
||||
|
Window w2 |
||||
| Window
w1
|
|||
|
Window w2
|
|||
This sequence would normally be fairly quick and appear as a flash on the screen
With pause mode
| Window
w1
|
|||
|
Window w2
|
|||
| Window
w1
|
|||
|
Window w2
|
|||
Uses ODBC to connect to data. This allows access to heterogeneous data and increased portability of application. See also SET SESSION OPTION/SET CURSOR OPTION
Using ODBC allows access to any data source for which an ODBC driver is available. This requires:
1) An ODBC driver manager
2) (UNIX Only) ODBC sources file (~/.odbc.ini)
3) Username/Password combination for the required data source.
Notes:
UNIX
The ODBC driver for UNIX is compiled into the 4GL application.
Windows
The ODBC driver is a core part of the operating system
ODBC Sources file - Unix
This file should normally reside in the users home directory and is called '.odbc.ini' .This file has the following format :
[ODBC Data Sources]
sample=Aubit Computing Ltd
[sample]
Driver = /home/odbcdrivers/driver.so
Host = localhost
Database = mja1
Options =
ReadOnly = No
FetchBufferSize = 60
The entries under "ODBC Data Sources" compromise the Data Source Names (or DSNs). Each entry here has a correspnding group of options which are dependant on the driver being used. Aubit 4GL uses these Data Source Names as the "Database Name" in the modules.
For the example above we might use:
DATABASE sample
DEFINE ...
In order to make a connection to a data source, you generally need to specify a Username and Password. Depending on the ODBC driver, you may need to specify a valid Unix Login/Password or Database Login/Password. In Aubit 4GL these may be specified in one of two ways :
Using the SQLUID and SQLPWD environment variables & the DATABASE statement:
eg.
DATABASE informix
or in the program
eg.
OPEN SESSION s_id1 TO DATABASE informix as user "username" password "password"
or
OPEN SESSION s_id1 TO DATABASE informix as "username", "password"
Using the Open Session allows you to obtain the username and password at runtime from the user more easily than using the DATABASE statement and is more secure.
During development it is easier to use the DATABASE statement and SQLUID/SQLPWD, and this format MUST be used when using the DEFINE..LIKE statement.
Sessions
Using the DATABASE statement opens a connection or 'session' to the database with the name 'default'. It is possible to open more than one session to the same, or different databases, and because ODBC makes available databases which are not even of the same type, it is possible to open two sessions to two completly different databases.
The syntax for using sessions is as follows
OPEN SESSION session_name TO DATABASE database_name AS USER "username" PASSWORD "password"
If you do not specify a username and password then these will be read from the SQLUID and SQLPWD environment statements. To make use of sessions you may either:
1) Change the current session
2) Temporarily change the current session.
SET SESSION TO session_name
USE SESSION session_name FOR
[SQL STATEMENT]
You can remove close a database session by using the CLOSE SESSION statement:
Eg.
CLOSE SESSION session1
An example:
|
MAIN DEFINE l_rec RECORD var1 INTEGER, var2 CHAR(20) END RECORD OPEN SESSION original_db TO DATABASE db1 AS USER "m123456" PASSWORD "a1234" OPEN SESSION copy_db TO DATABASE db2 AS USER "m654321" PASSWORD "b2345" USE SESSION original_db FOR DECLARE c1 CURSOR FOR SELECT col1,col2 FROM tab1 FOREACH c1 INTO l_rec.* USE SESSION copy_db FOR INSERT INTO copy_tab VALUES(l_rec.*) END FOREACH CLOSE SESSION copy_db CLOSE SESSION original_db END MAIN |
Using the new OPEN/USE/CLOSE SESSION it is possible to open multiple connections to the same or to different data sources. For example, run two transactions against a database or copy from one type of database to another. Use Informix and Access to gather data for a report. Or Oracle and SQL-Server for data entry screens. It ideal for copying data too.
allow access to multiple databases at the same time.
OPEN SESSION
USE SESSION
CLOSE SESSION
DEFINE CONSTANT name
"Fred"
DEFINE CONSTANT cv_arraysize 20
DEFINE m_array ARRAY[cv_arraysize] OF
INTEGER
These can then even be used for array sizing
DEFINE CONSTANT cv_arrsize 100
DEFINE m_array ARRAY[cv_arrsize] OF INTEGER
FOREACH some_cursor into m_array[l_counter]
LET l_counter=l_counter+1
IF l_counter>vc_arrsize THEN
ERROR "There May be more rows than can be displayed"
EXIT FOREACH
END IF
END FOREACH
It is possible to instruct the compiler to generate a map file describing where variables, functions, cursors, windows etc. are defined and used. Detailed list of the usage of cursors, windows, variables, tables, columns etc.
4glpc -map hello_db.4gl
this will produce a hello_db.map file in the format I described
It gives answers to :
I'll try and make a full list sometime soon..
Have you looked at map files yet ? (Do a grep for addmap in the rules: $ grep addmap *.rule)
type|object|Place|Line|Module
Where type is :
d for a define
M is for the MAIN
L is for an assignment (LET)
v is for a variable being used
W is for a window name
C is for a function call
F is for a function definition
t is for a table being referenced
D is for a declare cursor
(I think these are right - have a look for 'add_map' in the 4glc/rules).
Object is the name of the variable/cursor/window etc
Place is 'Module' for functions reports and 'MAIN' Its 'MAIN' for things in 'MAIN'..'END MAIN' and its the Function name/report name for things in those areas.
for hello_db.4gl :
d|ifx_sess|Module|2|hello_db.4gl|
d|pg_sess|Module|4|hello_db.4gl|
M|MAIN|MODULE|10|hello_db.4gl|
d|p_tabname|MAIN|13|hello_db.4gl|
d|dsn_informix|MAIN|14|hello_db.4gl|
d|dsn_postgres|MAIN|16|hello_db.4gl|
L|ifx_sess|MAIN|18|hello_db.4gl|
L|pg_sess|MAIN|19|hello_db.4gl|
v|dsn_informix|MAIN|21|hello_db.4gl|
v|dsn_postgres|MAIN|22|hello_db.4gl|
W|w1|MAIN|29|hello_db.4gl|
w|w1|MAIN|33|hello_db.4gl|
C|db_select|MAIN|36|hello_db.4gl|
F|db_select|MODULE|49|hello_db.4gl|
d|dsn_informix|db_select|52|hello_db.4gl|
d|dsn_postgres|db_select|54|hello_db.4gl|
C|ifx_session|db_select|77|hello_db.4gl|
v|dsn_informix|db_select|77|hello_db.4gl|
C|pg_session|db_select|84|hello_db.4gl|
v|dsn_postgres|db_select|84|hello_db.4gl|
C|pg_session_scroll|db_select|89|hello_db.4gl|
v|dsn_postgres|db_select|89|hello_db.4gl|
F|ifx_database|MODULE|104|hello_db.4gl|
d|dsn_informix|ifx_database|107|hello_db.4gl|
d|p_tabname|ifx_database|109|hello_db.4gl|
t|systables|ifx_database|120|hello_db.4gl|
D|"hello_db:c1"|ifx_database|120|hello_db.4gl|
v|p_tabname|ifx_database|121|hello_db.4gl|
v|p_tabname|ifx_database|123|hello_db.4gl|
F|ifx_session|MODULE|132|hello_db.4gl|
d|dsn_informix|ifx_session|135|hello_db.4gl|
d|p_tabname|ifx_session|137|hello_db.4gl|
L|dsn_informix|ifx_session|145|hello_db.4gl|
v|ifx_sess|ifx_session|150|hello_db.4gl|
L|ifx_sess|ifx_session|153|hello_db.4gl|
t|systables|ifx_session|160|hello_db.4gl|
D|"hello_db:c4"|ifx_session|160|hello_db.4gl|
v|p_tabname|ifx_session|161|hello_db.4gl|
v|p_tabname|ifx_session|163|hello_db.4gl|
F|pg_database|MODULE|174|hello_db.4gl|
d|dsn_postgres|pg_database|178|hello_db.4gl|
d|p_tabname|pg_database|180|hello_db.4gl|
t|pg_type|pg_database|190|hello_db.4gl|
D|"hello_db:c2"|pg_database|190|hello_db.4gl|
v|p_tabname|pg_database|191|hello_db.4gl|
v|p_tabname|pg_database|193|hello_db.4gl|
F|pg_session|MODULE|202|hello_db.4gl|
d|dsn_postgres|pg_session|205|hello_db.4gl|
d|p_tabname|pg_session|207|hello_db.4gl|
v|pg_sess|pg_session|213|hello_db.4gl|
L|dsn_postgres|pg_session|213|hello_db.4gl|
v|dsn_postgres|pg_session|215|hello_db.4gl|
L|pg_sess|pg_session|227|hello_db.4gl|
t|pg_type|pg_session|236|hello_db.4gl|
D|"hello_db:c3"|pg_session|236|hello_db.4gl|
v|p_tabname|pg_session|237|hello_db.4gl|
v|p_tabname|pg_session|239|hello_db.4gl|
F|pg_session_scroll|MODULE|251|hello_db.4gl|
d|dsn_postgres|pg_session_scroll|254|hello_db.4gl|
d|p_tabname|pg_session_scroll|256|hello_db.4gl|
v|pg_sess|pg_session_scroll|264|hello_db.4gl|
L|pg_sess|pg_session_scroll|266|hello_db.4gl|
t|pg_type|pg_session_scroll|275|hello_db.4gl|
D|"hello_db:c3"|pg_session_scroll|275|hello_db.4gl|
v|p_tabname|pg_session_scroll|276|hello_db.4gl|
v|p_tabname|pg_session_scroll|278|hello_db.4gl|
>|"hello_db:c3"|pg_session_scroll|281|hello_db.4gl|
v|p_tabname|pg_session_scroll|282|hello_db.4gl|
v|p_tabname|pg_session_scroll|284|hello_db.4gl|
>|"hello_db:c3"|pg_session_scroll|284|hello_db.4gl|
v|p_tabname|pg_session_scroll|285|hello_db.4gl|
v|p_tabname|pg_session_scroll|287|hello_db.4gl|
(For use with Form/cursor/window/Prepare statement names etc.) Many of these compile time 'names' may now be variables or string constants. They are also compiled in such a way that one module can access cursors etc. defined in another. Each ID name is processed by the module name at compile time, cursor, c_cursor1 in module mod1.4gl becomes "mod1:c_cursor1" when called from a different module. In this way normal processing is not affected. In order to use strings or variables use the _variable keyword in 4GL :
DEFINE a
CHAR(20)
LET
a="Window1"
OPEN FORM _variable(a)
FROM "filename"
DISPLAY FORM
_variable("Window1")
4GL uses a lot of IDs, session names, cursor names, form names etc.
These may be written using the normal identifier method or via the special function '_variable' (note the important '_')
OPEN WINDOW _variable("e1") AT 1,1 ....
is roughly equivalent to (see globbing) :
OPEN WINDOW e1 AT 1,1
The advantage of this is that IDs can now be passed to functions as strings.
You dont need the _variable for these instances, the _variable is for naming things, eg. windows, cursors etc. where YOU give it a name
The informix syntax for database is
DATABASE dbname
OR
DATABASE variable-name
This is the same in a4gl:
DEFINE xx CHAR(20)
LET xx="mydbname"
DATABASE xx #1
DATABASE yy #2
If it can't resolve the name as a variable - it assumes its the name of the database
#1 - Connect to "mydbname"
#2 - Connect to "yy"
The same goes for the sessions. The only difference is that you GIVE the sessions a name:
OPEN SESSION mysession TO DATABASE xx #1 again
or
OPEN SESSION mysession TO DATABASE yy #2 again
Would work the same as before.
You can use the _variable for the open session - but only for specifying the name of the session :
DEFINE zz CHAR(20)
LET zz="mysession"
OPEN SESSION zz TO DATABASE xx # would open a session called 'zz' #1
OPEN SESSION _variable(zz) TO DATABASE xx # would open a session
called 'mysession' #2
In fact - its a little more involved than that - if you look at the generated code, the code in #1 would actually 'clobber' the session id to be 'modulename:zz', whereas #2 would not do any clobbering and so could be easily referenced from a different module...
_variable (cursor_name)
_variable (session_id)
_variable (window_name)
etc...DEFINE lv_winname CHAR(20)
DEFINE lv_cnt INTEGER
FOR lv_cnt=1 to 10 STEP 4
LET lv_winname="w_",lv_cnt using "<<"
OPEN WINDOW _variable(lv_winname)
at lv_cnt,lv_cnt with 3 columns,3 rows
attribute(border) ## check syntax for the rows/columns bit
END FOR
_variable is NOT supported in that place because it is not REQUIRED in that place!
WRT the etc. - I'll check the source and get you a definative list - bit it is basically any 'NAMES' like statements, windows, etc.
OPEN WINDOW _variable(...
OPEN _variable(cursor)
But I'll generate a list:
> > They use some spec char (I forgot) to signify that the following is
> > the name of the variable, like OPEN WINDOW @var_winname AT ...
>
> Do you want to change _VARIABLE to be @variable ?
module1.4gl:
DECLARE c1 CURSOR FOR SELECT * FROM systables
CALL open_cursor("module1:c1")
anymod.4gl:
FUNCTION open_cursor(p_cursor)
DEFINE p_cursor CHAR(20)
OPEN CURSOR _variable(p_cursor)
IF SQLCA.SQLCODE!=0 THEN
ERROR "There was some error opening the cursor!"
END IF
END FUNCTION
Whilst C code may hamper portability, this feature does allow embedding of C statements within the 4GL code. This means that you do not need to compile and call separate C functions in C modules:
MAIN
code
{
char buff[256];
FILE *f;
f=fopen("filename","r");
fgets(buff,255,f);
}
endcode
DISPLAY "Read : ",buff clipped AT 1,1
END MAIN
You can embed C code directly into a 4GL program by using the code/endcode block. The code and endcode MUST be in lowercase (This is the only statement that is case-sensitive and MUST begin at the start of a line and the line may not contain any other characters (including spaces and tabs) :
code
f=fopen("FILE","r");
ENDCODE # this will not work!!!
Everything between the code and endcode is passed directly into the C module created when the 4GL module is compiled.
You can use code/endcode :
1) Any where you can use any other 4GL statement
2) Before/After any section
You cannot use the code/endcode in a:
1) globals section
2) in a define section
Variables
You can define variables in a code section as in normal C after a '{'. It is therefore common to use the following code segment:
code
{
.
.
}
endcode
Any variables defined in the code segment are only available IN that code segment. Normal C rules apply (eg. define the variable as 'static' if you want to keep its value between calls.)
You can also access the variables define in the 4GL module. INTEGERs normally translate to long, SMALLINTs to short etc. The most common mistake made is with strings, strings in 4GL are padded to fill the size of the string with spaces ' '.When using the strings in C you normally need to remove these spaces.
E.g.:
FUNCTION open_file(p_filename,p_mode)
# This is a bad example because it doesnt work properly
DEFINE p_filename CHAR(20)
DEFINE p_mode CHAR(2)
DEFINE lv_file INTEGER
code
lv_file=(long)fopen(p_filename,p_mode);
endcode
RETURN lv_file
END FUNCTION
Calling this with "fgl.log","r" will result in a call to fopen of ("fgl.log ","r ") which will most likely fail.
The easiest way of resolving this is to remove the extra spaces using the trim 'C' function:
FUNCTION open_file(p_filename,p_mode)
# This is a better example
DEFINE p_filename CHAR(20)
DEFINE p_mode CHAR(2)
DEFINE lv_file INTEGER
code
trim(p_filename); /* must be in a code/endcode block */
trim(p_mode); .
lv_file=(long)fopen(p_filename,p_mode);
endcode
RETURN lv_file
END FUNCTION
Calling 4GL functions from embedded C code
4GL function names are 'globbed' when compiled in the above example
FUNCTION open_file
will be translated into
aclfgl_open_file()
You need to pass the number of parameters as the parameter, and push the actual parameters onto the 4GL stack.
Eg:
code
{
int a;
push_char("fgl.log");
push_char("r");
a=aclfgl_open_file(2);
}
Numbers can be pushed using push_double, push_float, push_int,push_date etc.
The number of values returned from the function is returned as an integer from the function and can be poped from the 4GL stack using the pop.. group of functions
Calling C functions from 4GL
Use a code/endcode block (see the example above). Some C functions required the inclusion of another file via a '#include .." statement. These can be put in a code/endcode block at the top of the program
eg. full file open example
code
#include <stdio.h>
endcode
FUNCTION open_file(p_filename,p_mode)
DEFINE p_filename CHAR(20)
DEFINE p_mode CHAR(2)
DEFINE lv_file INTEGER
code
trim(p_filename); /* must be in a code/endcode block */
trim(p_mode);
/* need to convert this to a long only works if sizeof(long)=sizeof(void
*) */
lv_file=(long)fopen(p_filename,p_mode);
endcode
RETURN lv_file
END FUNCTION
Most 4gls allow calls to C, but doing that for a novice is not easy, tried calling fopen ? You have to write all the interface code yourself to pop off the stack and push back on it...
FUNCTION fopen(p_filename,p_mode)
DEFINE p_filename CHAR(128)
DEFINE p_mode CHAR(5)
DEFINE p_fileid INTEGER
code
trim(p_filename); // library function to remove trailing spaces
trim(p_mode);
p_fileid=fopen(p_filename,p_mode);
endcode
RETURN p_fileid
END FUNCTION
Could it be any simpler ?
(A4GL can also link to .so's looking up functions names dynamically...)
Allow Moving, hiding and redisplaying of windows:
HIDE WINDOW
SHOW WINDOW
MOVE WINDOW
Might come in handy, but costly in terms of performance!
WHENEVER SUCCESS/SQLSUCCESS
MENU
"Main Menu"
COMMAND "SubMenu 1"
MENU "Menu 2"
COMMAND "Option 2.1
COMMAND "Exit'
EXIT MENU
END MENU
COMMAND "SubMenu 2"
MENU "Menu 2"
COMMAND "Option 2.1
COMMAND "Exit'
EXIT MENU
END MENU
END MENU
BEFORE/AFTER ROW in DISPLAY ARRAY
Use Before row/After row in display arrays to display highlight bars or associated information.
FGL_SET_ARRLINE / FGL_SET_SCRLINE
Use with display/input arrays to set current array and screen lines - Great for searches.
Added in 'dddd','mmmm' for full weekday names and month names, also added 'th' for '1st', '2nd' type processing and 'd' & 'm' for non-padded days and months, eg : 'dddd dth mmmm yyyy' = 'Monday 20th July 1998'. And because not everybody speaks English, these are overridable by using either Environment variables or compile time environment settings.
Can also use :
dddd mmmm th
in the using clause for full day names, month names and the (1)'st', (2)'nd', (3)'rd', (4)'th' of the day number
functions can be defined as being local to a module (ie not callable outside that module).
LOCAL FUNCTION func_name...
END FUNCTION
This is the same as 'static' before a function in 'C'
CALL getinfo...
Get information on most things from with 4GL code, includes :
Windows
Height, Width etc., Comment/Message/Error line
Forms
Delimiters, database, screen record count, names, field names/count, Current field, Width, Height of fields.
Sessions
Database name, Most ODBC information.
Statements
No. of columns, rows, Column names, types, length etc.
There is a wide range of information available to 4GL programs about the current state of the application. This include details about Cursor, Windows, Forms etc. This information is the mainstay of 'extreme 4GL' because it allows the generation of generic code which can be used for a wide range of applications.
Firstly then, the get_info function call :
CALL get_info(type, ID, info) RETURNING ...
In all cases ID is the ID being queried. Bear in mind that this will normally point to a globbed value which should therefore include the module name containing the definition of the ID and a colon. Eg.
module1.4gl
OPEN WINDOW w1 AT 2,2 ....
CALL get_info("window","module1:w1","BeginX") RETURNING a
# a would be set to 2
For appropriate values for info for each types, see "Internal statements" section of Aubit 4gl manual
This allows the user to type in more text than can be displayed in a field by allowing horizontal scrolling
In the .per file :
No Maximum
Limit:
f000=formonly.fld1,dynamic;
Maximum of
80 characters:
f000=formonly.fld1,dynamic size=80;
This feature allow the programmer to write client/server applications using remote function calls. The functions can be based on the same host machine, or across a network. You can even use RFCs across platforms. This is useful for a variety of purposes especially :
Have you ever tried using rpcgen ? Ever tried to write the client and server side implementations ?
Heres the 4GL :
Server.4gl
FUNCTION disp(lv_who)
DEFINE lv_who CHAR(20)
DISPLAY "I've been called by ",lv_who CLIPPED
END FUNCTION
MAIN
START EXTERNAL FUNCTION[1] FOR disp
END MAINclient.4gl
MAIN
CALL EXTERNAL dax:disp[1] ("Me")
END MAIN
The function calls can also be set to run asynchronously by using the without waiting clause
Eg:
Server Code:
DEFINE mv_inv_num INTEGERFUNCTION next_invoice_number()
LET mv_inv_num=mv_inv_num+1
RETURN mv_inv_num
END FUNCTIONMAIN
SELECT MAX(invoice_no) INTO mv_inv_num FROM invoices
START EXTERNAL FUNCTION [1] FOR next_invoice_number
END MAINClient Code:
FUNCTION generate_invoice()
DEFINE lv_inv_no INTEGER
CALL EXTERNAL localhost : next_invoice_number[1] () RETURNING lv_inv_no
END FUNCTION
Example 2:
CALL EXTERNAL host:function-name '[' servernumber ']' '(' parameters ')'
START EXTERNAL FUNCTION '[' servernumber ']' FOR function-name-list
eg.2:
server.4gl
FUNCTION bibble()
RETURN 1
END FUNCTION
FUNCTION myfunc(x,y)
RETURN x+y
END FUNCTION
MAIN
START EXTERNAL FUNCTION [1] FOR bibble,myfunc
END MAIN
client.4gl
...
CALL EXTERNAL localhost:myfunc [1] (1,2)
...
It is now possible to 'link' a record with a table and its primary key.
Eg.
# stock (stock_code char(4), balance INTEGER);
DEFINE lv_stock LINKED TO stock (stock_code)
After defining this relation, you can then use the new USING construct to access this data:
LET lv_stock.stock_code="BAY1"
SELECT USING lv_stock
This will fill the record with all the other details from the table via the value set in the primary key field (or fields) The above example would be written by hand as :
SELECT * INTO lv_stock FROM stock WHERE stock_code=lv_stock.stock_code
You can also use the DELETE USING to delete the row referenced by the primary key.
In addition the UPDATE USING will update ALL non-key columns to the values in the structure for the given primary key. Columns given for the primary key are used to find the row to update. The values for the columns defined as primary keys are obviously not updated.
Eg.
LET lv_stock.stock_code="BAY1"
SELECT USING lv_stock
LET lv_stock.balance=lv_stock.balance-1
IF lv_stock > 0 THENUPDATE USING lv_stock
##################################################
# = UPDATE stock SET balance=lv_stock.balance
# WHERE stock_code=lv_stock.stock_code
##################################################ELSE # No stock left - remove the row
DELETE USING lv_stock
##################################################
# = DELETE FROM stock
# WHERE stock_code=lv_stock.stock_code
##################################################END IF
To define a composite primary key use the column names in the LINKED TO.
Eg.
DEFINE lv_tab1 LINKED TO mytable(pk1,pk2)
Useful with fgl_set_arrline/fgl_set_scrline for searches and context sensitive handling.
The library uses some environment variable, default values are specified by the library - but you can set your own, this are still overridden by environment variables when required.
Set ODBC specific options on sessions and cursors. For instance
DECLARE c1
CURSOR
SELECT tabname FROM systables
# only
pull back up to three rows
SET CURSOR c1
OPTION "MAX ROWS" TO "3"
# not
really need here - but dynamic cursors, no more stale data.
SET CURSOR c1
OPTION "CURSOR TYPE" TO "DYNAMIC"
OPEN c1
FOREACH c1
INTO l_str
DISPLAY l_str
END FOREACH
The applications generated can access data on another machine, and a GUI viewer is in construction which communicates with the application via TCP/IP. The built-in viewer is currently only text based. Once construction of the Thin Client viewer is complete the application will be able to run on any of one to three machines, and with the onset of Unix ODBC clients, application logic can still run on a server.
Possible Configurations
--- =
Network connection to different machines
+
= Running on the same machine
Data --- Application Logic --- Viewer
Data + Application Logic --- Viewer
Data --- Application Logic + Viewer
Data + Application Logic + Viewer
A4GL cannot ensure Y2K compliance of data sources but uses a AUBIT_Y2K variable for its own Y2K compliance. Setting this to a number will select which century will be chosen for a 2 digit year. E.g.. setting this to 80 will mean 80+ will be treated as the 19xx whilst 0-79 will be treated as the 20xx.
All IDs are shared between all modules in a program, in order to make them unique within a module a mechanism called globbing is employed. This entails using the module name to form part of the ID.
Upon compilation
DECLARE CURSOR c1 ...
has the Internal ID of "modulename:c1"
Eg. if the module was called test.4gl - "test:c1"
This is useful because it allows access to IDs defined in other modules when required
module1.4gl
open form form1 from "sales"
module2.4gl
display form _variable("module1:form1")
When you use the '_variable' no extra globbing is done, hence:
module1.4gl
open form _variable("form1") from "sales"
could be referenced in module2.4gl as :
display form _variable("form1")
Warning: Using this form of declaration can lead to problems when you require unique names across modules.
the Wizard processor takes an input file, and generates the 4GL for you after answering a couple of questions.
eg.
TEMPLATE
database=PROMPT "Database Name" # prompts for a dbname
tabname=PROMPT "Table Name" # prompts for a table name
collist=COLUMNS tabname # gets a list of columns for tab
funcname=PREPEND "proc_" TO tabname # see note 1
pk=PROMPTMANY "Primary key" # note 2
nonpk=collist-(pk) # note 3
pkvar=PREPEND "l_record." TO pk
nonpkvar=PREPEND "l_record." TO nonpk
varpkeq=LIST (USE pkvar WITH " = " ON pk) WITH " AND "
ENDTEMPLATE
database %database
function %funcname
define l_record record like %tabname
declare c_id1 cursor for
select * from %tabname where %(%pk*=l_record.%pk*)
foreach c_id1 into l_record.*
end main
In order to use the file you need the template compiler. This takes a module.tpl and outputs a module.4gl.
It would be quite easy to fudge 4glpc to read a tpl, convert it into /tmp/...4gl and then compile that and move the .c & .h files back..
There are some enhancements still needed on this - like reading some data directory for the primary keys etc. But that hasnt been done yet...
Runtime
There usage is really bloody complicated, but I dont envisage normal users using them.
We can generate a few for simple 4gl modules etc.
They work on lists which you can perform actions on. eg. prepend which add some text to everything in the list
note 1:
tabname is a list with only one entry, so we'll end up with another list called funcname = "proc_",tabname
note 2:
This gets as many values for 'pk' as required.
note 3:
list subtraction, removes elements from a list where they are in another list
When typing in 4GL there are some things that you have to keep on repeating because of the way 4GL works. This allows us to set up normal maintenance handlers in a couple of seconds!
ENABLE/DISABLE fields.. 100% complete
FIXME: This one is new, how do one use this?