OE10.2B04
I'm trying to define a method that returns a copy of an internal dataset.
How come the BY-VALUE token does not compile in a method parameters definition ?
From the help "Parameter definition syntax", it seems to me that this should work.
CLASS class1 :
DEF TEMP-TABLE tt
FIELD f1 AS CHAR.
DEF DATASET ds FOR tt.
METHOD PUBLIC VOID GetDSCopy(OUTPUT DATASET ds BY-VALUE):
END METHOD.
END CLASS.
I don't see "by-value" as a legal keyword.
"by-reference" exists, and is used by the caller, not the callee.
The default for the calling program is BY-VALUE, you don't have to specify it. BY-VALUE is specified for the CALLED program, to override any caller that used BY-REFERENCE.
mopfer wrote:
The default for the calling program is BY-VALUE, you don't have to specify it.
Yes, but default is not enough. I want to force the caller to get a copy, so he cannot access the encapsulated dataset.
mopfer wrote:
BY-VALUE is specified for the CALLED program, to override any caller that used BY-REFERENCE.
Yes, that is exactly what my example is trying to do in the original post.
How do you do this in a method ?
guilmori wrote:
mopfer wrote:
The default for the calling program is BY-VALUE, you don't have to specify it.
Yes, but default is not enough. I want to force the caller to get a copy, so he cannot access the encapsulated dataset.
mopfer wrote:
BY-VALUE is specified for the CALLED program, to override any caller that used BY-REFERENCE.
Yes, that is exactly what my example is trying to do in the original post.
How do you do this in a method ?
OUTPUT DATASET or DATASET-HANDLE will make a deep copy (ie value copy, not reference).
-- peter
pjudge wrote:
OUTPUT DATASET or DATASET-HANDLE will make a deep copy (ie value copy, not reference).
The CALLER may use BY-REFERENCE, which I want to prevent.
The CALLER must never be able to access the encapsulated dataset.
The CALLER may use BY-REFERENCE, which I want to prevent.
The CALLER must never be able to access the encapsulated dataset.
>
Ah, I see now. Add BY-REFERENCE to the keyword forget list? Or is this in some cases only.
Alternatively, always clone the dataset before passing it out; it's easiest using the COPY-DATASET method. Yes, you will incur at least one deep copy this way, and maybe two (clone + output) but this way you're sure that the original dataset is left untouched.
-- peter
or invoke write-json on the dataset to a longchar, and pass that back
as a parameter
or invoke write-json on the dataset to a longchar, and pass that back
as a parameter
It'd be interesting to compare the performance of (write-json + rehydrate the PDS) vs. (copy-dataset + output param).
-- peter
pjudge wrote:
Ah, I see now. Add BY-REFERENCE to the keyword forget list? Or is this in some cases only.
Hehe , no, in some cases only.
pjudge wrote:
Alternatively, always clone the dataset before passing it out; it's easiest using the COPY-DATASET method. Yes, you will incur at least one deep copy this way, and maybe two (clone + output) but this way you're sure that the original dataset is left untouched.
Yeah, I did try this alternative, but I didn't like the extra deep copy.
Unless of course the caller use BY-REFERENCE, but this is quite confusing, especially on a method named GetDSCopy()...
Yeah, I did try this alternative, but I didn't like the extra deep
copy.
Unless of course the caller use BY-REFERENCE, but this is quite
confusing, especially on a method named GetDSCopy()...
>
If you don't trust that the developer not to change the contents of the dataset, then maybe cloning is the only way to go. If course, now you have a separate copy of the dataset, you can expose the clone as a handle and avoid the (second) deep copy.
-- peter
guilmori wrote:
BY-VALUE
Specified for an INPUT, OUTPUT, or INPUT-OUTPUT TABLE, TABLE-HANDLE, DATASET, or DATASET-HANDLE parameter in a called routine, this option forces the parameter to be passed to the local routine by value, which overrides any BY-REFERENCE option in the corresponding routine invocation. For more information on BY-REFERENCE, see the Parameter passing syntax reference entry.
Well there you go!
pjudge wrote:
If you don't trust that the developer not to change the contents of the dataset, then maybe cloning is the only way to go.
I would never trust anyone to not use wrongly exposed write privileges.
pjudge wrote:
If course, now you have a separate copy of the dataset, you can expose the clone as a handle and avoid the (second) deep copy.
I want the caller to read a statically defined dataset.
What about the BY-VALUE option not compiling in the method definition? Is this a documentation bug ? Not supported for method, only for procedure ?
this extremely rough example shows that the xml version is approx 2.5x slower than a pds copy (65ms vs 25ms)
def temp-table ttCustomer like Customer.
def temp-table ttOrder like Order.
def temp-table ttOrderLine like Order-Line.
def dataset pdsData for ttcustomer, ttorder, ttorderline
data-relation for TTCustomer, TTOrder relation-fields (cust-num,cust-num) nested
data-relation for TTOrder, TTOrderLine relation-fields (order-num,order-num) nested.
def data-source dsCustomer for Customer.
def data-source dsOrder for Order.
def data-source dsOrderLine for Order-Line.
def var lv_Data as longchar no-undo.
def var pdsCopy as handle no-undo.
buffer TTCustomer:attach-data-source (data-source dsCustomer:handle).
buffer TTOrder:attach-data-source (data-source dsOrder:handle).
buffer TTOrderLine:attach-data-source (data-source dsOrderLine:handle).
dataset pdsData:fill ().
dataset pdsData:write-xml("longchar",lv_Data).
dataset pdsData:empty-dataset ().
etime(yes).
dataset pdsData:read-xml("longchar",lv_Data,?,?,?).
message etime view-as alert-box.
etime(yes).
create dataset pdsCopy.
pdsCopy:create-like (dataset pdsData:handle).
pdsCopy:copy-dataset (dataset pdsData:handle).
message etime view-as alert-box.
Guillaume, it seems to me that you are tying yourself in knots here over a questionable idea. It seems like you are trying to protect yourself from malicious programmers ... not a very healthy work environment! And the cost for this is a performance robbing deep copy and using up twice the memory!
Why not just encapsulate the PDS in an object and pass that? Provide only allowed methods or, at worst, a secret key or something that opens disallowed methods. Not only will you avoid passing the DS at all, but you can provide any kind of security or control that you need to fit the situation.
I might be missing something obviously here but as far that I knew the only way you can make an output parameter to be passed by reference is to declare it as reference-only in the caller and specify bind on both the method definition and on method call... otherwise the output dataset will be by value (deep-copy) regardless of whether or not by-reference was used by the caller... at least trying that on an already old 10.1C shows the datasets are different (check unique-id on them and you'll see, no changes applied to the returned dataset will affect the one in the callee).
tamhas wrote:
Guillaume, it seems to me that you are tying yourself in knots here over a questionable idea. It seems like you are trying to protect yourself from malicious programmers ... not a very healthy work environment! And the cost for this is a performance robbing deep copy and using up twice the memory!
Why not just encapsulate the PDS in an object and pass that? Provide only allowed methods or, at worst, a secret key or something that opens disallowed methods. Not only will you avoid passing the DS at all, but you can provide any kind of security or control that you need to fit the situation.
This is exactly what I'm doing, a class holding its data using an internal PDS with all its logic dealing with the PDS.
However, at some point, another class need to iterate over the list of items contained in the first class (readonly access), and perform some other logic for each items.
And I would prefer not to move this "external" logic inside the first class to keep concerns separated since it is unrelated to the first class responsibility.
In .Net, I would expose an IEnumerable and be done.
I thought the simplest way to deal with this in ABL would have been to allow the caller to get a copy of the PDS so he can FOR EACH over it.
marianedu wrote:
I might be missing something obviously here but as far that I knew the only way you can make an output parameter to be passed by reference is to declare it as reference-only in the caller and specify bind on both the method definition and on method call... otherwise the output dataset will be by value (deep-copy) regardless of whether or not by-reference was used by the caller... at least trying that on an already old 10.1C shows the datasets are different (check unique-id on them and you'll see, no changes applied to the returned dataset will affect the one in the callee).
My error.
You are right, BY-REFERENCE would not expose the encapsulated dataset.
Instead it would "inject" the caller's dataset into the called class, for the duration of the method call.
Thus on a method defined like this, it would always return the dataset that was passed in.
METHOD PUBLIC VOID GetDSCopy(OUTPUT DATASET ds):
END METHOD.
However, it is still possible for the caller to use BIND and gain access to the internal dataset:
myObj:GetDSCopy(OUTPUT DATASET-HANDLE hds BIND)
So basically, I'm looking for a way to define method GetDSCopy() that would:
- never allow the caller to access internal dataset
- incur a single deep copy
How is iterating over the dataset not a part of the subject matter of basic navigation of the dataset?
Most extreme case, return a new class which is a read-only encapsulated version of the dataset, but the same dataset ... but you are going to have to convince me that makes sense compared to just providing the navigation in the same class.
guilmori wrote:
METHOD PUBLIC VOID GetDSCopy(OUTPUT DATASET ds):
END METHOD.
However, it is still possible for the caller to use BIND and gain access to the internal dataset:
myObj:GetDSCopy(OUTPUT DATASET-HANDLE hds BIND)
not unless you use bind in your method definition as well, like this...
method public void getDsCopy (output dataset ds bind):
end.
marianedu wrote:
not unless you use bind in your method definition as well, like this...
method public void getDsCopy (output dataset ds bind):
end.
Not true. BIND can be specified in the caller only, not required in the called method definition.
"can be specified" does not mean that it alone means a pass by reference.
Marian is saying that unless both sides set up for a pass by reference, one gets pass by value, aka a deep copy. The two are unique to the object.
Of course, I think pass by value is objectionable and you shouldn't be passing it in the first place, but I don't think you actually have a problem here.
guilmori wrote:
Not true. BIND can be specified in the caller only, not required in the called method definition.
sure it can, it compile just fine but... have you tried that?
if you try to bound to a dataset and the callee doen't allow that (by using bind option in method definition) you should get the following error...
If a caller or called REFERENCE-ONLY parameter
will become BOUND to its opposite as a result of the
call, then the BIND keyword must be supplied on both the caller
invocation and on the called parameter definition. (13161)
tamhas wrote:
Of course, I think pass by value is objectionable and you shouldn't be passing it in the first place, but I don't think you actually have a problem here.
agreed, something like a collection (in fact an iterator over it) would be preffered and IEnumerable was mentioned before... but this could work for a particular temp-table not the whole dataset
marianedu wrote:
guilmori wrote:
Not true. BIND can be specified in the caller only, not required in the called method definition.
sure it can, it compile just fine but... have you tried that?
if you try to bound to a dataset and the callee doen't allow that (by using bind option in method definition) you should get the following error...
If a caller or called REFERENCE-ONLY parameter
will become BOUND to its opposite as a result of the
call, then the BIND keyword must be supplied on both the caller
invocation and on the called parameter definition. (13161)
Hmm.. works fine for me.
What test did you do ?
/* Test.p */
DEF VAR o AS class1.
DEF VAR hds AS HANDLE.
o = NEW Class1().
o:GetDSCopy(OUTPUT DATASET-HANDLE hds bind).
MESSAGE "caller" hds hds:UNIQUE-ID
VIEW-AS ALERT-BOX.
o:DisplayData().
hds:GET-BUFFER-HANDLE(1):FIND-FIRST("").DO TRANSACTION:
hds:GET-BUFFER-HANDLE(1):BUFFER-FIELD("f1"):BUFFER-VALUE = "caller changed data".END.
o:DisplayData()./* Class1.cls
CLASS class1:
DEF TEMP-TABLE tt
FIELD f1 AS CHAR.
DEF DATASET ds FOR tt.
CONSTRUCTOR PUBLIC class1 ( ):
DO TRANSACTION:
CREATE tt.
ASSIGN tt.f1 = "callee data".
END.
MESSAGE "callee constructor" DATASET ds:HANDLE DATASET ds:unique-id
VIEW-AS ALERT-BOX.
END CONSTRUCTOR.
METHOD PUBLIC VOID GetDSCopy(OUTPUT DATASET ds):
MESSAGE "callee method" DATASET ds:HANDLE DATASET ds:unique-id
VIEW-AS ALERT-BOX.
END METHOD.
METHOD PUBLIC VOID DisplayData():
FOR EACH tt:
MESSAGE tt.f1 VIEW-AS ALERT-BOX.
END.
END METHOD.
END CLASS.
huh, definitively works and imho this is a bug...
the way I've tried was using a dataset parameter not a dataset-handle, in that case you need to define the dataset as reference-only in caller and bind need to be specified on both ends otherwise you get that error... bref, it does look inconsistent and the way you tried does shows it violates the encapsulation
define temp-table tt reference-only
field f1 as char.
define dataset ds reference-only for tt.
def var o as Class1.
o = new Class1().
o:getDSCopy (output dataset ds bind).