TableParameter.java

/*
** Module   : TableParameter.java
** Abstract : Parameter support for TABLE and TABLE-HANDLE.
**
** Copyright (c) 2013-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
** 001 SVL 20130226 Created initial version.
** 002 SVL 20140106 Added table handle variable.
** 003 CA  20140110 Added support for usage from java side (when there is no DMO passed).
** 004 SVL 20140414 Added support for remote parameters.
** 005 SVL 20140611 Do not allow tableHandle to be null.
** 006 SVL 20140709 Added parameterIndex.
** 007 CA  20171030 Added support for BY-REFERENCE table parameter mode.
** 008 OM  20190510 Added error handling when incorrect handle is passed to constructor.
**     OM  20190523 Unified parameter options handling for TempTables and DataSet params.
** 009 ECF 20190823 Temporary workaround for regression with output parameters.
**                  TODO: proper fix needed.
** 010 HC  20190719 Fixed unexpected error when passing TABLE-HANDLE as output parameter.
**     CA  20190731 Create a dynamic table from a REMOTE table, when INPUT is used.
**     CA  20190826 If a table-handle parameter is for a temp-table, then set the 'table'
**                  Java field for the default-buffer, and not the temp-table.
** 011 CA  20200427 Allow a TABLE to be read from JSON argument, in case of a remote REST call.
** 012 OM  20210106 Dropped 2nd parameter of TEMP-TABLE-PREPARE.
**     SVL 20210517 Added isValid().
**     SVL 20210517 Fixed isValid() for the case of a result set.
**     CA  20211125 Fixed an issue when the TABLE-HANDLE handle is not unknown, but invalid - getTable() must
**                  return null.
**     CA  20220901 Refactored scope notification support: ScopeableFactory was removed, and the registration 
**                  is now specific to each type of scopeable.  For each case, the block will be registered 
**                  for scope support (for that particular scopeable) only when the scopeable is 'active' 
**                  (i.e. unnamed streams or accumulators are used).  This allows a lazy registration of 
**                  scopeables, to avoid the unnecessary overhead of processing all the scopeables for each
**                  and every block.
**     GES 20210430 Reworked to enable subclasses which reduce parameter processing from common use cases.
**     CA  20220601 Any TableParameter or DataSetParameter instances at a legacy OO method or constructor call
**                  must be transformed to their mode'ed version, depending on the INPUT, OUTPUT or 
**                  INPUT-OUTPUT mode, and the DATASET-HANDLE, DATASET, TABLE-HANDLE or TABLE versions.
**     CA  20220727 Fixed OO dataset/table parameters (at the method definition and method call) when there is
**                  an APPEND option.
**     IAS 20221029 Added SCHEMA-MARSHAL attribute support.
**     IAS 20221108 Replaced static 'TempTableBuilder.createRemoteTable' with a regular one.
**     IAS 20221111 Fixed 'schemaMarshalLevel' support.
**     CA  20230116 Avoid using handle.unwrap, handle.getReference or other BDT usage from within FWD runtime.
** 013 CA  20240409 Allow the parameter mode to be set (useful for CALL parameter).
*/

/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
** 
** Additional terms under GNU Affero GPL version 3 section 7:
** 
**   Under Section 7 of the GNU Affero GPL version 3, the following additional
**   terms apply to the works covered under the License.  These additional terms
**   are non-permissive additional terms allowed under Section 7 of the GNU
**   Affero GPL version 3 and may not be removed by you.
** 
**   0. Attribution Requirement.
** 
**     You must preserve all legal notices or author attributions in the covered
**     work or Appropriate Legal Notices displayed by works containing the covered
**     work.  You may not remove from the covered work any author or developer
**     credit already included within the covered work.
** 
**   1. No License To Use Trademarks.
** 
**     This license does not grant any license or rights to use the trademarks
**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
**     of Golden Code Development Corporation. You are not authorized to use the
**     name Golden Code, FWD, or the names of any author or contributor, for
**     publicity purposes without written authorization.
** 
**   2. No Misrepresentation of Affiliation.
** 
**     You may not represent yourself as Golden Code Development Corporation or FWD.
** 
**     You may not represent yourself for publicity purposes as associated with
**     Golden Code Development Corporation, FWD, or any author or contributor to
**     the covered work, without written authorization.
** 
**   3. No Misrepresentation of Source or Origin.
** 
**     You may not represent the covered work as solely your work.  All modified
**     versions of the covered work must be marked in a reasonable way to make it
**     clear that the modified work is not originating from Golden Code Development
**     Corporation or FWD.  All modified versions must contain the notices of
**     attribution required in this license.
*/

package com.goldencode.p2j.util;

import java.util.*;

import com.goldencode.p2j.persist.*;

/**
 * Represents a table or table-handle parameter, the mode (INPUT, INPUT-OUTPUT or OUTPUT) and any table
 * parameter options (APPEND, BY-VALUE, BIND or BY-REFERENCE).  Allows subclasses to specialize this
 * behavior.  A core feature is to implement interchangeability between TABLE and TABLE-HANDLE parameters
 * (in the 4GL you can pass a TABLE parameter to a procedure that takes TABLE-HANDLE and vice versa).
 * <p>
 * The parameter mode can be implemented in this class or it can be overridden in a subclass.  If it is
 * implemented here, then the input and output flags must be passed to the constructor.  If the subclass
 * overrides the {@link #isInputMode} and {@link #isOutputMode} methods then the input and output boolean
 * members will be ignored.  This is sub-optimal but it was done deliberately to enable the base class to
 * be used as a full replacement for any of the subclasses.
 */
public class TableParameter
{
   /** Buffer of the wrapped table. */
   private Temporary table;
   
   /** Table handle. */
   private final handle tableHandle;
   
   /** Table parameter option (APPEND, BY-VALUE, BIND or BY-REFERENCE). */
   private EnumSet<ParameterOption> options;
   
   /** Calling- or called-side result set (for remote parameters). */
   private TableWrapper resultSet = null;
   
   /** Determines if this is a remote (passed to appserver) parameter. */
   private boolean remoteParameter = false;
   
   /** {@code true} if the parameter mode is INPUT or INPUT-OUTPUT. */
   private boolean input = false;
   
   /** {@code true} if the parameter mode is OUTPUT or INPUT-OUTPUT. */
   private boolean output = false;
   
   /** Table SCHEMA-MARSHAL attribute value encodes as int */ 
   private int schemaMarshalLevel;
   
   /** Flag indicating this is a TABLE-HANDLE argument. */
   private boolean isTableHandle = false;
   
   /**
    * 1-based index of the table parameter (in declaration of the function). <code>0</code>
    * if it is not defined.
    */
   private int parameterIndex = 0;
   
   /**
    * Create an instance using a buffer.
    *
    * @param    table
    *           Buffer of the wrapped table.
    */
   public TableParameter(Temporary table)
   {
      this(table, ParameterOption.NONE);
   }
   
   /**
    * Create an instance using a buffer.
    *
    * @param    table
    *           Buffer of the wrapped table.
    * @param    option
    *           Table parameter option (APPEND, BY-VALUE, BIND or BY-REFERENCE).
    */
   public TableParameter(Temporary table, ParameterOption option)
   {
      this(table, option, false, false);
   }
   
   /**
    * Create an instance using a buffer.
    *
    * @param    table
    *           Buffer of the wrapped table.
    * @param    options
    *           Table parameter options (APPEND, BY-VALUE, BIND or BY-REFERENCE).
    */
   public TableParameter(Temporary table, EnumSet<ParameterOption> options)
   {
      this(table, options, false, false);
   }
   
   /**
    * Create an instance using a buffer.
    *
    * @param    table
    *           Buffer of the wrapped table.
    * @param    option
    *           Table parameter option (APPEND, BY-VALUE, BIND or BY-REFERENCE).
    * @param    input
    *           {@code true} if the parameter mode is INPUT or INPUT-OUTPUT.
    * @param    output
    *           {@code true} if the parameter mode is OUTPUT or INPUT-OUTPUT.
    */
   public TableParameter(Temporary table, ParameterOption option, boolean input, boolean output)
   {
      this(table, EnumSet.of(option), input, output);
   }
   
   /**
    * Create an instance using a buffer.
    *
    * @param    table
    *           Buffer of the wrapped table.
    * @param    options
    *           Table parameter options (APPEND, BY-VALUE, BIND or BY-REFERENCE).
    * @param    input
    *           {@code true} if the parameter mode is INPUT or INPUT-OUTPUT.
    * @param    output
    *           {@code true} if the parameter mode is OUTPUT or INPUT-OUTPUT.
    */
   public TableParameter(Temporary table, EnumSet<ParameterOption> options, boolean input, boolean output)
   {
      BufferManager.registerScopeable(null);

      this.table       = (Temporary) ((BufferImpl) table).ref();
      this.tableHandle = ((BufferReference) table).buffer().tableHandle();
      this.options     = options;
      this.input       = input;
      this.output      = output;
      this.schemaMarshalLevel = ((AbstractTempTable)this.tableHandle.getResource()).getSchemaMarshalLevel();
   }
   
   /**
    * Create an instance using a table handle.
    *
    * @param    tableHandle
    *           Handle to the wrapped table.
    */
   public TableParameter(handle tableHandle)
   {
      this(tableHandle, ParameterOption.NONE);
   }
   
   /**
    * Create an instance using a table handle.
    *
    * @param    tableHandle
    *           Handle to the wrapped table.
    * @param    option
    *           Table parameter option (APPEND, BY-VALUE, BIND or BY-REFERENCE).
    */
   public TableParameter(handle tableHandle, ParameterOption option)
   {
      this(tableHandle, option, false, false);
   }
   
   /**
    * Create an instance using a table handle.
    *
    * @param    tableHandle
    *           Handle to the wrapped table.
    * @param    options
    *           Table parameter options (APPEND, BY-VALUE, BIND or BY-REFERENCE).
    */
   public TableParameter(handle tableHandle, EnumSet<ParameterOption> options)
   {
      this(tableHandle, options, false, false);
   }
   
   /**
    * Create an instance using a table handle.
    *
    * @param    tableHandle
    *           Handle to the wrapped table.
    * @param    option
    *           Table parameter option (APPEND, BY-VALUE, BIND or BY-REFERENCE).
    * @param    input
    *           {@code true} if the parameter mode is INPUT or INPUT-OUTPUT.
    * @param    output
    *           {@code true} if the parameter mode is OUTPUT or INPUT-OUTPUT.
    */
   public TableParameter(handle tableHandle, ParameterOption option, boolean input, boolean output)
   {
      this(tableHandle, EnumSet.of(option), input, output);
   }
   
   /**
    * Create an instance using a table handle.
    *
    * @param    tableHandle
    *           Handle to the wrapped table.
    * @param    options
    *           Table parameter options (APPEND, BY-VALUE, BIND or BY-REFERENCE).
    * @param    input
    *           {@code true} if the parameter mode is INPUT or INPUT-OUTPUT.
    * @param    output
    *           {@code true} if the parameter mode is OUTPUT or INPUT-OUTPUT.
    */
   public TableParameter(handle tableHandle, EnumSet<ParameterOption> options, boolean input, boolean output)
   {
      BufferManager.registerScopeable(null);

      WrappedResource res = tableHandle.getResource();
      isTableHandle = true;
      
      if (tableHandle._isValid())
      {
         if (res instanceof TempTable)
         {
            this.table = TemporaryBuffer.getDefaultBuffer(tableHandle);
         }
         else if (res instanceof Temporary)
         {
            this.table = (Temporary) res;
         }
         else
         {
            String msg1 = "TEMP-TABLE handle parameter requires valid and prepared handle";
            String msg2 = "Error attempting to push run time parameters onto the stack";
            ErrorManager.recordOrThrowError(new int[] { 9068, 984 },
                                            new String[] { msg1, msg2 }, false);
         }
      }
      else
      {
         this.table = null;
      }
      
      this.tableHandle = tableHandle;
      this.options     = options;
      this.input       = false;
      this.output      = false;
   }
   
   /**
    * Create an instance using a result set (for remote parameters).
    *
    * @param    resultSet
    *           Result set containing input data.
    */
   public TableParameter(TableWrapper resultSet)
   {
      this(resultSet, ParameterOption.NONE);
   }
   
   /**
    * Create an instance using a result set (for remote parameters).
    *
    * @param    resultSet
    *           Result set containing input data.
    * @param    option
    *           Table parameter option (APPEND, BY-VALUE, BIND or BY-REFERENCE).
    */
   public TableParameter(TableWrapper resultSet, ParameterOption option)
   {
      this(resultSet, option, false, false);
   }
   
   /**
    * Create an instance using a result set (for remote parameters).
    *
    * @param    resultSet
    *           Result set containing input data.
    * @param    option
    *           Table parameter option (APPEND, BY-VALUE, BIND or BY-REFERENCE).
    * @param    input
    *           {@code true} if the parameter mode is INPUT or INPUT-OUTPUT.
    * @param    output
    *           {@code true} if the parameter mode is OUTPUT or INPUT-OUTPUT.
    */
   public TableParameter(TableWrapper resultSet, ParameterOption option, boolean input, boolean output)
   {
      this(resultSet, EnumSet.of(option), input, output);
   }
   
   /**
    * Create an instance using a result set (for remote parameters).
    *
    * @param    resultSet
    *           Result set containing input data.
    * @param    options
    *           Table parameter options (APPEND, BY-VALUE, BIND or BY-REFERENCE).
    * @param    input
    *           {@code true} if the parameter mode is INPUT or INPUT-OUTPUT.
    * @param    output
    *           {@code true} if the parameter mode is OUTPUT or INPUT-OUTPUT.
    */
   public TableParameter(TableWrapper resultSet, EnumSet<ParameterOption> options, boolean input, boolean output)
   {
      BufferManager.registerScopeable(null);

      this.table           = null;
      this.tableHandle     = new handle();
      this.resultSet       = resultSet;
      this.options         = options;
      this.remoteParameter = true;
      this.input           = input;
      this.output          = output;
      
      // if INPUT, create a dynamic table
      if (resultSet.isInput() && !resultSet.isFromJson() && resultSet.isAfterTable())
      {
         BufferImpl buff = new TempTableBuilder().createRemoteTable(resultSet.getTableName(),
                                                              resultSet.propertyIterator(),
                                                              resultSet.getIndexes(),
                                                              resultSet.getXmlns(),
                                                              resultSet.getXmlPrefix(),
                                                              resultSet.getSchemaMarshalLevel());
         
         TemporaryBuffer.insertAllRows(resultSet,
                                       (Temporary) buff,
                                       resultSet.isAppend(),
                                       TemporaryBuffer.CopyTableMode.INPUT_PARAM_MODE,
                                       getParameterIndex());
         
         this.table = (Temporary) buff;
      }
   }
   
   /**
    * Get the state of the {@link #isTableHandle} flag.
    * 
    * @return   See above.
    */
   public boolean isTableHandle()
   {
      return isTableHandle;
   }
   
   /**
    * Determines if this parameter is INPUT or INPUT-OUTPUT.
    *
    * @return   {@code true} if this is INPUT or INPUT-OUTPUT.
    */
   public boolean isInputMode()
   {
      return input;
   }

   /**
    * Set the flag for whether this parameter is INPUT or INPUT-OUTPUT.
    *
    * @param    input
    *           {@code true} if this is INPUT or INPUT-OUTPUT.
    */
   public void setInputMode(boolean input)
   {
      this.input = input;
   }

   /**
    * Determines if this parameter is OUTPUT or INPUT-OUTPUT.
    *
    * @return   {@code true} if this is OUTPUT or INPUT-OUTPUT.
    */
   public boolean isOutputMode()
   {
      return output;
   }
   
   /**
    * Set the flag for whether this parameter is OUTPUT or INPUT-OUTPUT.
    *
    * @param    output
    *           {@code true} if this is OUTPUT or INPUT-OUTPUT.
    */
   public void setOutputMode(boolean output)
   {
      this.output = output;
   }

   /**
    * Determines if this is a remote parameter.
    *
    * @return   {@code true} if this is a remote parameter, {@code false} if this is a
    *           local parameter.
    */
   public boolean isRemoteParameter()
   {
      return remoteParameter;
   }

   /**
    * Get the result set (for remote parameters).
    *
    * @return   Result set.
    */
   public TableWrapper getResultSet()
   {
      return resultSet;
   }

   /**
    * Set the result set (for remote parameters).
    *
    * @param    resultSet
    *           Result set, normally containing output data.
    */
   public void setResultSet(TableWrapper resultSet)
   {
      this.resultSet = resultSet;
   }

   /**
    * Get the table-handle which references the wrapped table.
    *
    * @return   The table-handle (contains the wrapped table).
    */
   public handle getTableHandle()
   {
      return tableHandle;
   }

   /**
    * Set the parameter's table.
    * 
    * @param    table
    *           The table
    */
   public void setTable(Temporary table)
   {
      this.table = table;
   }
   
   /**
    * Get the buffer of the wrapped table.
    *
    * @return   buffer of the wrapped table.
    */
   public Temporary getTable()
   {
      if (table == null)
      {
         if (tableHandle._isValid())
         {
            AbstractTempTable tt = (AbstractTempTable) tableHandle.getResource();
            return (tt == null) ? null : (Temporary) tt.defaultBufferHandleNative();
         }
         else
         {
            return null;
         }
      }
      else
      {
         return table;
      }
   }

   /**
    * Determines if the APPEND option was specified on the calling side.
    *
    * @return    {@code true} if the APPEND option was specified on the calling side.
    */
   public boolean isAppend()
   {
      return options.contains(ParameterOption.APPEND);
   }
   
   /**
    * Determines if the {@code BY-VALUE} option was specified on the calling side.
    *
    * @return   {@code true} if the {@code BY-VALUE} option was specified on the calling side.
    */
   public boolean isByValue()
   {
      return options.contains(ParameterOption.BY_VALUE);
   }
   
   /**
    * Determines if the {@code BIND} option was specified on the calling side.
    *
    * @return   {@code true} if the {@code BIND} option was specified on the calling side.
    */
   public boolean isBind()
   {
      return options.contains(ParameterOption.BIND);
   }
   
   /**
    * Determines if the {@code BY-REFERENCE} option was specified on the calling side.
    *
    * @return   {@code true} if the {@code BY-REFERENCE} option was specified on the calling side.
    */
   public boolean isByReference()
   {
      return options.contains(ParameterOption.BY_REFERENCE);
   }
   
   /**
    * Get the table parameter options for this argument.
    * 
    * @return   The table parameter options.
    */
   public EnumSet<ParameterOption> getParameterOptions()
   {
      return options;
   }

   /**
    * Set the table parameter options for this argument.
    * 
    * @param    options
    *           The table parameter options.
    */
   public void setParameterOptions(EnumSet<ParameterOption> options)
   {
      this.options = options;
   }
   
   /**
    * Get 1-based index of the table parameter (in declaration of the function).
    *
    * @return    1-based index of the table parameter or <code>0</code> if it is not defined.
    */
   public int getParameterIndex()
   {
      return parameterIndex;
   }
   
   /** Get encoded table SCHEMA-MARSHAL attribute value.
    * 
    * @return encoded table SCHEMA-MARSHAL attribute value.
    */
   public int getSchemaMarshalLevel()
   {
      return schemaMarshalLevel;
   }

   /**
    * Set 1-based index of the table parameter (in declaration of the function).
    *
    * @param    parameterIndex
    *           1-based index of the table parameter.
    */
   public void setParameterIndex(int parameterIndex)
   {
      this.parameterIndex = parameterIndex;
   }

   /**
    * Return <code>true</code> if the table parameter is valid. It can be invalid, say, if an unknown table
    * handle has been assigned to it.
    *
    * @return <code>true</code> if the table parameter is valid.
    */
   public boolean isValid()
   {
      return table != null || (tableHandle != null && tableHandle._isValid()) || resultSet != null;
   }
}