DataSetParameter.java

/*
** Module   : DataSetParameter.java
** Abstract : Container for DataSet reference/handle and calling-side APPEND option.
**
** Copyright (c) 2019-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
** 001 OM  20190403 Created initial version.
** 002 OM  20190510 Added dataset runtime support. Changed to parameter passing option as enum
**                  which simplifies API and adds mutual exclusion to options.
**     HC  20190707 Implemented read(dataSet) and read(tempTable) methods of JsonObject legacy
**                  class.
** 003 CA  20190712 Temporarily added c'tors with a DataSetParameter - this is because conversion
**                  rules incorrectly emit a DataSetParameter for the variable handle, in case of
**                  DATASET-HANDLE argument.  This needs to be fixed in TRPL.
**     OM  20190716 Dropped changes in H003 after fixing TRPL. Recreate dataset from remote
**                  parameter (table included).
**     CA  20190802 TempTableBuilder.createRemoteTable is responsible to create a temp-table.
**     CA  20190826 Expose the original dataset passed by the caller, so that it can be used for 
**                  'inverse binding' (from callee to caller).
** 004 CA  20191203 Added toString().
** 005 CA  20200427 Allow a DATASET to be read from JSON argument, in case of a remote REST call.
** 006 OM  20210106 Dropped 2nd parameter of TEMP-TABLE-PREPARE.
**     CA  20210213 Fixed a NPE in createDataSet - the SIMPLE table was not processed.
**     OM  20210215 Created before table and linked to its peer/after table before populating.
**     SVL 20210614 Remote OUTPUT TABLE/DATASET-HANDLE parameters are treated like INPUT-OUTPUT parameters
**                  with no input data rows, which allows to access table structure if non-unknown
**                  table/dataset handle has been passed from the calling side.
**     CA  20210831 Fixed deserialization of a remote dataset which has data in the before-table(s).
**     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 20210501 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 20221108 Fixed support for SCHEMA-MARSHAL == NONE. Re-worked indexes (de)serialization.
**     IAS 20221111 Fixed support of relations' for SCHEMA-MARSHAL == NONE and issues with 
**                  processing 'schemaMarshalLevel', 'xmlNodeName', 'nsURI', and 'nsPrefix'. 
**     CA  20221116 Do not call setters for 'xmlNodeName', 'nsURI', and 'nsPrefix' if the value is null.
**     CA  20230116 Avoid using handle.unwrap, handle.getReference or other BDT usage from within FWD runtime.
** 007 OM  20231027 Added support for passing the ERROR attribute with the dataset.
** 008 CA  20240409 Allow the parameter mode to be set (useful for CALL parameter).
** 009 CA  20240502 When a BEFORE-TABLE is being built too, for a dynamic temp-table, the associated DMOs for
**                  both the before and after table must have the after/beforeTable annotations set.
** 010 OM  20240429 Being more lenient when processing the field pairs.
** 011 AS  20240808 Added originalDataSet type awareness.
** 012 DDF 20250211 Removed unused DataSetParameter(DataSetParameter) constructor.
*/

/*
** 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.persist;

import com.goldencode.p2j.util.*;

import java.util.*;

/**
 * Represents a dataset or dataset-handle parameter, the mode (INPUT, INPUT-OUTPUT or OUTPUT) and any
 * parameter options (APPEND, BY-VALUE, BIND or BY-REFERENCE).  Allows subclasses to specialize this
 * behavior.  A core feature is to implement interchangeability between DATASET and DATASET-HANDLE parameters
 * (in the 4GL you can pass a DATASET parameter to a procedure that takes DATASET-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 DataSetParameter
{
   /** Reference of the wrapped dataset. */
   private final handle /*DataSet*/ dataset;
   
   /** Parameter passing mode APPEND, BY-VALUE, BIND or BY-REFERENCE option. */
   private EnumSet<ParameterOption> options;
   
   /** Determines if this is a remote (passed to appserver) parameter. */
   private boolean remoteParameter = false;
   
   /** 
    * Calling- or called-side result set (for remote parameters).
    * <p>
    * (equivalent of {@code TableWrapper resultSet} in {@code TableParameter}).
    */
   private DatasetWrapper datasetWrapper = null;
   
   /**
    * 1-based index of the dataset parameter (in declaration of the function). {@code 0} when
    * not defined.
    */
   private int parameterIndex = 0;

   /** The original dataset passed as an argument. */
   private DataSet originalDataSet;

   /**
    * true if the original dataset is a Proxy
    */
   private boolean isOriginalDataSetProxy = 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;
   
   /**
    * Create a new parameter for the REMOTE dataset.
    * 
    * @param    wrapper
    *           The REMOTE dataset.
    */
   public DataSetParameter(DatasetWrapper wrapper)
   {
      BufferManager.registerScopeable(null);
      
      this.datasetWrapper = wrapper;
      this.options = EnumSet.of(wrapper.isAppend() ? ParameterOption.APPEND : ParameterOption.NONE);
      this.remoteParameter = true;
      
      dataset = TypeFactory.handle();
      if (!wrapper.isFromJson())
      {
         createDataSet(wrapper);
      }
   }
   
   /**
    * Create an instance using a dataset reference.
    *
    * @param   dataset
    *          Reference of the wrapped dataset.
    */
   public DataSetParameter(DataSet dataset)
   {
      this(dataset, ParameterOption.NONE);
   }
   
   /**
    * Create an instance using a dataset reference.
    *
    * @param   dataset
    *          Reference of the wrapped dataset.
    * @param   option
    *          Table parameter option (APPEND, BY-VALUE, BIND or BY-REFERENCE).
    */
   public DataSetParameter(DataSet dataset, ParameterOption option)
   {
      this(dataset, option, false, false);
   }
   
   /**
    * Create an instance using a dataset reference.
    *
    * @param   dataset
    *          Reference of the wrapped dataset.
    * @param   options
    *          Table parameter options (APPEND, BY-VALUE, BIND or BY-REFERENCE).
    */
   public DataSetParameter(DataSet dataset, EnumSet<ParameterOption> options)
   {
      this(dataset, options, false, false);
   }

   /**
    * Create an instance using a dataset reference.
    *
    * @param    dataset
    *           Reference of the wrapped dataset.
    * @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 DataSetParameter(DataSet dataset, ParameterOption option, boolean input, boolean output)
   {
      this(dataset, EnumSet.of(option), input, output);
   }

   /**
    * Create an instance using a dataset reference.
    *
    * @param    dataset
    *           Reference of the wrapped dataset.
    * @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 DataSetParameter(DataSet dataset, EnumSet<ParameterOption> options, boolean input, boolean output)
   {
      BufferManager.registerScopeable(null);

      this.originalDataSet        = dataset;
      this.dataset                = new handle(dataset);
      this.options                = options; 
      // TODO: check default value for mode if ParameterOption.NONE is passed in
      this.input                  = input;
      this.output                 = output;
      this.isOriginalDataSetProxy = true;
   }

   /**
    * @return true is the original dataset is a Proxy
    */
   public boolean isOriginalDataSetProxy()
   {
      return isOriginalDataSetProxy;
   }
   
   /**
    * Create an instance using a dataset handle.
    *
    * @param    dsHandle
    *           Handle to the wrapped dataset.
    */
   public DataSetParameter(handle dsHandle)
   {
      this(dsHandle, ParameterOption.NONE);
   }
   
   /**
    * Create an instance using a dataset handle.
    *
    * @param    dsHandle
    *           Handle to the wrapped dataset.
    * @param    option
    *           Table parameter option (APPEND, BY-VALUE, BIND or BY-REFERENCE).
    */
   public DataSetParameter(handle dsHandle, ParameterOption option)
   {
      this(dsHandle, option, false, false);
   }
   
   /**
    * Create an instance using a dataset handle.
    *
    * @param    dsHandle
    *           Handle to the wrapped dataset.
    * @param    options
    *           Table parameter options (APPEND, BY-VALUE, BIND or BY-REFERENCE).
    */
   public DataSetParameter(handle dsHandle, EnumSet<ParameterOption> options)
   {
      this(dsHandle, options, false, false);
   }
   
   /**
    * Create an instance using a dataset handle.
    *
    * @param    dsHandle
    *           Handle to the wrapped dataset.
    * @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 DataSetParameter(handle dsHandle, ParameterOption option, boolean input, boolean output)
   {
      this(dsHandle, EnumSet.of(option), input, output);
   }

   /**
    * Create an instance using a dataset handle.
    *
    * @param    dsHandle
    *           Handle to the wrapped dataset.
    * @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 DataSetParameter(handle dsHandle, EnumSet<ParameterOption> options, boolean input, boolean output)
   {
      BufferManager.registerScopeable(null);
      
      this.originalDataSet = (DataSet)dsHandle.getResource();
      this.dataset = dsHandle;
      this.options = options; // TODO: check default value for mode if ParameterOption.NONE is passed in
      this.input   = input;
      this.output  = output;
   }

   /**
    * Get the table parameter option for this argument.
    * 
    * @return   The table parameter option.
    */
   public EnumSet<ParameterOption> getParameterOptions()
   {
      return options;
   }
   
   /**
    * Set the table parameter option for this argument.
    * 
    * @param   options
    *          The table parameter option.
    */
   public void setParameterOptions(EnumSet<ParameterOption> options)
   {
      this.options = options;
   }

   /**
    * Obtain the {@code DataSet} handle.
    *
    * @return  a handle to the dataset passed as parameter.
    */
   public handle get()
   {
      return new handle(getDataset());
   }
   
   /**
    * Get the original dataset, as passed by the caller.
    * 
    * @return    The {@link #originalDataSet}.
    */
   public DataSet getOriginalDataSet()
   {
      return originalDataSet;
   }

   /**
    * 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;
   }

   /**
    * Returns the unwrapped dataset instance.
    *
    * @return  see above.
    */
   public DataSet getDataset()
   {
      return dataset == null || dataset.isUnknown() ? null : (DataSet) dataset.getResource();
   }
   
   /**
    * Returns the wrapped dataset instance.
    *
    * @return  see above.
    */
   public handle getDatasetHandle()
   {
      return dataset;
   }
   
   /**
    * Checks whether this parameter was passed with {@code APPEND} option.
    * 
    * @return   {@code true} if this parameter was passed with {@code APPEND} option.
    */
   public boolean isAppend()
   {
      return options.contains(ParameterOption.APPEND);
   }
   
   /**
    * Checks whether this parameter was passed with {@code BY-REFERENCE} option.
    *
    * @return  {@code true} if this parameter was passed with {@code BY-REFERENCE} option.
    */
   public boolean isByReference()
   {
      return options.contains(ParameterOption.BY_REFERENCE);
   }
   
   /**
    * Checks whether this parameter was passed with {@code BY-VALUE} option.
    *
    * @return  {@code true} if this parameter was passed with {@code BY-VALUE} option.
    */
   public boolean isByValue()
   {
      return options.contains(ParameterOption.BY_VALUE);
   }
   
   /**
    * Checks whether this parameter was passed with {@code BIND} option.
    *
    * @return  {@code true} if this parameter was passed with {@code BIND} option.
    */
   public boolean isBind()
   {
      return options.contains(ParameterOption.BIND);
   }
   
   /**
    * Checks whether 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 {@link #datasetWrapper}.
    * 
    * @return   See above.
    */
   public DatasetWrapper getDatasetWrapper()
   {
      return datasetWrapper;
   }
   
   /**
    * Set the {@link #datasetWrapper}.
    * 
    * @param    datasetWrapper
    *           Calling- or called-side result set (for remote parameters).
    */
   public void setDatasetWrapper(DatasetWrapper datasetWrapper)
   {
      this.datasetWrapper = datasetWrapper;
   }
   
   /**
    * Get 1-based index of the dataset parameter (in declaration of the function).
    *
    * @return  1-based index of the dataset parameter or {@code 0} when not defined.
    */
   public int getParameterIndex()
   {
      return parameterIndex;
   }
   
   /**
    * Set 1-based index of the dataset parameter (in declaration of the function).
    *
    * @param   parameterIndex
    *          1-based index of the dataset parameter.
    */
   public void setParameterIndex(int parameterIndex)
   {
      this.parameterIndex = parameterIndex;
   }
   
   /**
    * Create a new DATASET resource from the specified wrapper, and save it in the {@link #dataset}
    * handle.
    * 
    * @param    wrapper
    *           The dataset from the remote side.
    */
   public void createDataSet(DatasetWrapper wrapper)
   {
      if (wrapper.getDatasetName() == null)
      {
         dataset.setUnknown();
         return;
      }
      
      DataSet.create(dataset);
      DataSet newDS = (DataSet) dataset.getResource();
      newDS.name(wrapper.getDatasetName());
      newDS.error(wrapper.hasError());
      if (wrapper.namespaceURI() != null)
      {
         newDS.namespaceURI(wrapper.namespaceURI());
      }
      if (wrapper.namespacePrefix() != null)
      {
         newDS.namespacePrefix(wrapper.namespacePrefix());
      }
      if (wrapper.getXmlNodeName() != null)
      {
         newDS.setXmlNodeName(wrapper.getXmlNodeName());
      }
      
      List<DsTableDefinition> tableDefs = wrapper.getTableDefs();
      Set<String> tablesWithoutSchema = new HashSet<>();
      if (tableDefs != null && !tableDefs.isEmpty())
      {
         BufferImpl[] buffs = new BufferImpl[tableDefs.size()];
         for (int k = 0; k < tableDefs.size(); k++)
         {
            DsTableDefinition tableDef = tableDefs.get(k);
            if (!tableDef.isReceivedSchema())
            {
               tablesWithoutSchema.add(tableDef.getName());
               continue;
            }
            if (!tableDef.isBeforeTable())
            {
               Iterator<PropertyDefinition> props = tableDef.propertyIterator();
               String idx = tableDef.getIndexes();
               String xmlns = tableDef.getXmlns();
               String prefix = tableDef.getXmlPrefix();
               List<IndexDefinition> indexDefs = tableDef.getIndexDefs();
               TempTableBuilder ttb = new TempTableBuilder(); 
               if (indexDefs != null)
               {
                  buffs[k] = ttb.createRemoteTable(tableDef.getName(), 
                           props, () -> indexDefs.stream().forEach(ix -> ix.addTo(ttb)), 
                           xmlns, prefix, tableDef.getSchemaMarshalLevel());
               }
               else
               {
                  buffs[k] = ttb.createRemoteTable(tableDef.getName(), 
                           props, idx, xmlns, prefix, tableDef.getSchemaMarshalLevel());
               }
               
               if (tableDef.isAfterTable() && tableDef.getPeerDef() != null)
               {
                  // ask the after-table to create its before table in order to have them correctly linked
                  TempTableBuilder afterTable = (TempTableBuilder) buffs[k].tableHandleNative();
                  DsTableDefinition beforeTableDef = tableDef.getPeerDef();
                  afterTable.createBeforeTable(beforeTableDef.getName(), afterTable._name());
               }
            }
            
            // flush data from [tableDef.getRows()] to [ttb] / buffs[k]
            TemporaryBuffer.insertAllRows(wrapper.getTableWrapper(k),
                                          ((Temporary) buffs[k]),
                                          wrapper.isAppend(),
                                          TemporaryBuffer.CopyTableMode.INPUT_PARAM_MODE,
                                          getParameterIndex());
         }
         newDS.setBuffersImpl(buffs);
      }
      
      List<DsRelationDefinition> relationsDefs = wrapper.getRelationsDefs();
      if (relationsDefs != null && !relationsDefs.isEmpty())
      {
         for (DsRelationDefinition relDef : relationsDefs)
         {
            if (tablesWithoutSchema.contains(relDef.getParentBuffer()) ||
                tablesWithoutSchema.contains(relDef.getChildBuffer()))
            {
               continue;
            }
            
            // the ADD-RELATION method of DataSet will raise error condition (11963) if the fields are
            // qualified, even if they are correct. Trying to be more lenient by dropping the buffer name,
            // in case that it is possible
            String pairs = relDef.getFieldPairs();
            String[] fields = pairs.split(",");
            boolean touched = false;
            for (int j = 0; j < fields.length; j++)
            {
               int i = fields[j].indexOf('.');
               if (i > 0)
               {
                  // drop the buffer qualifier:
                  fields[j] = fields[j].substring(i + 1);
                  touched = true;
               }
            }
            if (touched)
            {
               pairs = String.join(",", fields);
            }
            
            handle hRel = newDS.addRelation(
                  newDS.bufferHandle(relDef.getParentBuffer()),
                  newDS.bufferHandle(relDef.getChildBuffer()),
                  new character(pairs),
                  new logical(relDef.isReposition()),
                  new logical(relDef.isNested()),
                  new logical(relDef.isActive()),
                  new logical(relDef.isRecursive()),
                  new logical(relDef.isFkeyHidden()));
            ((CommonHandleChain) hRel.getResource()).name(relDef.getName());
         }
      }
   }
   
   /**
    * Get a string representation of this dataset.
    * 
    * @return   See above.
    */
   @Override
   public String toString()
   {
      return datasetWrapper != null 
               ? datasetWrapper.toString() 
               : originalDataSet != null 
                  ? originalDataSet.toString()
                  : dataset != null && !dataset.isUnknown() 
                     ? dataset.get().toString() 
                     : super.toString();
   }
}