Project

General

Profile

Feature #6379

buffer parameters can work as structural matches (without field name matching)

Added by Greg Shah almost 2 years ago. Updated almost 2 years ago.

Status:
New
Priority:
Normal
Assignee:
-
Target version:
-
Start date:
Due date:
% Done:

0%

billable:
No
vendor_id:
GCD
version:

Related issues

Related to Base Language - Feature #4350: method overload when they differ by a temp-table, dataset, buffer or object or extent or parameter modes WIP

History

#1 Updated by Greg Shah almost 2 years ago

  • Related to Feature #4350: method overload when they differ by a temp-table, dataset, buffer or object or extent or parameter modes added

#2 Updated by Greg Shah almost 2 years ago

Eric wrote some testcases and I extended them and checked them into the old testcases project (see uast/buffer_params/).

The testcases show that for internal procedures, you can pass a buffer for a table that is a structural match while not matching field names. A structural match is:

  • The structural comparison rules check the number of fields, type of each field, extent of each field and the order these fields appear. These are the only cases are detected as differences in structure.
  • The following things are are ignored: table names, table options, field names, field options (other than EXTENT), anything to do with indexes.

A procedure like this:

def temp-table tt1 field notf1 as int.

// BUFFER parameters may not be defined for a main .p, only for internal procedures, functions and methods. (13806)
// define parameter buffer myBuf for tt1.

procedure ip-buf:
   define parameter buffer myBuf for tt1.
end.

will be emitted as:

   public void ipBuf(final Tt1_2.Buf _mybuf)
   {
      Tt1_2_1.Buf mybuf = RecordBuffer.defineAlias(Tt1_2_1.Buf.class, _mybuf, "mybuf", "myBuf");

      internalProcedure(new Block((Body) () -> 
      {
         RecordBuffer.openScope(mybuf);
      }));
   }

Calls to that internal procedure like this:

def temp-table tt1 field f1 as int.
def temp-table tt2 field z1 as int.

...

run buffer_params/callee2.p persistent set h2.

run ip-buf in h2 (buffer tt1).

will emit like this:

   Tt2_1_1.Buf tt2 = TemporaryBuffer.define(Tt2_1_1.Buf.class, "tt2", "tt2", false);

   Tt1_1_1.Buf tt1 = TemporaryBuffer.define(Tt1_1_1.Buf.class, "tt1", "tt1", false);

...

         ControlFlowOps.invokePersistentSet("callee2.p", h2);
         ControlFlowOps.invokeInWithMode("ip-buf", h2, "B", tt1);

Notice that we are passing an instance of Tt1_1_1.Buf to the call that expects Tt1_2_1.Buf. This case works fine in the 4GL but it will generate a runtime exception in FWD. It will be something like this:

Caused by: java.lang.ClassCastException: com.goldencode.p2j.persist.$__Proxy6 cannot be cast to com.goldencode.testcases.dmo._temp.Tt1_2$Buf
        at com.goldencode.testcases.Callee2MethodAccess.invoke(Unknown Source)
        at com.goldencode.p2j.util.ControlFlowOps$InternalEntryCaller.invokeImpl(ControlFlowOps.java:8451)
        at com.goldencode.p2j.util.ControlFlowOps$InternalEntryCaller.invoke(ControlFlowOps.java:8422)
        at com.goldencode.p2j.util.ControlFlowOps.lambda$invokeImpl$10(ControlFlowOps.java:6896)
        at com.goldencode.p2j.util.ControlFlowOps.invokeImpl(ControlFlowOps.java:6911)
        at com.goldencode.p2j.util.ControlFlowOps.invoke(ControlFlowOps.java:4093)
        at com.goldencode.p2j.util.ControlFlowOps.invokeImpl(ControlFlowOps.java:6478)
        at com.goldencode.p2j.util.ControlFlowOps.invokeImpl(ControlFlowOps.java:6381)
        at com.goldencode.p2j.util.ControlFlowOps.invokeWithMode(ControlFlowOps.java:1005)
        at com.goldencode.p2j.util.ControlFlowOps.invokeWithMode(ControlFlowOps.java:987)
...

This is an issue for OO methods as well, except it is a compile problem instead of a runtime issue. See #3751-492 and the new testcases project in oo/params/buffer_external_caller.p:

    [javac] /home/ges/projects/testcases/src/com/goldencode/testcases/oo/params/BufferExternalCaller.java:50: error: no suitable method found for mtable(com.goldencode.testcases.dmo._temp.Tt1_1_1.Buf)
    [javac]          methodNum.assign(obj.ref().mtable(tt1));
    [javac]                                    ^
    [javac]     method Overloads.mtable(InputOutputTableParameter) is not applicable
    [javac]       (argument mismatch; com.goldencode.testcases.dmo._temp.Tt1_1_1.Buf cannot be converted to InputOutputTableParameter)
    [javac]     method Overloads.mtable(com.goldencode.testcases.dmo._temp.Tt1_2_1.Buf) is not applicable
    [javac]       (argument mismatch; com.goldencode.testcases.dmo._temp.Tt1_1_1.Buf cannot be converted to com.goldencode.testcases.dmo._temp.Tt1_2_1.Buf)
    [javac]     method Overloads.mtable(com.goldencode.testcases.dmo._temp.Tt2_2_1.Buf) is not applicable
    [javac]       (argument mismatch; com.goldencode.testcases.dmo._temp.Tt1_1_1.Buf cannot be converted to com.goldencode.testcases.dmo._temp.Tt2_2_1.Buf)
    [javac] /home/ges/projects/testcases/src/com/goldencode/testcases/oo/params/BufferExternalCaller.java:52: error: no suitable method found for mtable(com.goldencode.testcases.dmo._temp.Tt1_1_1.Buf)
    [javac]          methodNum.assign(obj.ref().mtable(b1));
    [javac]                                    ^
    [javac]     method Overloads.mtable(InputOutputTableParameter) is not applicable
    [javac]       (argument mismatch; com.goldencode.testcases.dmo._temp.Tt1_1_1.Buf cannot be converted to InputOutputTableParameter)
    [javac]     method Overloads.mtable(com.goldencode.testcases.dmo._temp.Tt1_2_1.Buf) is not applicable
    [javac]       (argument mismatch; com.goldencode.testcases.dmo._temp.Tt1_1_1.Buf cannot be converted to com.goldencode.testcases.dmo._temp.Tt1_2_1.Buf)
    [javac]     method Overloads.mtable(com.goldencode.testcases.dmo._temp.Tt2_2_1.Buf) is not applicable
    [javac]       (argument mismatch; com.goldencode.testcases.dmo._temp.Tt1_1_1.Buf cannot be converted to com.goldencode.testcases.dmo._temp.Tt2_2_1.Buf)

The problem here is that our type is too specific. We really need a generic structural type that matches both DMO types. This issue was found in #4350 but it is out of scope there and will be worked here instead.

#3 Updated by Constantin Asofiei almost 2 years ago

Greg Shah wrote:

The problem here is that our type is too specific. We really need a generic structural type that matches both DMO types. This issue was found in #4350 but it is out of scope there and will be worked here instead.

IIRC for temp-tables the same issue exist: the match is done how you described, using the field type and order, not their names.

And we solve this by switching the DMO proxy in RecordBuffer, mapping field names, etc, as in 4GL you will be able to access the argument buffer's fields using the parameter buffer's as the program was converted (so if the argument buffer has a field named barg.f1 and this matches the parameter's buffer field bparam.g1, using bparam.g1 you will actually read the barg.f1 field.

So I don't think the issue is at conversion, instead we need to fix this as we fixed it for the temp-table parameters.

#4 Updated by Constantin Asofiei almost 2 years ago

Actually, for temp-tables, we emit TableParameter and not the buffer. So this is why it can be handled at runtime.

For both the OO and non-OO buffer parameter cases, I think we need to switch from emitting directly the buffer DMO interface to emitting a BufferParameter. And #4350 needs to take care of solving the overload problem (as you will no longer be able to overload via the DMO interface).

I don't think it helps if we add a generic structural type that matches both DMO types. - if you set this at the parameter's type, how will it solve what I mentioned in previous note, as the parameter's buffer will still have static references to the field names, which must emit at the conversion as is.

#5 Updated by Greg Shah almost 2 years ago

I don't think it helps if we add a generic structural type that matches both DMO types. - if you set this at the parameter's type, how will it solve what I mentioned in previous note, as the parameter's buffer will still have static references to the field names, which must emit at the conversion as is.

My idea was that it would be unpacked as a concreate instance of the right DMO, but internally it would handle the mapping in some way.

On the other hand, I don't want to re-invent this at this time. I will say that the 4GL probably just handles this at a low level by positional copying of the structure itself and probably doesn't have the overhead or complexity that we have.

#6 Updated by Constantin Asofiei almost 2 years ago

Greg Shah wrote:

My idea was that it would be unpacked as a concreate instance of the right DMO, but internally it would handle the mapping in some way.

I don't think this can work, because the field names can differ.

#7 Updated by Greg Shah almost 2 years ago

The idea is that the super-interface would not care about names. It would have a field 1 that is the correct type and extent, a field 2 that is the correct type and extent and so forth. We'd just use generic names (f1, f2...) for the fields. The child interfaces would have specific getters/setters that have the right names. The parent interface could be the parameter type and you could pass one child interface for the other.

A the invocation handler or some other proxy could handle the mapping. Maybe we could do a fast copy based on the knowledge that the structure is the same, but I worry that the semantics of buffer processing (always input-output, triggers and so forth might need to fire properly) might mean that the fast copy is not possible.

#8 Updated by Constantin Asofiei almost 2 years ago

Greg Shah wrote:

The idea is that the super-interface would not care about names. It would have a field 1 that is the correct type and extent, a field 2 that is the correct type and extent and so forth. We'd just use generic names (f1, f2...) for the fields. The child interfaces would have specific getters/setters that have the right names. The parent interface could be the parameter type and you could pass one child interface for the other.

A the invocation handler or some other proxy could handle the mapping.

And the conversion will just have field1, field2, instead of 'cost', 'price', etc?

#9 Updated by Greg Shah almost 2 years ago

And the conversion will just have field1, field2, instead of 'cost', 'price', etc?

Not in the business logic. We would just initialize the proxy for an instance of the correct DMO using the passed in parent interface.

#10 Updated by Constantin Asofiei almost 2 years ago

Greg Shah wrote:

And the conversion will just have field1, field2, instead of 'cost', 'price', etc?

Not in the business logic. We would just initialize the proxy for an instance of the correct DMO using the passed in parent interface.

OK, so you would have the generic interface at the parameter, and create the 'real' buffer parameter in the method's code, right? So the 'real' buffer parameter can reference the fields by their hard-coded name.

#11 Updated by Greg Shah almost 2 years ago

Exactly.

#12 Updated by Greg Shah almost 2 years ago

It gives type-safety (as much as the 4GL allows) and also would allow overloading.

But it would require a good bit of change and I simply don't have the time to implement it now.

#13 Updated by Constantin Asofiei almost 2 years ago

Greg Shah wrote:

It gives type-safety (as much as the 4GL allows) and also would allow overloading.

Yes, this is a good solution. The runtime already has support for this behavior at temp-tables, via the TemporaryBuffer$ReferenceProxy.

OTOH, I don't see this conversion problem for OO in current projects, and if this appears at runtime, then it can be fixed via the ReferenceProxy pretty easy.

#14 Updated by Greg Shah almost 2 years ago

I don't see this conversion problem for OO in current projects, and if this appears at runtime, then it can be fixed via the ReferenceProxy pretty easy.

Yes. My plan was to defer this work for now.

Also available in: Atom PDF