DataSource.java
/*
** Module : DataSource.java
** Abstract : DataSource implementation.
**
** Copyright (c) 2019-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
** 001 OM 20190325 Created initial version.
** 002 OM 20190611 Added runtime implementation.
** 003 OM 20190720 Added NEXT/RESTART-ROWID implementation.
** OM 20190721 Added validation of NAME attribute. Fixed case-sensitivity.
** CA 20190728 If the DATA-SOURCE is already attached when attempting to attach it, detach it
** from its buffer.
** CA 20190812 Changes to allow for mutable buffers; any API which receives a Buffer instance
** and is invoked from converted code must resolve the runtime instance before
** saving the instance.
** 004 OM 20201120 Made FILL-WHERE-STRING recompute automatically when conditions change.
** OM 20210226 Added sibling navigation support.
** OM 20210309 Do not use DmoMeta as key in TableMapper because temp-tables may share the same
** DmoMeta instance.
** CA 20210928 DATA-SOURCE:QUERY attribute must return an unknown value if the query is not valid.
** CA 20211005 Added deletable support for dynamic data-sources.
** CA 20210929 Fixed SAVE-WHERE-STRING when the pairs contain the rowid function.
** OM 20211023 Fixed FILL for EXTENT fields.
** OM 20220217 Fixed SAVE-WHERE-STRING for ROWID keys.
** OM 20220412 Setting the query attribute to unknown is a no-op.
** OM 20220801 Recompute the buffer list dynamically.
** OM 20220523 Fine-tuned conditions when the error 11892 is raised.
** OM 20220524 Allowed qualified field names in ADD-SOURCE-BUFFER parameter.
** OM 20220526 Added legacy name for static DATA-SOURCEs. Moved implementation of KEYS attributes
** to BufferImpl.
** OM 20220617 Populate the buffer list form the associated query if no buffers are present.
** OM 20220620 Added missing features in ATTACH-DATA-SOURCE method and DATA-SOURCE-ROWID attribute.
** CA 20221123 Static DATA-SOURCE instances must not be added to SESSION:FIRST-DATA-SOURCE chain.
** OM 20221129 FILL-WHERE-STRING is unknown if the buffer is not child an active dataset relation.
** SVL 20230110 P2JIndex.components() provides direct access to the components array in order to improve
** performance.
** HC 20230116 Replaced some handle usages with the actual wrapped resources for
** performance.
** CA 20230116 Avoid using handle.unwrap, handle.getReference or other BDT usage from within FWD runtime.
** HC 20230118 Eliminated some of the uses of String.toUpperCase and/or String.toLowerCase
** for performance.
** 005 IAS 20230223 Fix after rebase.
** 20230301 Added support for the RESTART-ROW attribute.
** 006 OM 20230315 DataRelation.get***BufferNative() methods return BufferImpl values.
** 007 CA 20230523 Performance improvement of the buffer fill operation - when possible (data type match),
** both source and destination buffers), use direct access to read and write the value.
** Extent fields are not supported yet.
** 008 OM 20230712 Reworked the DATA-SOURCE-COMPLETE-MAP and the related methods and attributes.
** 009 CA 20230724 Further reduce context-local usage.
** CA 20230727 FieldReference reads the field's extent directly without the overhead of creating the
** BUFFER-FIELD resource.
** 010 CA 20230828 Clear the 'BufferImpl.parentDataSource' when a data-source gets deleted.
** 011 OM 20240110 Fixed management of the query attribute.
** 012 CA 20240320 Avoid BDTs within internal FWD runtime. Replaced iterators with Java 'for', where it
** applies.
** 013 OM 20240326 Setting the FILL-WHERE-STRING does not trigger a query-prepare.
** 014 OM 20240401 Code for collecting the substitution buffers for dataset queries was moved to QueryWrapper
** class, avoiding code duplication.
** 015 OM 20240306 Fixed initialization of fields in FieldReference constructor.
** 016 AP 20240701 Overloaded addSourceBuffer to receive only a Buffer object. It is used for adding a new
** buffer when a query is for a data source.
** Modified addSourceBufferImpl method to, first, check if the actual resource used by the
** runtime(returned by ref method) exists in the buffers list, as this is also added,
** if successful.
** 017 CA 20240918 Removed default methods from interfaces and moved them as concrete implementations, as
** default methods cause the Java metaspace to spike one order of magnitude.
** 018 ICP 20250129 Used logical constants to leverage cached instances.
** 019 AS 20250404 Take into account the reposition attribute of the relations for a fill operation.
*/
/*
** 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.persist.orm.*;
import com.goldencode.p2j.util.*;
import com.goldencode.util.*;
import org.apache.commons.lang3.tuple.*;
import java.util.*;
/**
* A data-source object supports the automatic filling of a {@code DataSet} object member table,
* as well as applying updates back to one or more database tables. There is a distinct
* data-source object for each member buffer, which allows a single ProDataSet object and a
* single FILL operation on that object to combine data from multiple databases. A data-source
* object is defined independently of any {@code DataSet} object.
*/
@LegacyResource(resource = LegacyResource.DATA_SOURCE)
public class DataSource
extends HandleChain
implements ADMData,
DataSourceInterface,
DynamicResource,
Keyable,
QueryAssociable
{
/** Marker for added buffers which use their ROWID instead of a field-formed unique key. */
public static final String ROWID_KEY = "rowid";
/**
* Flags the dynamically constructed objects (using {@code CREATE} as opposed to statically
* defined objects, with {@code DEFINE}).
*/
private final boolean dynamic;
/** Flag indicating if this resource has been deleted. */
private boolean deleted = false;
/**
* Temporary builder during the DEFINE process. Defines the {@code DataSource} using a chaining
* set of methods that ends with the registration of the (new) {@code DataSource}.
*/
private Builder builder = null;
/** The value of {@code ADM-DATA} ABL attribute. */
private String admData = null;
/** The list of buffers of this {@code DataSource}. */
private final List<BufferImpl> buffers = new LinkedList<>();
/** The current values of NEXT-ROWID attributes. */
private final Map<BufferImpl, Long> nextRowids = new HashMap<>();
/** The current values of RESTART-ROWID attributes. */
private final Map<BufferImpl, Long> restartRowids = new HashMap<>();
/** The current values of RESTART-ROW attribute. */
private long restartRow = 0;
/**
* The {@code Buffer} attached to this {@code DataSource}. It must be a {@code DataSet} buffer.
*/
private BufferImpl datasetBuffer = null;
/** The handle to the implicit query that defines the buffers for the data-source object. */
private handle hImplicitQuery = new handle();
/**
* The handle to the optional explicit query that defines the buffers for the data-source object. If this
* is not {@code null} and valid, it will take precedence before {@link #hImplicitQuery}.
*/
private handle hExplicitQuery = null;
/**
* The current value of the {@code PREFER-DATASET} attribute. Default value is {@code false}.
*/
private boolean preferDataset = false;
/**
* The current value of the {@code MERGE-BY-FIELD} attribute. Default value is {@code true}.
*/
private boolean mergeByField = true;
/**
* The current value of {@code FILL-WHERE-STRING} attribute, if set programmatically. The default value
* is {@code null} / {@code unknown}, but this value cannot be set programmatically, at runtime.
*/
private String _fillWhereString = null;
/** The current mapping between the {@link #datasetBuffer} and fields from this object. */
private List<Pair<String, FieldReference>> buf2dsrc = null;
/** The reversed {@link #buf2dsrc} map. */
private Map<String, String> revBuf2src = null;
/** The values of {@code SAVE-WHERE-STRING} attribute, mapped by their buffers. */
private final Map<Buffer, String[]> saveWhereStrings = new HashMap<>();
/**
* Factory method. Corresponds to {@code CREATE DATASOURCE} statement. Creates a new
* {@code DataSource} and stores the object in the supplied handle.
*
* @param dsHandle
* The destination handle.
*/
public static void create(handle dsHandle)
{
DataSource.create(dsHandle, (character) null);
}
/**
* Factory method. Corresponds to {@code CREATE DATASOURCE} statement. Creates a new
* {@code DataSource} and stores the object in the supplied handle.
*
* @param dsHandle
* The destination handle.
* @param widgetPool
* The widget pool to created the new {@code DataSource} into.
*/
public static void create(handle dsHandle, String widgetPool)
{
DataSource.create(dsHandle, new character(widgetPool));
}
/**
* Factory method. Corresponds to {@code CREATE DATASOURCE} statement. Creates a new
* {@code DataSource} and stores the object in the supplied handle.
*
* @param dsHandle
* The destination handle.
* @param widgetPool
* The widget pool to created the new {@code DataSource} into.
*/
public static void create(handle dsHandle, character widgetPool)
{
// validate widget pool
if (!WidgetPool.validWidgetPool(widgetPool))
{
return;
}
DataSource ds = new DataSource(false);
dsHandle.assign(ds);
ds.addToPool(widgetPool);
ds.name("");
}
/**
* Starts the definition of a new static {@code DataSource}. This corresponds to the static
* {@code DEFINE DATA-SOURCE} statement.
*
* @return A {@code Builder} object used to chain the options of the statement.
*/
@Deprecated
public static Builder define()
{
DataSource ds = new DataSource(true);
return ds.builder;
}
/**
* Starts the definition of a new static {@code DataSource}. This corresponds to the static
* {@code DEFINE DATA-SOURCE} statement.
*
* @param legacyName
* The legacy name of the DATA-SOURCE.
*
* @return A {@code Builder} object used to chain the options of the statement.
*/
public static Builder define(String legacyName)
{
DataSource ds = new DataSource(true);
ds.name = legacyName;
return ds.builder;
}
/**
* The private constructor.
*
* @param staticDef
* {@code true} when constructor is called form a static {@code DEFINE DATA-SET}
* statement and {@code false} when called from a {@code CREATE DATA-SET} statement.
*/
private DataSource(boolean staticDef)
{
super(!staticDef, !staticDef);
this.dynamic = !staticDef;
if (staticDef)
{
builder = new Builder();
}
}
/**
* Check if this resource supports the NAME attribute in read-only mode.
*
* @return {@code false}, allowing the name to be altered.
*/
@Override
protected boolean hasNameReadOnly()
{
return false;
}
/**
* Allows each resource to declare whether it has some naming constraints.
*
* @return This method always returns {@code false}, requiring that its name should be
* validated before assignment.
*
* @see #validateName
*/
@Override
protected boolean hasNameConstraints()
{
return true;
}
/**
* Returns {@code true} if the buffer is dynamic.
* <p>
* This is not emitted by conversion, is used internally by FWD.
*
* @return {@code true} if the resource is dynamic, {@code false} otherwise.
*/
@Override
public boolean _dynamic()
{
return this.dynamic;
}
/**
* Checks whether the resource is dynamic or static.
*
* @return {@code true} if the resource is dynamic, {@code false} otherwise.
*/
@Override
public logical dynamic()
{
return logical.of(dynamic);
}
/**
* Reports if this object is valid for use.
*
* @return {@code true} if we are valid (can be used).
*/
@Override
public boolean valid()
{
return !deleted;
}
/**
* Get the {@code PREV-SIBLING} attribute of this DataSource. Actually always returns {@code null} because
* this attribute is not supported by DataSources. Only {@code NEXT-SIBLING} is supported.
*
* @return Always {@code null}.
*/
@Override
public handle getPrevSibling()
{
handle.invalidAttribute("PREV-SIBLING", false, LegacyResource.DATASET);
return new handle();
}
/**
* Get the {@code NEXT-SIBLING} attribute of this DataSource. Actually always returns the super's
* {@code getPrevSibling()} because DataSets are queued in reverse order(!).
* <p>
* TODO: investigate other handle resource that manifest the same from the point of NEXT/PREV-SIBLING.
*
* @return Always {@code null}.
*/
@Override
public handle getNextSibling()
{
return super.getPrevSibling();
}
/**
* Get the value of the ADM-DATA attribute.
*
* @return See above.
*/
@Override
public character getADMData()
{
return new character(admData);
}
/**
* Set the value of the ADM-DATA attribute.
*
* @param value The new value.
*/
@Override
public void setADMData(String value)
{
this.admData = value;
}
/**
* Set the value of the ADM-DATA attribute.
*
* @param value The new value.
*/
@Override
public void setADMData(character value)
{
this.admData = value.toJavaType();
}
/**
* Get the query associated with the given resource.
*
* @return The associated query or {@code unknown} if none exists.
*/
@Override
public handle getQueryAsHandle()
{
if (hExplicitQuery != null)
{
if (hExplicitQuery._isValid())
{
// if [hExplicitQuery] is set and valid, it takes precedence before [hImplicitQuery]
return hExplicitQuery;
}
else
{
// self update: if the explicit query was deleted switch back to implicit query
hExplicitQuery = null;
}
}
if (!hImplicitQuery._isValid())
{
createImplicitQuery();
}
return hImplicitQuery;
}
/**
* Set the query associated with the given resource. If an explicit query was previously set and now an
* unknown handle is use, the implicit handle is restored.
*
* @param qry
* The query to associate.
*/
@Override
public void setQueryAsHandle(handle qry)
{
if (qry.isUnknown())
{
hExplicitQuery = null;
return;
}
if (!qry.isType(LegacyResource.QUERY) || !qry._isValid())
{
ErrorManager.recordOrShowError(11976);
// Data-source QUERY attribute requires valid query handle or unknown.
ErrorManager.recordOrThrowError(3131, "QUERY", "", "DATA-SOURCE");
// Unable to set attribute <attribute name> in widget <widget name> of type <widget-type>.
return;
}
setQueryImpl(qry);
}
/**
* Set the query associated with the given resource.
*
* @param qry
* The query to associate.
*/
@Override
public void setQueryAsHandle(QueryWrapper qry)
{
setQueryImpl(new handle(qry));
}
/**
* Checks whether the modifications to the data currently in the data source are ignored when
* saving changes from a {@code DataSet} temp-table buffer to the associated data source using
* the {@code SAVE-ROW-CHANGES()} method. The default value is {@code FALSE}.
*
* @return the current value of {@code PREFER-DATASET} attribute, as described above.
*/
@Override
public logical getPreferDataset()
{
return new logical(preferDataset);
}
/**
* Configures whether the modifications to the data currently in the data source are ignored
* when saving changes from a {@code DataSet} temp-table buffer to the associated data source
* using the {@code SAVE-ROW-CHANGES()} method.
* <p>
* This attribute is synonym to {@code IGNORE-CURRENT-MODIFIED}. The latter one is deprecated,
* but its name is reported in exception message.
*
* @param pref
* the new value of {@code PREFER-DATASET} attribute, as described above.
*/
@Override
public void setPreferDataset(logical pref)
{
if (pref == null || pref.isUnknown())
{
unableToAssignUnknown("IGNORE-CURRENT-MODIFIED", "DATA-SET widget");
return;
}
setPreferDataset(pref.getValue());
}
/**
* Configures whether the modifications to the data currently in the data source are ignored
* when saving changes from a {@code DataSet} temp-table buffer to the associated data source
* using the {@code SAVE-ROW-CHANGES()} method.
*
* @param pref
* the new value of {@code PREFER-DATASET} attribute, as described above.
*/
@Override
public void setPreferDataset(boolean pref)
{
preferDataset = pref;
}
/**
* Gets the current {@code WHERE} expression for a data-source query, beginning with the
* keyword {@code WHERE}, and not including the prior {@code FOR EACH} phrase of an
* {@code OPEN QUERY} statement.
*
* @return The current value of the {@code FILL-WHERE-STRING} attribute, as described above.
*/
@Override
public character getFillWhereString()
{
return new character(_fillWhereString != null ? _fillWhereString : computeFillWhereString());
}
/**
* Sets the current {@code WHERE} expression for a data-source query, beginning with the
* keyword {@code WHERE}, and not including the prior {@code FOR EACH} phrase of an
* {@code OPEN QUERY} statement.
*
* @param pref
* The new value of the {@code FILL-WHERE-STRING} attribute, as described above.
*/
@Override
public void setFillWhereString(String pref)
{
if (datasetBuffer == null)
{
ErrorManager.recordOrThrowError(12786);
// FILL-WHERE-STRING cannot be assigned unless the DATA-SOURCE is attached to some dataset buffer by a prior ATTACH-DATA-SOURCE. (12786)
return;
}
// NOTE: initially I lived under the impression that IN P4GL, the syntax of the string is NOT checked
// (at least at this moment). It turned out that a query is created and prepare for it, so the
// syntax is surely checked. Possibly if no buffers are set first?
this._fillWhereString = pref;
if (hImplicitQuery != null && hImplicitQuery._isValid())
{
QueryWrapper query = (QueryWrapper) hImplicitQuery.getResource();
if (!query.isExplicit())
{
query.delete();
}
}
hImplicitQuery = new handle();
}
/**
* Sets the current {@code WHERE} expression for a data-source query, beginning with the
* keyword {@code WHERE}, and not including the prior {@code FOR EACH} phrase of an
* {@code OPEN QUERY} statement.
*
* @param pref
* The new value of the {@code FILL-WHERE-STRING} attribute, as described above.
*/
@Override
public void setFillWhereString(Text pref)
{
if (pref == null || pref.isUnknown())
{
unableToAssignUnknown("FILL-WHERE-STRING", "DATA-SOURCE widget");
return;
}
setFillWhereString(pref.getValue());
}
/**
* Obtain the list of key fields of a specified buffer as a comma-separated list.
*
* @return the list of key fields for the requested buffer.
*/
@Override
public character keys()
{
return keys(1);
}
/**
* Obtain the list of key fields of a specified buffer as a comma-separated list.
*
* @param seqNum
* The index of the buffer in the list of buffer for this {@code DataSource}.
*
* @return the list of key fields for the requested buffer.
*/
@Override
public character keys(long seqNum)
{
if (seqNum < 1 || seqNum > buffers.size())
{
return new character();
}
BufferImpl buffer = buffers.get((int) (seqNum - 1));
return buffer.ref().keys();
}
/**
* Obtain the list of key fields of a specified buffer as a comma-separated list.
*
* @param seqNum
* The index of the buffer in the list of buffer for this {@code DataSource}.
*
* @return the list of key fields for the requested buffer.
*/
@Override
public character keys(NumberType seqNum)
{
if (seqNum == null || seqNum.isUnknown())
{
return new character();
}
return keys(seqNum.longValue());
}
/**
* Checks whether the changes on a field-by-field basis will be merged when saving changes from
* a {@code DataSet} temp-table buffer to the associated data source using the
* {@code SAVE-ROW-CHANGES()} method. The default value is {@code TRUE}.
*
* @return {@code true} if the changes are merged on save on a field-by-field basis.
*/
@Override
public logical getMergeByField()
{
return new logical(mergeByField);
}
/**
* Configures whether the changes on a field-by-field basis will be merged when saving changes
* from a {@code DataSet} temp-table buffer to the associated data source using the
* {@code SAVE-ROW-CHANGES()} method.
*
* @param merge
* The new value for the {@code MERGE-BY-FIELD} attribute.
*/
@Override
public void setMergeByField(boolean merge)
{
mergeByField = merge;
}
/**
* Configures whether the changes on a field-by-field basis will be merged when saving changes
* from a {@code DataSet} temp-table buffer to the associated data source using the
* {@code SAVE-ROW-CHANGES()} method.
*
* @param merge
* The new value for the {@code MERGE-BY-FIELD} attribute.
*/
@Override
public void setMergeByField(logical merge)
{
if (merge == null || merge.isUnknown())
{
unableToAssignUnknown("MERGE-BY-FIELD", "DATA-SOURCE widget");
return;
}
setMergeByField(merge.getValue());
}
/**
* Gets the {@code ROWID} of the data source row at which the next {@code FILL} operation
* should start. The attribute is set after each {@code FILL} operation in a series of
* {@code FILL} operations to retrieve data source rows in batches.
*
* @return The current value of code {@code NEXT-ROWID} as described above.
*/
@Override
public rowid getNextRowid()
{
return getNextRowid(1L);
}
/**
* Gets the {@code ROWID} of the data source row at which the next {@code FILL} operation
* should start. The attribute is set after each {@code FILL} operation in a series of
* {@code FILL} operations to retrieve data source rows in batches.
*
* @param buffNo
* the sequence number of a buffer in the list of buffers for the data-source object.
*
* @return The current value of code {@code NEXT-ROWID} as described above.
*/
@Override
public rowid getNextRowid(NumberType buffNo)
{
if (buffNo == null || buffNo.isUnknown())
{
return new rowid();
}
return getNextRowid(buffNo.longValue());
}
/**
* Gets the {@code ROWID} of the data source row at which the next {@code FILL} operation
* should start. The attribute is set after each {@code FILL} operation in a series of
* {@code FILL} operations to retrieve data source rows in batches.
*
* @param buffNo
* the sequence number of a buffer in the list of buffers for the data-source object.
*
* @return The current value of code {@code NEXT-ROWID} as described above.
*/
@Override
public rowid getNextRowid(long buffNo)
{
if (buffNo <= 0 || buffNo > buffers.size())
{
return new rowid();
}
return new rowid(nextRowids.get(buffers.get((int) buffNo - 1)));
}
/**
* Gets the {@code ROWID} of the data source row at which the next {@code FILL} operation
* should start. The attribute is set after each {@code FILL} operation in a series of
* {@code FILL} operations to retrieve data source rows in batches.
*
* @param buffName
* The name of a buffer in the list of buffers for the data-source object.
*
* @return The current value of code {@code NEXT-ROWID} as described above.
*/
@Override
public rowid getNextRowid(String buffName)
{
for (BufferImpl buffer : buffers)
{
if (buffer.doGetName().equals(buffName))
{
return new rowid(nextRowids.get(buffer));
}
}
return new rowid();
}
/**
* Gets the {@code ROWID} of the data source row at which the next {@code FILL} operation
* should start. The attribute is set after each {@code FILL} operation in a series of
* {@code FILL} operations to retrieve data source rows in batches.
*
* @param buffName
* The name of a buffer in the list of buffers for the data-source object.
*
* @return The current value of code {@code NEXT-ROWID} as described above.
*/
@Override
public rowid getNextRowid(character buffName)
{
if (buffName == null || buffName.isUnknown())
{
return new rowid();
}
return getNextRowid(buffName.getValue());
}
/**
* Sets the {@code ROWID} of the data source row at which the next {@code FILL} operation
* should start. Typically, the value of this attribute is assigned to the
* {@code RESTART-ROWID} attribute before each {@code FILL} operation.
*
* @param row
* The new value of code {@code NEXT-ROWID} as described above.
*/
@Override
public void setNextRowid(rowid row)
{
setNextRowid(1, row);
}
/**
* Sets the {@code ROWID} of the data source row at which the next {@code FILL} operation
* should start. Typically, the value of this attribute is assigned to the
* {@code RESTART-ROWID} attribute before each {@code FILL} operation.
*
* @param buffNo
* the sequence number of a buffer in the list of buffers for the data-source object.
* @param row
* The new value of code {@code NEXT-ROWID} as described above.
*/
@Override
public void setNextRowid(NumberType buffNo, rowid row)
{
if (buffNo != null && !buffNo.isUnknown())
{
setNextRowid(buffNo.longValue(), row);
}
}
/**
* Sets the {@code ROWID} of the data source row at which the next {@code FILL} operation
* should start. Typically, the value of this attribute is assigned to the
* {@code RESTART-ROWID} attribute before each {@code FILL} operation.
*
* @param buffNo
* the sequence number of a buffer in the list of buffers for the data-source object.
* @param row
* The new value of code {@code NEXT-ROWID} as described above.
*/
@Override
public void setNextRowid(long buffNo, rowid row)
{
if (buffNo > 0 && buffNo <= buffers.size())
{
nextRowids.put(buffers.get((int) buffNo - 1), row.getValue());
}
}
/**
* Sets the {@code ROWID} of the data source row at which the next {@code FILL} operation
* should start. Typically, the value of this attribute is assigned to the
* {@code RESTART-ROWID} attribute before each {@code FILL} operation.
*
* @param buffName
* The name of a buffer in the list of buffers for the data-source object.
* @param row
* The new value of code {@code NEXT-ROWID} as described above.
*/
@Override
public void setNextRowid(String buffName, rowid row)
{
for (BufferImpl buffer : buffers)
{
if (buffer.doGetName().equals(buffName))
{
nextRowids.put(buffer, row.getValue());
return;
}
}
}
/**
* Sets the {@code ROWID} of the data source row at which the next {@code FILL} operation
* should start. Typically, the value of this attribute is assigned to the
* {@code RESTART-ROWID} attribute before each {@code FILL} operation.
*
* @param buffName
* The name of a buffer in the list of buffers for the data-source object.
* @param row
* The new value of code {@code NEXT-ROWID} as described above.
*/
@Override
public void setNextRowid(character buffName, rowid row)
{
if (buffName != null && !buffName.isUnknown())
{
setNextRowid(buffName.getValue(), row);
}
}
/**
* Obtain the number of source buffers in this data-source object.
*
* @return the number of source buffers in this data-source object.
*/
@Override
public integer getNumSourceBuffers()
{
return new integer(_getNumSourceBuffers());
}
/**
* Gets the current position (absolute row number) for the {@code FILL} query.
*
* @return the current value of {@code RESTART-ROW} attribute.
*/
@Override
public integer getRestartRow()
{
return new integer(restartRow);
}
/**
* Reposition the {@code FILL} query to an absolute row number.
*
* @param newRow
* The new row number to reposition the query.
*/
@Override
public void setRestartRow(long newRow)
{
this.restartRow = newRow;
}
/**
* Reposition the {@code FILL} query to an absolute row number.
*
* @param newRow
* The new row number to reposition the query.
*/
@Override
public void setRestartRow(NumberType newRow)
{
if (newRow == null || newRow.isUnknown())
{
ErrorManager.recordOrShowError(4083, "RESTART-ROW", "DATA-SOURCE");
// **Unable to assign UNKNOWN value to attribute RESTART-ROW on DATA-SOURCE widget. (4083)
return;
}
setRestartRow(newRow.longValue());
}
/**
* Obtain the {@code ROWID} of the data source row at which a {@code FILL} operation will
* start.
*
* @return the current value of {@code RESTART-ROWID} attribute.
*/
@Override
public rowid getRestartRowid()
{
return getRestartRowid(1L);
}
/**
* Obtain the {@code ROWID} of the data source row at which a {@code FILL} operation will
* start.
*
* @param buffNum
* The index of the buffer.
*
* @return the current value of {@code RESTART-ROWID} attribute.
*/
@Override
public rowid getRestartRowid(NumberType buffNum)
{
if (buffNum == null || buffNum.isUnknown())
{
return new rowid();
}
return getRestartRowid(buffNum.longValue());
}
/**
* Obtain the {@code ROWID} of the data source row at which a {@code FILL} operation will
* start.
*
* @param buffNum
* The index of the buffer.
*
* @return the current value of {@code RESTART-ROWID} attribute.
*/
@Override
public rowid getRestartRowid(long buffNum)
{
if (buffNum <= 0 || buffNum > buffers.size())
{
return new rowid();
}
return new rowid(restartRowids.get(buffers.get((int) buffNum - 1)));
}
/**
* Obtain the {@code ROWID} of the data source row at which a {@code FILL} operation will
* start.
*
* @param buffName
* The name of the buffer.
*
* @return the current value of {@code RESTART-ROWID} attribute.
*/
@Override
public rowid getRestartRowid(Text buffName)
{
if (buffName == null || buffName.isUnknown())
{
return new rowid();
}
return getRestartRowid(buffName.getValue());
}
/**
* Obtain the {@code ROWID} of the data source row at which a {@code FILL} operation will
* start.
*
* @param buffName
* The name of the buffer.
*
* @return the current value of {@code RESTART-ROWID} attribute.
*/
@Override
public rowid getRestartRowid(String buffName)
{
for (BufferImpl buffer : buffers)
{
if (buffer.doGetName().equals(buffName))
{
return new rowid(restartRowids.get(buffer));
}
}
return new rowid();
}
/**
* Configures the {@code ROWID} of the data source row at which a {@code FILL} operation will
* start. Set this attribute before each {@code FILL} operation in a series of {@code FILL}
* operations to retrieve data source rows in batches. Typically, this attribute is set by
* assigning the value of the {@code NEXT-ROWID} attribute, after each {@code FILL} operation.
*
* @param row
* The new value for {@code RESTART-ROWID} attribute as described above.
*/
@Override
public void setRestartRowid(rowid row)
{
setRestartRowid(1L, row);
}
/**
* Configures the {@code ROWID} of the data source row at which a {@code FILL} operation will
* start. Set this attribute before each {@code FILL} operation in a series of {@code FILL}
* operations to retrieve data source rows in batches. Typically, this attribute is set by
* assigning the value of the {@code NEXT-ROWID} attribute, after each {@code FILL} operation.
*
* @param buffNum
* The index of the buffer.
* @param row
* The new value for {@code RESTART-ROWID} attribute as described above.
*/
@Override
public void setRestartRowid(NumberType buffNum, rowid row)
{
if (buffNum != null && !buffNum.isUnknown())
{
setRestartRowid(buffNum.longValue(), row);
}
}
/**
* Configures the {@code ROWID} of the data source row at which a {@code FILL} operation will
* start. Set this attribute before each {@code FILL} operation in a series of {@code FILL}
* operations to retrieve data source rows in batches. Typically, this attribute is set by
* assigning the value of the {@code NEXT-ROWID} attribute, after each {@code FILL} operation.
*
* @param buffNum
* The index of the buffer.
* @param row
* The new value for {@code RESTART-ROWID} attribute as described above.
*/
@Override
public void setRestartRowid(long buffNum, rowid row)
{
if (buffNum > 0 && buffNum <= buffers.size())
{
restartRowids.put(buffers.get((int) buffNum - 1), row.getValue());
}
}
/**
* Configures the {@code ROWID} of the data source row at which a {@code FILL} operation will
* start. Set this attribute before each {@code FILL} operation in a series of {@code FILL}
* operations to retrieve data source rows in batches. Typically, this attribute is set by
* assigning the value of the {@code NEXT-ROWID} attribute, after each {@code FILL} operation.
*
* @param buffName
* The name of the buffer.
* @param row
* The new value for {@code RESTART-ROWID} attribute as described above.
*/
@Override
public void setRestartRowid(Text buffName, rowid row)
{
if (buffName != null && !buffName.isUnknown())
{
setRestartRowid(buffName.getValue(), row);
}
}
/**
* Configures the {@code ROWID} of the data source row at which a {@code FILL} operation will
* start. Set this attribute before each {@code FILL} operation in a series of {@code FILL}
* operations to retrieve data source rows in batches. Typically, this attribute is set by
* assigning the value of the {@code NEXT-ROWID} attribute, after each {@code FILL} operation.
*
* @param buffName
* The name of the buffer.
* @param row
* The new value for {@code RESTART-ROWID} attribute as described above.
*/
@Override
public void setRestartRowid(String buffName, rowid row)
{
for (BufferImpl buffer : buffers)
{
if (buffer.doGetName().equals(buffName))
{
restartRowids.put(buffer, row.getValue());
return;
}
}
}
/**
* Obtain the {@code WHERE} clause used to find the database buffer identified by the
* {@code buffName} in a data-source object.
*
* @return the {@code WHERE} clause used to find the specified buffer.
*/
@Override
public character getSaveWhereString()
{
return getSaveWhereString(1L);
}
/**
* Obtain the {@code WHERE} clause used to find the database buffer identified by the
* {@code buffName} in a data-source object.
*
* @param buffNum
* The buffer index.
*
* @return the {@code WHERE} clause used to find the specified buffer.
*/
@Override
public character getSaveWhereString(NumberType buffNum)
{
if (buffNum == null || buffNum.isUnknown() || buffers.isEmpty())
{
// no errors this time!
return new character();
}
return getSaveWhereString(buffNum.longValue());
}
/**
* Obtain the {@code WHERE} clause used to find the database buffer identified by the
* {@code buffName} in a data-source object.
*
* @param buffNum
* The buffer index.
*
* @return the {@code WHERE} clause used to find the specified buffer.
*/
@Override
public character getSaveWhereString(long buffNum)
{
if (buffers.isEmpty())
{
return new character();
}
if (buffNum < 1 || buffNum > buffers.size())
{
return new character();
}
BufferImpl buf = buffers.get((int) (buffNum - 1));
if (datasetBuffer == null)
{
String[] ret = saveWhereStrings.get(buf);
if (ret == null)
{
ErrorManager.recordOrShowError(11892, buffers.get(0).doGetName());
// Attempt to reference dataset buffer of a data-source before ATTACH-DATA-SOURCE for data-source buffer <name>.
return new character();
}
AbstractTempTable bt =
(AbstractTempTable) datasetBuffer.buffer().getParentTable().getBeforeTableNative();
return new character(String.join(bt._name(), ret));
}
return new character(getSaveWhereStringImpl(buf, true));
}
/**
* Obtain the {@code WHERE} clause used to find the database buffer identified by the
* {@code buffName} in a data-source object.
*
* @param buffName
* The buffer name.
*
* @return the {@code WHERE} clause used to find the specified buffer.
*/
@Override
public character getSaveWhereString(Text buffName)
{
if (buffName == null || buffName.isUnknown() || buffers.isEmpty())
{
// no errors this time!
return new character();
}
return getSaveWhereString(buffName.getValue());
}
/**
* Obtain the {@code WHERE} clause used to find the database buffer identified by the
* {@code buffName} in a data-source object.
*
* @param buffName
* The buffer name.
*
* @return the {@code WHERE} clause used to find the specified buffer.
*/
@Override
public character getSaveWhereString(String buffName)
{
if (buffers.isEmpty())
{
return new character();
}
for (BufferImpl buffer : buffers)
{
if (buffer.doGetName().equalsIgnoreCase(buffName))
{
if (datasetBuffer == null)
{
String[] ret = saveWhereStrings.get(buffer);
if (ret == null)
{
ErrorManager.recordOrShowError(11892, buffers.get(0).doGetName());
// Attempt to reference dataset buffer of a data-source before ATTACH-DATA-SOURCE for data-source buffer <name>.
return new character();
}
AbstractTempTable bt =
(AbstractTempTable) datasetBuffer.buffer().getParentTable().getBeforeTableNative();
return new character(String.join(bt._name(), ret));
}
return new character(getSaveWhereStringImpl(buffer, true));
}
}
// no buffer found, no problem
return new character();
}
/**
* Configures the {@code SAVE-WHERE-STRING} attribute.
*
* @param val
* The new value for {@code SAVE-WHERE-STRING} attribute.
*/
@Override
public void setSaveWhereString(Text val)
{
if (buffers.isEmpty())
{
return;
}
if (val == null || val.isUnknown())
{
unableToAssignUnknown("SAVE-WHERE-STRING", "DATA-SOURCE widget");
return;
}
setSaveWhereString(1, val.getValue());
}
/**
* Configures the {@code SAVE-WHERE-STRING} attribute.
*
* @param buffNum
* The buffer index.
* @param val
* The new value for {@code SAVE-WHERE-STRING} attribute.
*/
@Override
public void setSaveWhereString(NumberType buffNum, Text val)
{
if (buffers.isEmpty())
{
return;
}
if (val == null || val.isUnknown())
{
unableToAssignUnknown("SAVE-WHERE-STRING", "DATA-SOURCE widget");
return;
}
if (buffNum == null || buffNum.isUnknown())
{
// no error here
return;
}
setSaveWhereString(buffNum.longValue(), val.getValue());
}
/**
* Configures the {@code SAVE-WHERE-STRING} attribute.
*
* @param buffNum
* The buffer index.
* @param val
* The new value for {@code SAVE-WHERE-STRING} attribute.
*/
@Override
public void setSaveWhereString(long buffNum, Text val)
{
if (buffers.isEmpty())
{
return;
}
if (val == null || val.isUnknown())
{
unableToAssignUnknown("SAVE-WHERE-STRING", "DATA-SOURCE widget");
return;
}
setSaveWhereString(buffNum, val.getValue());
}
/**
* Configures the {@code SAVE-WHERE-STRING} attribute.
*
* @param buffName
* The buffer name.
* @param val
* The new value for {@code SAVE-WHERE-STRING} attribute.
*/
@Override
public void setSaveWhereString(Text buffName, Text val)
{
if (buffers.isEmpty())
{
return;
}
if (val == null || val.isUnknown())
{
unableToAssignUnknown("SAVE-WHERE-STRING", "DATA-SOURCE widget");
return;
}
if (buffName == null || buffName.isUnknown())
{
// no errors here
return;
}
setSaveWhereString(buffName.getValue(), val.getValue());
}
/**
* Configures the {@code SAVE-WHERE-STRING} attribute.
*
* @param buffName
* The buffer name.
* @param val
* The new value for {@code SAVE-WHERE-STRING} attribute.
*/
@Override
public void setSaveWhereString(String buffName, Text val)
{
if (buffers.isEmpty())
{
return;
}
if (val == null || val.isUnknown())
{
unableToAssignUnknown("SAVE-WHERE-STRING", "DATA-SOURCE widget");
return;
}
setSaveWhereString(buffName, val.getValue());
}
/**
* Configures the {@code SAVE-WHERE-STRING} attribute.
*
* @param val
* The new value for {@code SAVE-WHERE-STRING} attribute.
*/
@Override
public void setSaveWhereString(String val)
{
setSaveWhereString(1, val);
}
/**
* Configures the {@code SAVE-WHERE-STRING} attribute.
*
* @param buffNum
* The buffer index.
* @param val
* The new value for {@code SAVE-WHERE-STRING} attribute.
*/
@Override
public void setSaveWhereString(NumberType buffNum, String val)
{
if (buffNum == null || buffNum.isUnknown() || buffers.isEmpty())
{
return;
}
setSaveWhereString(buffNum.longValue(), val);
}
/**
* Configures the {@code SAVE-WHERE-STRING} attribute.
*
* @param buffNum
* The buffer index.
* @param val
* The new value for {@code SAVE-WHERE-STRING} attribute.
*/
@Override
public void setSaveWhereString(long buffNum, String val)
{
if (buffers.isEmpty())
{
return;
}
// if (datasetBuffer == null)
// {
// ErrorManager.recordOrShowError(11892, buffers.get(0).doGetName());
// // Attempt to reference dataset buffer of a data-source before ATTACH-DATA-SOURCE for data-source buffer <name>.
// return;
// }
if (buffNum < 1 || buffNum > buffers.size())
{
return;
}
saveWhereStrings.put(buffers.get((int) (buffNum - 1)), new String[] { val });
}
/**
* Configures the {@code SAVE-WHERE-STRING} attribute.
*
* @param buffName
* The buffer name.
* @param val
* The new value for {@code SAVE-WHERE-STRING} attribute.
*/
@Override
public void setSaveWhereString(Text buffName, String val)
{
if (buffName == null || buffName.isUnknown())
{
// no errors here
return;
}
setSaveWhereString(buffName.getValue(), val);
}
/**
* Configures the {@code SAVE-WHERE-STRING} attribute.
*
* @param buffName
* The buffer name.
* @param val
* The new value for {@code SAVE-WHERE-STRING} attribute.
*/
@Override
public void setSaveWhereString(String buffName, String val)
{
// if (datasetBuffer == null)
// {
// ErrorManager.recordOrShowError(11892, buffers.get(0).doGetName());
// // Attempt to reference dataset buffer of a data-source before ATTACH-DATA-SOURCE for data-source buffer <name>.
// return;
// }
for (BufferImpl buffer : buffers)
{
if (buffer.doGetName().equalsIgnoreCase(buffName))
{
saveWhereStrings.put(buffer, new String[] { val });
return;
}
}
// buffer not found by name? no problem.
}
/**
* Checks whether the to ignores modifications to the data currently in the data source when
* saving changes from a {@code DataSet} temp-table buffer to the associated data source using
* the {@link Buffer#saveRowChanges} method.
* <p>
* This attribute is supported only for backward compatibility. Use the
* {@link #getPreferDataset} method instead.
*
* @return the current value of {@code IGNORE-CURRENT-MODIFIED} attribute.
*/
@Override
public logical isIgnoreCurrentModified()
{
return getPreferDataset();
}
/**
* Sets whether the to ignores modifications to the data currently in the data source when
* saving changes from a {@code DataSet} temp-table buffer to the associated data source using
* the {@link Buffer#saveRowChanges} method.
* <p>
* This attribute is supported only for backward compatibility. Use the
* {@link #setPreferDataset} method instead.
*
* @param on
* The new value of {@code IGNORE-CURRENT-MODIFIED} attribute.
*/
@Override
public void setIgnoreCurrentModified(boolean on)
{
setPreferDataset(on);
}
/**
* Sets whether the to ignores modifications to the data currently in the data source when
* saving changes from a {@code DataSet} temp-table buffer to the associated data source using
* the {@link Buffer#saveRowChanges} method.
* <p>
* This attribute is supported only for backward compatibility. Use the
* {@link #setPreferDataset} method instead.
*
* @param on
* The new value of {@code IGNORE-CURRENT-MODIFIED} attribute.
*/
@Override
public void setIgnoreCurrentModified(logical on)
{
setPreferDataset(on);
}
/**
* Adds a database buffer to a data-source object at run time.
*
* @param buf
* The buffer to be added.
*
* @return {@code true} on success.
*/
public logical addSourceBuffer(Buffer buf)
{
return addSourceBufferImpl((BufferImpl) buf, null);
}
/**
* Adds a database buffer to a dynamic data-source object at run time.
*
* @param hBuf
* The buffer handle to be added.
* @param keyFields
* A list of one or more comma-separated database table fields that constitute a
* unique key
*
* @return {@code true} on success.
*/
@Override
public logical addSourceBuffer(handle hBuf, Text keyFields)
{
if (!isAlterable())
{
ErrorManager.recordOrShowError(11973);
// You may not change the buffers for a static data-source.
return new logical(false);
}
if (hBuf.isUnknown() || !hBuf.isType(LegacyResource.BUFFER))
{
ErrorManager.recordOrShowError(11974);
// The first argument to ADD-SOURCE-BUFFER must be a valid buffer handle.
return new logical(false);
}
BufferImpl buffer = (BufferImpl) hBuf.getResource();
RecordBuffer recBuf = buffer.buffer();
String[] fields = null;
if (keyFields == null || keyFields.isUnknown() || keyFields.getValue().isEmpty())
{
// search for first unique index and use its field list
// NOTE: TO 2xCHECK: apparently, only the first index is used if UNIQUE, the subsequent are not
// 'seen', even if UNIQUE
int k = 0;
TableMapper.LegacyIndexInfo index = TableMapper.getLegacyIndexInfo(recBuf, k);
while (index != null)
{
if (index.isUnique())
{
fields = index.getComponents();
break;
}
index = TableMapper.getLegacyIndexInfo(recBuf, ++k);
}
// if no unique index found, use the ROWID_KEY
if (fields == null)
{
fields = new String[]{ROWID_KEY};
}
}
else
{
fields = keyFields.getValue().split(",");
}
return addSourceBufferImpl(buffer, fields);
}
/**
* Adds a database buffer to a dynamic data-source object at run time.
*
* @param hBuf
* The buffer handle to be added.
* @param keyFields
* A list of one or more comma-separated database table fields that constitute a
* unique key
*
* @return {@code true} on success.
*/
@Override
public logical addSourceBuffer(handle hBuf, String keyFields)
{
if (!isAlterable())
{
ErrorManager.recordOrShowError(11973);
// You may not change the buffers for a static data-source.
return new logical(false);
}
if (hBuf.isUnknown() || !hBuf.isType(LegacyResource.BUFFER))
{
ErrorManager.recordOrShowError(11974);
// The first argument to ADD-SOURCE-BUFFER must be a valid buffer handle.
return new logical(false);
}
String[] fields;
if (keyFields == null || keyFields.isEmpty())
{
fields = new String[] { ROWID_KEY };
}
else
{
fields = keyFields.split(",");
}
return addSourceBufferImpl((BufferImpl) hBuf.getResource(), fields);
}
/**
* Gets the handle to the source buffer in the data-source object at the specified index
* position.
*
* @return the handle to the specified buffer.
*/
@Override
public handle getSourceBuffer()
{
return getSourceBuffer(1);
}
/**
* Gets the handle to the source buffer in the data-source object at the specified index
* position.
*
* @return the specified buffer.
*/
public BufferImpl getSourceBufferNative()
{
return getSourceBufferNative(1);
}
/**
* Gets the handle to the source buffer in the data-source object at the specified index
* position.
*
* @param bufIndex
* The index of the requested buffer.
*
* @return the handle to the specified buffer.
*/
@Override
public handle getSourceBuffer(long bufIndex)
{
return new handle(getSourceBufferNative(bufIndex));
}
/**
* Gets the handle to the source buffer in the data-source object at the specified index
* position.
*
* @param bufIndex
* The index of the requested buffer.
*
* @return the specified buffer.
*/
public BufferImpl getSourceBufferNative(long bufIndex)
{
if (bufIndex < 1 || bufIndex > buffers.size())
{
return null;
}
return buffers.get((int) (bufIndex - 1));
}
/**
* Gets the handle to the source buffer in the data-source object at the specified index
* position.
*
* @param bufIndex
* The index of the requested buffer.
*
* @return the handle to the specified buffer.
*/
@Override
public handle getSourceBuffer(NumberType bufIndex)
{
if (bufIndex == null || bufIndex.isUnknown())
{
return new handle();
}
return getSourceBuffer(bufIndex.longValue());
}
/**
* Gets the handle to the source buffer attached/associated with this {@code DataSource}.
*
* @return the handle to the specified buffer.
*/
@Override
public handle getDatasetBuffer()
{
return new handle(datasetBuffer);
}
/**
* Delete the resource.
*
* @return {@code true} if the resource was deleted.
*/
@Override
protected boolean resourceDelete()
{
if (hImplicitQuery != null && hImplicitQuery._isValid())
{
QueryWrapper query = (QueryWrapper) hImplicitQuery.getResource();
query.delete();
}
if (!_dynamic() && !implicitDeletion())
{
// no error is shown
return false;
}
this.deleted = true;
// detach the datasource from all buffers
for (BufferImpl buf : buffers)
{
buf.ref().clearParentDataSource();
}
return true;
}
/**
* Get a source buffer by its name. If not found {@code null} is returned.
*
* @param bufName
* The name of the desired source buffer.
*
* @return The 0-base index of the buffer or -1 if no source buffer is found with provided
* name.
*/
int getSourceBufferIndex(String bufName)
{
for (int i = 0; i < buffers.size(); i++)
{
if (buffers.get(i).doGetName().equalsIgnoreCase(bufName))
{
return i;
}
}
return -1;
}
/**
* Obtain the number of source buffers in this data-source object.
*
* @return the number of source buffers in this data-source object.
*/
int _getNumSourceBuffers()
{
return buffers.size();
}
/**
* Attaches or detaches the dataset buffer to this source (sets the dataset buffer).
*
* @param buffer
* The buffer to be attached. If {@code null} is passed, the currently attached buffer
* is detached.
* @param buf2dsrc
* The mapping between the dataset buffer and the fields of this object (these may
* come from different tables).
*/
void attach(BufferImpl buffer, List<Pair<String, FieldReference>> buf2dsrc)
{
this._fillWhereString = null;
if (buffer == null)
{
// this is a DETACH operation
this.datasetBuffer = null;
this.buf2dsrc = null;
this.revBuf2src = null;
this.saveWhereStrings.clear();
return;
}
buffer = buffer.ref();
if (this.datasetBuffer != null && datasetBuffer != buffer)
{
// detach the old buffer, if the same, let it be
this.datasetBuffer.detachDataSourceImpl();
}
this.datasetBuffer = buffer;
this.buf2dsrc = buf2dsrc;
}
/**
* Get the list of {@code RESTART-ROWID}-s for this {@code DataSource}. The order is important,
* it is the order in which the buffers were added at first.
*
* @return the current list of {@code RESTART-ROWID}.
*/
Long[] getRestartRowids()
{
Long[] ret = new Long[buffers.size()];
for (int i = 0; i < buffers.size(); i++)
{
ret[i] = restartRowids.get(buffers.get(i));
}
return ret;
}
/**
* Updates the FILL internal data, preparing it for the next batch, if any. Actually does the
* following things:
* <ul>
* <li>reset the list of {@code RESTART-ROWID}. All values will become unknown.</li>
* <li>updates the list of {@code NEXT-ROWID} values with the rowids currently selected
* in buffers. They were not processed yet so they are the 'NEXT' rows.</li>
* </ul>
* If the {@code FILL} operation failed, the {@code NEXT-ROWID} will be also set to unknown.
*
* @param success
* {@code true} if the fill ended with success.
*/
void updateFillRowidInfo(boolean success)
{
restartRowids.clear();
if (success)
{
// TODO: improve performance by using a mutable value (?)
for (BufferImpl buffer : buffers)
{
nextRowids.put(buffer, buffer._rowID());
}
}
else
{
nextRowids.clear();
}
}
/**
* Computes the value of {@code FILL-WHERE-STRING} attribute at this moment. Depending on the active
* relations from the dataset, this value may change.
*
* @return the value of {@code FILL-WHERE-STRING} attribute.
*/
private String computeFillWhereString()
{
if (datasetBuffer == null)
{
return null;
}
// check whether the buffer is filled as result as being the child buffer in an active relation
DataSet dataSet = datasetBuffer._dataSet();
if (!dataSet._areRelationsActive() ||
dataSet.getRelations(datasetBuffer, false, true, true, true).isEmpty())
{
return null;
}
StringBuilder sb = new StringBuilder();
String chBuff = datasetBuffer.doGetName();
List<DataRelation> rels = datasetBuffer._dataSet().getRelations(datasetBuffer, false, true, false);
if (rels.isEmpty())
{
// no constraints here
return null;
}
for (DataRelation rel : rels)
{
String parBuff = rel.getParentBufferNative()._name();
if (rel.isParentIdRelation().booleanValue())
{
// in PARENT-ID relations, the FILL WHERE is unknown
return null;
}
String[] relFields = rel._getRelationFields().split(",");
for (int k = 0; k < relFields.length; k += 2)
{
sb.append(sb.length() == 0 ? " WHERE " : " and "); // extra space!
String pf = relFields[k];
String cf = relFields[k + 1];
FieldReference fr = BufferImpl.getMappedField(this.datasetBuffer, cf);
if (fr == null && cf.toLowerCase().startsWith((datasetBuffer.doGetName() + ".").toLowerCase()))
{
// second chance, when the [cf] is qualified
cf = cf.substring(datasetBuffer.doGetName().length() + 1);
fr = BufferImpl.getMappedField(this.datasetBuffer, cf);
}
if (fr == null)
{
// this is but a guess, usually there is only a single buffer in data source,
// but there might be several
String guess = buffers.get(0).doGetName();
ErrorManager.recordOrShowError(11883, guess, cf, chBuff);
// Unable to auto-generate a FILL query predicate for <buffer> if field <fieldname> in <buffer> is not mapped.
return null; // not an error, but the fillWhereString remains [null]
}
sb.append(fr).append("=");
if (!pf.toLowerCase().startsWith((parBuff + ".").toLowerCase()))
{
sb.append(parBuff).append(".");
}
sb.append(pf);
}
}
// everything is fine
return sb.toString();
}
/**
* The implementation of assignation of QUERY attribute. Validates and link the query.
*
* @param hQry
* The handle to the new query to be assigned.
*/
private void setQueryImpl(handle hQry)
{
QueryWrapper qry = (QueryWrapper) hQry.getResource();
// once a query is assigned on an empty data-source, all its buffers are copied.
// if the query is set to null or other one, the buffers are kept.
int bufCount = qry.numBuffers().intValue();
if (buffers.isEmpty())
{
for (int k = 0; k < bufCount; k++)
{
if (!addSourceBufferImpl((BufferImpl) qry.getBufferByIndex(k + 1), null).booleanValue())
{
return;
}
}
}
else
{
if (qry.numBuffers().intValue() != buffers.size())
{
ErrorManager.recordOrShowError(11867);
// Query buffers and data-source buffers must match.
ErrorManager.recordOrThrowError(3131, "QUERY", "", "DATA-SOURCE");
// Unable to set attribute <attribute name> in widget <widget name> of type <widget-type>.
return;
}
for (int k = 0; k < bufCount; k++)
{
if (buffers.get(k) != qry.getBufferByIndex(k + 1))
{
ErrorManager.recordOrShowError(11867);
// Query buffers and data-source buffers must match.
ErrorManager.recordOrThrowError(3131, "QUERY", "", "DATA-SOURCE");
// Unable to set attribute <attribute name> in widget <widget name> of type <widget-type>.
return;
}
}
}
if (datasetBuffer != null)
{
qry.setSubstitutionBuffers(datasetBuffer);
}
hExplicitQuery = hQry;
}
/**
* Actual implementation of {@code ADD-SOURCE-BUFFER} method. The {@link #addSourceBuffer}
* method will call this method after preliminary validating the parameters.
*
* @param buffer
* The buffer to add.
* @param keyFields
* The fields set that for a unique index.
*
* @return {@code true} on success.
*/
private logical addSourceBufferImpl(BufferImpl buffer, String[] keyFields)
{
if (buffers.contains(buffer.ref()))
{
// this is strange, ADD action is not actually performed, but YES is returned
return logical.TRUE;
}
if (keyFields == null || keyFields.length == 0)
{
DmoMeta dmoInfo = buffer.buffer().getDmoInfo();
P2JIndex pIndex = dmoInfo.getPrimaryIndex(true);
if (pIndex != null && pIndex.isUnique())
{
String[] keyFieldsBuf = new String[20]; // 16 fields per index max theoretical
int fieldCount = 0;
ArrayList<P2JIndexComponent> comps = pIndex.components();
for (int i = 0; i < comps.size(); i++)
{
keyFieldsBuf[fieldCount++] = comps.get(i).getName(P2JIndexComponent.LEGACY);
}
keyFields = new String[fieldCount];
System.arraycopy(keyFieldsBuf, 0, keyFields, 0, fieldCount);
}
}
if (keyFields == null)
{
// to avoid NPE if the next test is disabled
keyFields = new String[0];
}
// NOTE: sometimes this is tested, sometimes not. What is the criterion?
// if (keyFields == null || keyFields.length == 0)
// {
// ErrorManager.recordOrShowError(11895, buffer.doGetName());
// // Unable to get SAVE-WHERE-STRING because no KEYS phrase given in DATA-SOURCE definition and no unique index found in <buffer-name>.
// return new logical(false);
// }
for (int i = 0; i < keyFields.length; i++)
{
String keyField = keyFields[i];
if (ROWID_KEY.equalsIgnoreCase(keyField))
{
continue;
}
if (!buffer.hasFieldEx(keyField))
{
ErrorManager.recordOrShowError(11975, keyField, buffer.doGetName());
// ADD-SOURCE-BUFFER key name <name> must be a field in buffer <name>.
return logical.FALSE;
}
int dotIdx = keyField.indexOf('.');
if (dotIdx != -1)
{
keyFields[i] = keyField.substring(dotIdx + 1);
}
}
// if everything was fine, save and return [true]
BufferImpl bufRef = buffer.ref();
boolean ok = bufRef.keys(this, keyFields);
if (ok)
{
buffers.add(bufRef);
}
return logical.of(ok);
}
/**
* Checks whether this object can be altered (added buffers and relations). Normally, the
* static defined {@code DataSource} s are not alterable, but they can 'altered' by their
* {@code Builder} while the object is being constructed.
*
* @return {@code true} if new buffers or relations can be added.
*/
private boolean isAlterable()
{
return _dynamic() || builder != null;
}
/**
* Get the current value of {@code SAVE-WHERE-STRING} for a specified {@code Buffer}. If this
* is the first time the attribute is accessed, its value is (lazily) computed.
*
* @param buf
* A {@code buffer} to obtain its save where string.
* @param useBefore
* Use the before-table name to construct the returned SAVE-WHERE-STRING (used for SAVE
* operation), as opposed to the after-table name (used for DATA-SOURCE-ROWID attribute).
*
* @return Get the current value of {@code SAVE-WHERE-STRING} for {@code buf}.
*/
String getSaveWhereStringImpl(Buffer buf, boolean useBefore)
{
BufferImpl bufImpl = (BufferImpl) buf;
String bufName = bufImpl.doGetName();
List<String> uIndexComp = bufImpl.keysList();
if (uIndexComp == null || uIndexComp.isEmpty())
{
boolean found = false;
// check if the buf2dsrc has a "<field>,rowid(<table>)" pair
for (Pair<String, FieldReference> pair : buf2dsrc)
{
FieldReference dsrcField = pair.getValue();
if (dsrcField.getName().equals(Session.PK))
{
// add these to the index component
uIndexComp = new ArrayList<>();
uIndexComp.add(Session.PK);
found = true;
}
}
if (!found)
{
ErrorManager.recordOrShowError(11895, bufName);
// Unable to get SAVE-WHERE-STRING because no KEYS phrase given in DATA-SOURCE definition and no unique index found in <buffer-name>.
return null;
}
}
String[] saveWhere = saveWhereStrings.get(buf);
if (saveWhere != null)
{
AbstractTempTable tempTable =
(AbstractTempTable) TempTableBuilder.asTempTable((Temporary) datasetBuffer);
TempTable bt = tempTable.getBeforeTableNative();
if (bt == null)
{
if (!tempTable._isTrackingChanges())
{
ErrorManager.recordOrShowError(11893);
// SAVE-WHERE-STRING cannot be generated when there is no BEFORE-TABLE--define one or use TRACKING-CHANGES.
}
// return [null] / unknown either way
return null;
}
if (useBefore)
{
tempTable = (AbstractTempTable) tempTable.getBeforeTableNative();
}
else
{
datasetBuffer.buffer().getParentTable()._name();
}
return String.join(tempTable._name(), saveWhere);
}
AbstractTempTable tempTable =
(AbstractTempTable) TempTableBuilder.asTempTable((Temporary) datasetBuffer);
TempTable bt = tempTable.getBeforeTableNative();
if (bt == null)
{
if (!tempTable._isTrackingChanges())
{
ErrorManager.recordOrShowError(11893);
// SAVE-WHERE-STRING cannot be generated when there is no BEFORE-TABLE--define one or use TRACKING-CHANGES.
}
// return [null] / unknown either way
return null;
}
if (revBuf2src == null)
{
if (buf2dsrc == null)
{
return null;
}
revBuf2src = new CaseInsensitiveHashMap<>();
for (Pair<String, FieldReference> pair : buf2dsrc)
{
revBuf2src.put(pair.getValue().toString(), pair.getKey());
}
}
String btName = bt.name().toStringMessage();
List<String> tokens = new ArrayList<>(6);
StringBuilder sb = new StringBuilder();
if (uIndexComp.size() == 1 && uIndexComp.get(0).equalsIgnoreCase(ROWID_KEY))
{
String btField = revBuf2src.get(bufName + ".recid");
if (btField == null)
{
ErrorManager.recordOrShowError(11894, bufName, btName);
// KEYS phrase for data-source buffer %1 contains ROWID, but no field in %2 was mapped to the rowid during ATTACH-DATA-SOURCE
return null;
}
int k = btField.indexOf('.');
if (k != -1)
{
btField = btField.substring(k + 1);
}
sb.append("WHERE ROWID(").append(bufName).append(") = ");
tokens.add(sb.toString());
sb = new StringBuilder();
sb.append(".").append(btField);
}
else
{
// auto generate the SAVE-WHERE-STRING value on first read access
for (String comp : uIndexComp)
{
String srcFld = bufName + "." + comp;
String btField = revBuf2src.get(srcFld);
if (btField == null)
{
ErrorManager.recordOrShowError(11896, bufName, comp, btName);
// Unable to generate a save predicate for <table-name> if field <field-name> is not mapped in <temp-table-name>.
return null;
}
int k = btField.indexOf('.');
if (k != -1)
{
btField = btField.substring(k + 1);
}
sb.append(sb.length() == 0 ? "WHERE " : " and ");
if (comp.equals(Session.PK))
{
srcFld = "rowid(" + bufName + ")";
}
sb.append(srcFld).append(" = ");
tokens.add(sb.toString());
sb = new StringBuilder();
sb.append('.').append(btField);
}
}
tokens.add(sb.toString());
String[] tokArray = new String[tokens.size()];
tokens.toArray(tokArray);
saveWhereStrings.put(buf, tokArray);
if (useBefore)
{
tempTable = (AbstractTempTable) tempTable.getBeforeTableNative();
}
else
{
datasetBuffer.buffer().getParentTable()._name();
}
return String.join(tempTable._name(), tokArray);
}
/**
* Test whether at current moment this data-source has an explicit query configured.
*
* @return {@code true} if a custom query was set for this data-source.
*/
boolean hasExplicitQuery()
{
return hExplicitQuery != null && hExplicitQuery._isValid();
}
/**
* Creates and prepares the implicit query. If a query already exists, does nothing.
*
* @return {@code true} if a new query was created and prepared.
*/
private boolean createImplicitQuery()
{
if (buffers.isEmpty() || hImplicitQuery._isValid())
{
return false;
}
BufferImpl srcBuffer = buffers.get(0);
// create an implicit query
hImplicitQuery = new handle();
QueryWrapper.createQuery(hImplicitQuery);
QueryWrapper query = (QueryWrapper) hImplicitQuery.getResource();
query.setImplicit();
StringBuilder sb = new StringBuilder("FOR EACH ");
sb.append(srcBuffer.name().toStringMessage());
query.addBuffer(srcBuffer);
String whereFillStr = getFillWhereString().toJavaType();
if (whereFillStr != null)
{
sb.append(" ");
sb.append(whereFillStr);
}
DataSet dataSet = null;
if (datasetBuffer != null)
{
query.setSubstitutionBuffers(datasetBuffer);
dataSet = (DataSet) datasetBuffer.dataSet().getResource();
}
if (!query.prepare(sb.toString()).getValue())
{
ErrorManager.recordOrShowError(11981);
// Failed to auto-prepare an automatic fill query. (11981)
// NOTE: OE crashes after printing the above message, should FWD do the same?
if (dataSet != null)
{
dataSet.error(true);
}
return false;
}
return true;
}
/**
* Gets a string representation of the {@code DataSource} object which contains the name, the buffers list
* and some details on the active queries, inlcluding their {@code FILL-WHERE-STRING} and
* {@code PREPARE-STRING}. This method is a usual debug tool.
*
* @return a string representation of the {@code DataSource} object.
*/
@Override
public String toString()
{
StringBuilder sb = new StringBuilder("DataSource{");
sb.append(datasetBuffer == null ? "no DS buffer" : datasetBuffer.doGetName());
sb.append(", buffers=");
buffers.forEach(b -> sb.append(b.doGetName()).append(", "));
sb.append("} FILL: implicit: ");
if (!hImplicitQuery._isValid())
{
sb.append("none");
}
else
{
QueryWrapper q = (QueryWrapper) hImplicitQuery.getResource();
sb.append("'").append(_fillWhereString).append("' / '");
sb.append(q.prepareString().toStringMessage()).append("'");
}
if (hExplicitQuery == null || hExplicitQuery._isValid())
{
sb.append(", explicit: none");
}
else
{
QueryWrapper q = (QueryWrapper) hExplicitQuery.getResource();
sb.append(", explicit: '").append(q.prepareString().toStringMessage()).append("'");
}
sb.append("}");
return sb.toString();
}
/**
* Builder class that helps construct a {@code DataSource} object statically, that is, using
* the {@code DEFINE DataSource} statement. It exposes a set of chaining methods that take each
* option parameters and set them into the {@code DataSource} being constructed. The chaining
* must finish with {@link #generate()} to obtain the final {@code DataSource} object. Before
* returning, the {@code generate()} will do the final checks before returning.
*/
public class Builder
{
/**
* Configures the a buffer for the {@code DataSource}. Corresponds to {@code FOR} option
* from {@code DEFINE DATASOURCE} statement. The syntax allows to specify none or more
* fields.
*
* @param buf
* The buffer for the {@code DataSource} to be constructed.
* @param fields
* The set of fields (if any) that that form a unique key.
*
* @return A reference of this object in order to help chaining.
*/
public Builder addBuffer(Buffer buf, String... fields)
{
DataSource.this.addSourceBufferImpl((BufferImpl) buf, fields);
return this;
}
/**
* Corresponds to {@code FOR} option from {@code DEFINE DATASOURCE} statement. This
* particular syntax allows to specify the ROWID as the key for record lookup.
*
* @param buf
* The buffer for the {@code DataSource} to be constructed.
* @param rowid
* This boolean will be ignored. It is used only to distinct the overloaded method.
*
* @return A reference of this object in order to help chaining.
*/
public Builder addBuffer(Buffer buf, boolean rowid)
{
DataSource.this.addSourceBufferImpl((BufferImpl) buf, new String[] { ROWID_KEY.toUpperCase() });
return this;
}
/**
* Sets the query associated with the given resource.
*
* @param query
* The query to associate.
*
* @return A reference of this object in order to help chaining.
*/
public Builder setQuery(QueryWrapper query)
{
DataSource.this.setQueryAsHandle(query);
return this;
}
/**
* Finalizes the build of the {@code DataSource}.
*
* @return The {@code DataSource} that have been constructed.
*/
public DataSource generate()
{
// prevent further options to be set
builder = null;
// end chain by returning the final object
return DataSource.this;
}
}
/**
* A reference to a field of a buffer. This is the 'lite' version of the {@link FieldReference}
* in the sense that it puts together the legacy field name to the reference of its buffer.
* This is a non-mutable object.
*/
public static class FieldReference
{
/** The buffer whose field this object represents. */
private final BufferImpl buffer;
/** The legacy name of the field. */
private final String name;
/** The EXTENT attribute of this field, if any, otherwise 0. */
private final int extent;
/** The EXTENT index of this field, if any, otherwise 0. {@code extent} must also be {@code > 0}. */
private final int index;
/** The field's resolved type. */
private final Class<? extends BaseDataType> type;
/**
* The field's resolved offset in the record's datum, for direct access. This will be used only when
* the buffer fill determines that the read and write can be done directly in the datum. Otherwise,
* this field can be null.
*/
private final Integer offset;
/**
* The constructor initialize both non mutable fields.
*
* @param buffer
* The buffer whose field this object represents.
* @param name
* The legacy name of the field.
* @param type
* The field's datatype.
* @param offset
* The field offset in the record datum.
*/
public FieldReference(BufferImpl buffer, String name, Class<? extends BaseDataType> type, Integer offset)
{
this.buffer = buffer;
this.type = type;
this.offset = offset;
if (name.contains("."))
{
String buffName = buffer.doGetName().toLowerCase();
String prefix = buffName + ".";
if (name.toLowerCase().startsWith(prefix))
{
name = name.substring(prefix.length());
}
else
{
throw new RuntimeException(
"Invalid buffer (" + buffName + ") for field reference: " + name);
}
}
if ("recid".equalsIgnoreCase(name))
{
this.name = name;
extent = 0;
index = 0;
}
else
{
int lb = name.indexOf('[');
int rb = name.indexOf(']');
int idx = 0;
if (lb > 0 && rb > 0)
{
try
{
idx = Integer.parseInt(name.substring(lb + 1, rb));
name = name.substring(0, lb);
}
catch (NumberFormatException e)
{
// do nothing, will throw exception later
}
}
// just resolve the extent, and not the BufferFieldImpl entire resource!
RecordBuffer recBuf = buffer.buffer();
Property property = recBuf.getDmoInfo().byLegacyName(name);
if (property.extent == 0)
{
this.extent = 0;
this.index = 0;
}
else
{
this.extent = property.extent;
this.index = idx;
}
this.name = name;
}
}
/**
* Get the field's {@link #offset} in the record datum.
*
* @return See above.
*/
public Integer getOffset()
{
return offset;
}
/**
* Get the field's data {@link #type}.
*
* @return See above.
*/
public Class<? extends BaseDataType> getType()
{
return type;
}
/**
* Obtains the buffer whose field this object represents.
*
* @return The buffer whose field thei object represents.
*/
public BufferImpl getBuffer()
{
return buffer;
}
/**
* Obtains the legacy name of the field.
*
* @return The legacy name of the field.
*/
public String getName()
{
return name;
}
/**
* Obtains the extent of this field.
*
* @return The EXTENT attribute of this field, if any, otherwise 0.
*/
public int getExtent()
{
return extent;
}
/**
* Obtains the index (1-base) of extent field. If this is not an extent, 0 is returned.
*
* @return the index (1-base) of extent field or 0 if this is not an extent.
*/
public int getIndex()
{
return index;
}
/**
* Gets a string representation of the object which contains the name buffer and the name of
* the field. This method is a usual debug tool.
*
* @return a string representation of the object.
*/
@Override
public String toString()
{
StringBuilder sb = new StringBuilder(buffer.doGetName()).append(".").append(name);
if (index > 0)
{
sb.append("[").append(index).append("]");
}
return sb.toString();
}
}
}