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();
}
}