Project

General

Profile

Bug #2535

enhancing datatype conversion functions

Added by Ovidiu Maxiniuc about 9 years ago. Updated over 7 years ago.

Status:
Closed
Priority:
Normal
Start date:
Due date:
% Done:

100%

billable:
No
vendor_id:
GCD
case_num:

integer-function-1.p Magnifier (975 Bytes) Ovidiu Maxiniuc, 03/10/2015 01:35 PM

om_upd20150311a.zip (13.3 KB) Ovidiu Maxiniuc, 03/11/2015 02:56 PM

om_upd20150313b.zip (12.3 KB) Ovidiu Maxiniuc, 03/13/2015 05:33 PM

History

#1 Updated by Ovidiu Maxiniuc about 9 years ago

I just discovered that we don't cover all the conversions between datatypes. We need to implement additional constructors for classes extending BDT to allow conversion from FieldReference objects. Here are more details.
Consider the following code (based on code from a customer application):

define temp-table tt1
    field f1-dte as date
    field f2-int as integer
    field f3-dte as date
    field f4-chr as char
    field f5-dec as decimal.

define temp-table tt2
    field f1-dte as date
    field f2-int as integer
    field f3-int as integer
    field f4-chr as character.

FOR 
    EACH tt1 
       WHERE  tt1.f1-dte = 02/05/05 
         AND  tt1.f2-int = 10 
         AND  tt1.f3-dte = 04/04/05 
         AND  tt1.f4-chr = 'ABC' NO-LOCK, 
    FIRST  tt2 
       WHERE  tt2.f1-dte = 02/05/05 
         AND  tt2.f2-int = 10 
         AND  tt2.f4-chr = 'ABC' 
         AND  (tt1.f5-dec = 0 
            OR tt2.f3-int = INTEGER(tt1.f5-dec))

It will be converted to
query0 = new CompoundQuery(false, false);
query0.addComponent(new AdaptiveQuery(tt1, "tt1.f1Dte = '2005-02-05' and tt1.f2Int = 10 and tt1.f3Dte = '2005-04-04' and upper(tt1.f4Chr) = 'ABC'", null, "tt1.id asc", LockType.NONE));
query0.addComponent(new RandomAccessQuery(tt2, "tt2.f1Dte = '2005-02-05' and tt2.f2Int = 10 and upper(tt2.f4Chr) = 'ABC'", whereExpr0, "tt2.id asc"), QueryConstants.FIRST);

where
    WhereExpression whereExpr0 = new WhereExpression() {
       public Object[] getSubstitutions() {
          return new Object[] {
             new IntegerExpression() {
                public integer execute() {
                    return new integer(new FieldReference(tt1, "f5Dec"));
                }
             }
          };
       }

This is not compilable because we don't have such integer constructor at this moment. The issue was not visible by the compiler because this query is generated at runtime and only the classes that handle dynamic queries will encounter it.

The implementation is as simple as adding add a new constructor with at least a single Accessor parameter, because using FieldReference would add an unwanted reference to persistence. A second optional parameter of type character can be used to specify the format to be used during the conversion.
The affected datatypes are integer, int64, decimal, date, datetime, datetimetz and logical.
The STRING function is implemented as a static method valueOf in character class and it also must be overloaded.

#2 Updated by Eric Faulhaber about 9 years ago

  • Target version set to Milestone 11

#3 Updated by Ovidiu Maxiniuc about 9 years ago

Eric,

I have an issue regarding the implementation of these methods. At first I implemented as simple as this:

   public integer(Accessor fieldRef)
   {
      assign(fieldRef.get(), false);
   }

Taking into account that these methods/c'tors will only be called with a in-place newly constructed FieldReference, the argument will never be null. But the problem occurs when the fieldRef refers a buffer that is not yet loaded with a record from database at the time the loop is declared. The P2J will print No tt1 record is available in message are and the procedure will end (see the attached testcase).

This happens because the RandomAccessQuery c'tor will try to resolveArgs() that will invoke the resolveArgs() of WhereExpression, whereExpr0 that will getSubstitutions() and resolve each item of the array in order to keep them in currentArgs. At this moment, the new c'tor will attempt to access the field referenced by fieldRef and an noted above, the buffer is not yet available (or alternatively, a record from an old query may be referred).
This happens long before query0.iterate(); has the chance to actually load the correct record form database.

To prevent this, I tried to rewrite the constructor as follows:

   public integer(Accessor fieldRef)
   {
      BaseDataType bdt;
      boolean ignore = ErrorManager.isIgnore();
      ErrorManager.setIgnore(true);
      try
      {
         bdt = fieldRef.get();
      }
      finally
      {
         // restore ignore value
         ErrorManager.setIgnore(ignore);
      }
      assign(bdt, false);
   }

Ie. to wrap the fieldRef.get() in an error-ignored temporary bracketing so that the error 91, No tt1 record is available will not raised.

This will work just fine for my toy-procedure, but I am not sure that it is correct to ignore this error every time. In fact I feel that is totally wrong but at this moment I don't have other solution except for delaying the RandomAccessQuery to resolveArgs() until .. don't know when.

#4 Updated by Eric Faulhaber about 9 years ago

  • Assignee set to Ovidiu Maxiniuc

You are right: we don't want to mask this error always, because there may be times when it represents a legitimate error.

It seems the problem here is that this conversion is simply broken:

public integer execute() {
   return new integer(new FieldReference(tt1, "f5Dec"));
}

The whole point of using FieldReference in these situations is to defer evaluation until the WhereExpression base class decides it is safe to resolve the value, which in the case of FieldReference and other Resolvable parameters doesn't happen until WhereExpression.evaluate. By wrapping this in a call to an integer constructor in the generated getSubstitutions method, we have broken that deferral, as you've determined in your debugging.

Adding the proposed new BDT subclass c'tors won't fix this, unless they preserve the deferred nature of the Accessor passed into them. But we don't want to fundamentally change these data types to deal with this edge case.

I think the most correct solution is to refactor the conversion of the WhereExpression to look something like this:

WhereExpression whereExpr0 = new WhereExpression()
{
   public Object[] getSubstitutions()
   {
      return new Object[]
      {
         new FieldReference(tt1, "f5Dec");
      };
   }
   ...
}

...and have the integer c'tor (the existing one which accepts decimal) called in the converted evaluate method.

Please post what that generated evaluate method looks like currently, and let's figure out how it needs to look to support this semantic correctly.

#5 Updated by Ovidiu Maxiniuc about 9 years ago

  • Assignee deleted (Ovidiu Maxiniuc)

The generated evaluate method for WhereExpression whereExpr0 looks currently like this:

public logical evaluate(final BaseDataType[] args)
{
   return or(isEqual(tt1.getF5Dec(), 0), new LogicalExpression()
   {
      public logical execute()
      {
         return isEqual(tt2.getF3Int(), (integer) args[0]);
      }
   });
}

I guess we should keep the new constructor and replace the return line like:
return isEqual(tt2.getF3Int(), new integer(args[0]);

#6 Updated by Ovidiu Maxiniuc about 9 years ago

  • Assignee set to Ovidiu Maxiniuc

#7 Updated by Eric Faulhaber about 9 years ago

Ovidiu Maxiniuc wrote:

I guess we should keep the new constructor and replace the return line like:

return isEqual(tt2.getF3Int(), new integer(args[0]);

I think this proposed conversion is correct, but we don't need the new constructors. Each data type should already have a c'tor that accepts BaseDataType (please confirm this). Assuming we make the getSubstitutions conversion change above, args[0] should already be a decimal value by the time it is passed into the generated evaluate(BDT[]) method. It will have been obtained by resolving the FieldReference object returned by getSubstitutions (see WhereExpression.evaluate(), specifically line 197).

#8 Updated by Eric Faulhaber about 9 years ago

  • Project changed from Base Language to Database

#9 Updated by Ovidiu Maxiniuc about 9 years ago

Yes, this is correct, the new constructors are not really needed.
I checked all datatypes classes and they all have a c'tor that accepts BaseDataType.

The fix for moving the field evaluation along with the function that use it as parameter is done by changing the evalLib("function_calls") or from line 196 of annotations/where_clause_post2.rules. I am working to rewrite this to something that can identify subtrees functions calls that don't contain references to fields that are not part of the current buffer. These subtrees must be kept in getSubstitutions().
If the subtree contains references to this kind of buffers, they are moved to evaluate() method and only the field references will get an entry in the getSubstitutions() returned array.

If the the tree would have already been processed some annotation could be in handy to identify these cases. But in walk-rules stage I must do an iteration of the subtree to detect non current-buffer field references.

#10 Updated by Ovidiu Maxiniuc about 9 years ago

With the attached update, queries like:

FOR 
   EACH tt1 
      WHERE  tt1.f1-dte = 02/05/05 
        AND  tt1.f2-int = 10 
        AND  tt1.f3-dte = 04/04/05 
        AND  tt1.f4-chr = "0'005" NO-LOCK, 
   FIRST  tt2 
      WHERE  tt2.f1-dte = 02/05/05 
        AND  tt2.f2-int = 10 
        AND  tt2.f4-chr = "0'005" 
        AND  (tt1.f5-dec = 0 
           OR tt2.f4-chr = STRING(INTEGER(tt1.f5-dec), "9'999")
           OR tt2.f3-int = INTEGER(k)
           OR tt2.f1-dte + 1 = today): 

gets converted to:
   WhereExpression whereExpr0 = new WhereExpression() {
      public Object[] getSubstitutions() {
         return new Object[] {
            new FieldReference(tt1, "f5Dec"),
            new IntegerExpression() {
               public integer execute() {
                  return new integer(k);
               }
            },
            new DateExpression() {
               public date execute() {
                  return date.today();
               }
            }
         };
      }

      public logical evaluate(final BaseDataType[] args) {
         return or(or(or(or(isEqual(tt1.getF5Dec(), 0), new LogicalExpression() {
            public logical execute() {
               return isEqual(tt2.getF4Chr(), valueOf(new integer((decimal) args[0]), "9'999"));
            }
         }), new LogicalExpression() {
            public logical execute() {
               return isEqual(tt2.getF3Int(), (integer) args[1]);
            }
         }), new LogicalExpression() {
            public logical execute() {
               return isEqual(tt2.getF3Int(), k);
            }
         }), new LogicalExpression() {
            public logical execute() {
               return isEqual(date.plusDays(tt2.getF1Dte(), 1), (date) args[2]);
            }
         });
      }
   };

query0 = new CompoundQuery(false, false);
query0.addComponent(new AdaptiveQuery(tt1, "tt1.f1Dte = '2005-02-05' and tt1.f2Int = 10 and tt1.f3Dte = '2005-04-04' and upper(tt1.f4Chr) = '0'005'", null, "tt1.id asc", LockType.NONE));
query0.addComponent(new RandomAccessQuery(tt2, "tt2.f1Dte = '2005-02-05' and tt2.f2Int = 10 and upper(tt2.f4Chr) = '0'005'", whereExpr0, "tt2.id asc"), QueryConstants.FIRST);

I am not sure I wrote the function call annotation in the right place (index_selection.rules) but it can be moved.
Please review.

#11 Updated by Eric Faulhaber about 9 years ago

Code review 0311a:

I am not sure I wrote the function call annotation in the right place (index_selection.rules) but it can be moved.

Right, I don't think index_selection.rules is the most appropriate place for these changes, since they are about query substitution parameters, not index selection. This processing probably best belongs in where_clause_prep.rules, which is similar in structure to index_selection.rules, and which is where we do a lot of annotation regarding query substitution parameters.

I think the rest of the update is OK, but it's hard to tell whether it will cause broader changes than what you intend. This should become clearer with conversion regression testing.

#12 Updated by Ovidiu Maxiniuc about 9 years ago

Logic that was incorrectly placed in index_select.rules moved to where_clause_prep.rules.

#13 Updated by Eric Faulhaber about 9 years ago

Code review 0313b:

Just thought of one edge case which might be problematic: consider a WHERE clause with a single, non-current-buffer field (would have to be a FIELD_LOGICAL) and nothing else. Wouldn't this mean the field's parent is the KW_WHERE node, which would be marked with the "where_not_subst" annotation? Perhaps a silly case which would better be refactored differently...but would it break the update?

Otherwise looks good. If it needs to be fixed to deal with the above case, please do so. Then, please conversion regression test. If the converted output is different, but looks correct, runtime regression test as well.

#14 Updated by Ovidiu Maxiniuc about 9 years ago

I guess the om_upd20150313b.zip update passed the regression testing. There were a few issues though:
  • in CTRL+C the clients were killed twice in ctrlc_11_session3 so there were semaphore issues in ctrlc_11_session1 and ctrlc_11_session4.
  • in main tc_job_clock_002 failed beside tc_job_002. This is a known-to fail test.

I will restart the runtime test and hope the tests not executed will have a second chance now.

#15 Updated by Ovidiu Maxiniuc about 9 years ago

I guess after the second run, update passed the test.

#16 Updated by Eric Faulhaber about 9 years ago

Please commit and distribute the update.

#17 Updated by Ovidiu Maxiniuc about 9 years ago

Update committed to bzr as revno 10815 and distributed by mail.

#18 Updated by Eric Faulhaber about 9 years ago

  • Status changed from WIP to Closed
  • % Done changed from 0 to 100

#19 Updated by Greg Shah over 7 years ago

  • Target version changed from Milestone 11 to Cleanup and Stablization for Server Features

Also available in: Atom PDF