One of the oldest things in the 4GL is buffer scoping. Nowadays we have some syntax to decouple the buffer from the definition (see the BIND-discussion http://www.psdn.com/library/thread.jspa?threadID=2891&tstart=0).
When I look at the daCar.p procedure in AutoEdge I see something that makes me shiver:
- definition of datasets
- passing of handles to override the globally defined dataset
That's one of the things I really dislike about todays ABL, since it's very error prone.
When I look at beCar.p calling daCar.p, I see the handle magic demonstrated:
PROCEDURE fetchWhere :
DEFINE OUTPUT PARAMETER DATASET-HANDLE phDataSet .
phDataSet = DATASET dsCar:HANDLE.
RUN fetchWhere IN hAccessProc
(OUTPUT DATASET dsCar BY-REFERENCE).
END PROCEDURE. /* fetchWhere */
We pass our beCar.p globally defined dataset to our daCar.p and let it populate that one. So what happens when you call this procedure twice during a task?
Another thing I don't like in the daCar and sceCar routines is the dynamic buffer handling, bypassing compile time checks on the buffers. Things like:
ASSIGN hCurBuffer = phDataSet:get-buffer-handle("eCar":U).
cCurCarId = hCurBuffer:buffer-field("CarId"):buffer-value.
in sceCar.p.
I know that, when you start passing a dataset handle, you're forced to make another procedure call to cast the handle back to a static definition.
I also know that you're forced to use a handle when you don't want to populate the dataset defined at the global procedure level.
Maybe the sce-procedures could be used with static defined buffer parameters, so at least the sce-routines can do a simple FIND/FOR EACH to populate the datasets...
When I look at the daCar.p procedure in AutoEdge I
see something that makes me shiver:
- definition of datasets
- passing of handles to override the globally defined
dataset
That's one of the things I really dislike about
todays ABL, since it's very error prone.
The dataset definition in daCar.p should be made REFERENCE-ONLY. This way, it's less error prone because you can't use the globally define dataset, only the one passed using either the BY-REFERENCE or BIND option.
When I look at beCar.p calling daCar.p, I see the
handle magic demonstrated:
PROCEDURE fetchWhere :
DEFINE OUTPUT PARAMETER DATASET-HANDLE phDataSet
.
phDataSet = DATASET dsCar:HANDLE.
RUN fetchWhere IN hAccessProc
(OUTPUT DATASET dsCar BY-REFERENCE).
PROCEDURE. /* fetchWhere */
We pass our beCar.p globally defined dataset to our
daCar.p and let it populate that one. So what happens
when you call this procedure twice during a task?
Looking at the fetchWhere in daSupport.p, the dataset is emptied prior to the fill.
The dataset definition in daCar.p should be made
REFERENCE-ONLY. This way, it's less error prone
because you can't use the globally define dataset,
only the one passed using either the BY-REFERENCE or
BIND option.
Yes.
If you want to go in this direction maybe it would be better to wrap the dataset by a class and exchange a class instance, simulating a resultset object. This way the dataset will always have a parent: the class instance wrapping it.
According to KB-entry P70795 the OUTPUT dataset handle should be deleted at the end of the procedure, so
PROCEDURE fetchWhere :
DEFINE OUTPUT PARAMETER DATASET-HANDLE phDataSet.
phDataSet = DATASET dsCar:HANDLE.
RUN fetchWhere IN hAccessProc (OUTPUT DATASET dsCar BY-REFERENCE).
PROCEDURE. /* fetchWhere */
Should be:
PROCEDURE fetchWhere :
DEFINE OUTPUT PARAMETER DATASET-HANDLE phDataSet.
phDataSet = DATASET dsCar:HANDLE.
RUN fetchWhere IN hAccessProc (OUTPUT DATASET dsCar BY-REFERENCE).
DELETE OBJECT phDataSet.
PROCEDURE. /* fetchWhere */
Another thing I don't like in the daCar and sceCar
routines is the dynamic buffer handling, bypassing
compile time checks on the buffers. Things like:
ASSIGN hCurBuffer =
phDataSet:get-buffer-handle("eCar":U).
cCurCarId =
hCurBuffer:buffer-field("CarId"):buffer-value.
in sceCar.p.
I know that, when you start passing a dataset handle,
you're forced to make another procedure call to cast
the handle back to a static definition.
I also know that you're forced to use a handle when
you don't want to populate the dataset defined at the
global procedure level.
Maybe the sce-procedures could be used with static
defined buffer parameters, so at least the
sce-routines can do a simple FIND/FOR EACH to
populate the datasets...
From what I understand, when separating DSO from DAO object, the DSO become unaware of the dataset structure. One reason is for being able to re-use the DSO with different dataset. This means the DSO routines have to receive the dataset by DATASET-HANDLE.
However, the DSO should still know the temp-table which he's supposed to fill.
As you said, maybe the dataset's temp-table handle could be cast to a static tt, then this tt could be use statically inside DSO routines.
Here's a little example that seems to work but hasn't been thoroughly tested.
/ sceXXX.p /
/* Include the tt definition as reference-only.
It will be bound to the tt in the dataset-handle received. */
DEF TEMP-TABLE tt REFERENCE-ONLY
FIELD f1 AS CHAR
FIELD f2 AS INTE.
PROCEDURE AfterRowFill:
DEF INPUT PARAM DATASET-HANDLE phDataSet.
DEF VAR htt AS HANDLE.
/* Dynamically get the temp-table handle from the dataset.
Then bind it to the statically defined one using DynamicToStatic procedure.*/
ASSIGN htt = phDataSet:GET-BUFFER-HANDLE("tt"):TABLE-HANDLE.
RUN DynamicToStaticTT(INPUT TABLE-HANDLE htt BIND).
/* This message show that the static tt is now bound the dynamic one,
as it displays the same handle. */
MESSAGE htt TEMP-TABLE tt:HANDLE
VIEW-AS ALERT-BOX INFO BUTTONS OK.
/* Can read and create records statically */
FOR EACH tt:
DISPLAY tt.
END.
CREATE tt.
ASSIGN tt.f1 = "def"
tt.f2 = 456.
END PROCEDURE.
PROCEDURE DynamicToStaticTT:
DEF INPUT PARAM TABLE FOR tt BIND.
MESSAGE TEMP-TABLE tt:HANDLE
VIEW-AS ALERT-BOX INFO BUTTONS OK.
END PROCEDURE.
I seem to recall
this suggestion somewhere ... Among other things, it keeps one
from having to have temp-table definitions all over the place.
I seem to recall this suggestion somewhere ...
Yes, I even thought about quoting you, but I was a bit worried for your reply This approach simply wraps the data container like an internal Data Transfer Object, so it wouldn't be a full blown domain object... And here we are again: use the ABL strength (and weaknesses) using buffers/temp-tables or using domain objects and don't use/expose buffers/temp-tables at all....
To be sure, I would tend to go all the way, but I think any amount of wrapping illustrates the point.