DynamicConversionHelper.java
/*
** Module : DynamicConversionHelper.java
** Abstract : Base class with common functions for DynamicQueryHelper and DynamicValidationHelper.
**
** Copyright (c) 2017-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
** 001 SVL 20171024 Created initial version.
** 002 CA 20190717 Added COM-HANDLE, BLOB, CLOB, RAW, ROWID types.
** CA 20190722 Added object type.
** CA 20190826 force_dmo_alias must be set only at the buffer used in the query, and not at
** the default buffer, when there is an explicit buffer used by the query.
** 003 OM 20200906 New ORM implementation.
** 004 OM 20200919 Improved access to dmo metadata.
** OM 20200924 P2JIndexComponent carries multiple information to avoid map lookups for them.
** CA 20200924 Performance improvement for prepareTempTable - cache the buffer DMO and schema ASTs, and
** also re-use the schema dictionary scope.
** OM 20201001 Improved DMO manipulation performance by caching slow Property annotation access.
** OM 20201208 Fixed model and schema AST caching for dynamic DMOs.
** OM 20210127 Replaced TableMapper.getLegacyName() triple map lookup with direct access to local
** dmoMeta.legacyTable.
** OM 20210309 Do not use DmoMeta as key in TableMapper because temp-tables share the same DmoMeta.
** ECF 20210506 Use RecordBuffer.getDmoInfo() getter instead of direct field access to prevent
** NPE in proxy case.
** ECF 20210511 Replaced DynamicTablesHelper.normalizeName with TextOps.rightTrimLower.
** CA 20210709 Added support for rowid and recid injected variables.
** ECF 20210914 Minor format cleanup.
** ME 20211229 Allow backslash escape character in strings only for UNIX.
** ECF 20220103 Minor format cleanup.
** OM 20220224 Fixed preprocessing of literals extracted from a converted query.
** OM 20220522 Added "force_dmo_alias" annotation in all cases to be sure the buffer names match.
** TJD 20220504 Upgrade do Java 11 minor changes
** SVL 20230110 P2JIndex.components() provides direct access to the components array in order to improve
** performance.
** OM 20230112 Small optimizations: cache local values, replaced double map lookup from TableMapper with
** direct access.
** SVL 20230113 Improved performance by replacing some "for-each" loops with indexed "for" loops.
** CA 20230116 Avoid using handle.unwrap, handle.getReference or other BDT usage from within FWD runtime.
** 005 ECF 20230301 Fixed prepareTempTable regression introduced by dynamic, persistent DMO buffer. May need
** further refactoring.
** 006 OM 20230215 Handled collision of buffers with same dynamic table name.
** 007 AD 20231211 Modified injectVariable() method to handle conversion error cases as 4GL does,
** using injectVariableHelper() method and InjectVariableContext inner class.
** 008 OM 20240318 Improved support for dynamic tables in mutable databases.
** 009 CA 20240410 Compiler constants need to be processed as variables - added support for buffer state
** constants (ROW-UNMODIFIED/CREATED/MODIFIED/DELETED).
** 010 OM 20240410 The information on mutable databases are mergeFromAst() into global scope.
** 011 AL2 20240530 Prepare the dictFile as well to be able to work with SchemaWorker in run-time conversion.
** 012 AI 20240605 Updated processString to corectly process ' and ".
** 013 AS 20240905 Fixed 223 error condition handling.
** 014 ICP 20250129 Used int64.of and logical.of to leverage caches instances.
*/
/*
** 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 antlr.*;
import com.goldencode.ast.*;
import com.goldencode.p2j.cfg.*;
import com.goldencode.p2j.convert.*;
import com.goldencode.p2j.persist.orm.*;
import com.goldencode.p2j.schema.*;
import com.goldencode.p2j.uast.*;
import com.goldencode.p2j.util.*;
import java.util.*;
import java.util.Map;
/**
* Base class with common functions for {@link DynamicQueryHelper} and
* {@link DynamicValidationHelper}.
*/
abstract class DynamicConversionHelper
{
/** Map of P2J wrappers to their associated token type. */
private static final Map<String, Integer> wrapperToTokenType = new HashMap<>();
static
{
wrapperToTokenType.put(ParmType.CHAR.toString(), ProgressParserTokenTypes.FIELD_CHAR);
wrapperToTokenType.put(ParmType.INT.toString(), ProgressParserTokenTypes.FIELD_INT);
wrapperToTokenType.put(ParmType.INT64.toString(), ProgressParserTokenTypes.FIELD_INT64);
wrapperToTokenType.put(ParmType.DEC.toString(), ProgressParserTokenTypes.FIELD_DEC);
wrapperToTokenType.put(ParmType.LOG.toString(), ProgressParserTokenTypes.FIELD_LOGICAL);
wrapperToTokenType.put(ParmType.DATE.toString(), ProgressParserTokenTypes.FIELD_DATE);
wrapperToTokenType.put(ParmType.DT.toString(), ProgressParserTokenTypes.FIELD_DATETIME);
wrapperToTokenType.put(ParmType.DTTZ.toString(), ProgressParserTokenTypes.FIELD_DATETIME_TZ);
wrapperToTokenType.put(ParmType.HANDLE.toString(), ProgressParserTokenTypes.FIELD_HANDLE);
wrapperToTokenType.put(ParmType.OBJECT.toString(), ProgressParserTokenTypes.FIELD_CLASS);
wrapperToTokenType.put(ParmType.COMHANDLE.toString(), ProgressParserTokenTypes.FIELD_COM_HANDLE);
wrapperToTokenType.put(ParmType.RECID.toString(), ProgressParserTokenTypes.FIELD_RECID);
wrapperToTokenType.put(ParmType.ROWID.toString(), ProgressParserTokenTypes.FIELD_ROWID);
wrapperToTokenType.put(ParmType.BLOB.toString(), ProgressParserTokenTypes.FIELD_BLOB);
wrapperToTokenType.put(ParmType.CLOB.toString(), ProgressParserTokenTypes.FIELD_CLOB);
wrapperToTokenType.put(ParmType.RAW.toString(), ProgressParserTokenTypes.FIELD_RAW);
}
/**
* Create a {@link DataModelAst} with the given token type and text.
*
* @param type
* The node's token type.
* @param text
* The node's text.
* @param p2oFile
* The name of the {@link com.goldencode.p2j.schema.DataModelAst} tree with the
* temp-table objects.
*
* @return A {@link DataModelAst} instance setup as noted above.
*/
private static DataModelAst createDataModelAst(int type, String text, String p2oFile)
{
AstManager astManager = AstManager.get();
long treeId = astManager.getTreeId(p2oFile);
DataModelAst ast = new DataModelAst(new CommonToken(type, text.intern()));
ast.setId(astManager.getNextNodeId(treeId));
return ast;
}
/**
* Build an {@link DataModelAst index component} associated with the given property AST.
*
* @param comp
* The index component
* @param propAst
* The associated {@link DataModelAst property AST}.
* @param p2oFile
* The name of the {@link com.goldencode.p2j.schema.DataModelAst} tree with the
* temp-table objects.
*
* @return See above.
*/
private static DataModelAst buildIndexComponent(P2JIndexComponent comp,
DataModelAst propAst,
String p2oFile)
{
String name = comp.getLegacyName();
String historical = (String) propAst.getAnnotation(P2OLookup.HISTORICAL);
String datatype = (String) propAst.getAnnotation(P2OLookup.DATATYPE);
DataModelAst root = createDataModelAst(DataModelTokenTypes.INDEX_COL, name.intern(), p2oFile);
root.putAnnotation(P2OLookup.HISTORICAL, historical.intern());
root.putAnnotation(P2OLookup.DATATYPE, datatype.intern());
if (comp.isIgnoreCase())
{
root.putAnnotation(P2OLookup.IGNORE_CASE, true);
}
if (comp.isDescending())
{
root.putAnnotation(P2OLookup.DESCEND, true);
}
if (comp.isAbbreviated())
{
root.putAnnotation(P2OLookup.ABBREVIATED, true);
}
root.putAnnotation(P2OLookup.REFID, propAst.getId());
return root;
}
/**
* Obtain the other operand of a binary operator node. This method works for binary and unary
* operators since the next sibling is {@code null}.
*
* @param progAst
* The node to search for sibling.
*
* @return The sibling of the passed in node. If such node does not exist, {@code null} is
* returned. In case of ternary operator, the result is irrelevant.
*/
private static ProgressAst getSibling(ProgressAst progAst)
{
ProgressAst first = (ProgressAst) progAst.getParent().getFirstChild();
if (first == progAst)
{
// I am the first child, the second child if exists, is my next sibling
return (ProgressAst) progAst.getNextSibling();
}
else
{
// there is at least one sibling before me, return that
return first;
}
}
/**
* Build the root {@link ProgressAst} node for the schema dictionary AST.
*
* @param schema
* The schema name.
*
* @return See above.
*/
private static ProgressAst buildRootSchema(String schema)
{
ProgressAst root = createProgressAst(SchemaParserTokenTypes.DATABASE, schema);
root.putAnnotation("srcfile", "in-memory");
return root;
}
/**
* Build the {@link DataModelAst root} AST and associated with the given schema.
*
* @param schema
* The schema name.
* @param p2oFile
* The name of the {@link com.goldencode.p2j.schema.DataModelAst} tree with the
* temp-table objects.
*
* @return See above.
*/
private static DataModelAst buildRootDataModel(String schema, String p2oFile)
{
String pkg = Configuration.getParameter("pkgroot") + ".dmo";
// this is a reverse generation of what schema/p2o.xml does
DataModelAst root = createDataModelAst(DataModelTokenTypes.DATA_MODEL, schema, p2oFile);
root.putAnnotation(P2OLookup.PACKAGE, pkg);
return root;
}
/**
* Create a {@link DataModelAst data model object} AST for the specified buffer.
*
* @param buf
* The buffer instance.
* @param rbuf
* The {@link RecordBuffer} instance for the buffer.
* @param p2oFile
* The name of the {@link com.goldencode.p2j.schema.DataModelAst} tree with the
* temp-table objects.
*
* @return A {@link DataModelAst} buffer definition.
*
* @throws PersistenceException
* In case of problems during preparation of the temp-table schema and p2o ASTs.
*/
private static DataModelAst buildDataModelClass(Buffer buf, RecordBuffer rbuf, String p2oFile)
throws PersistenceException
{
String historical = rbuf.getDmoInfo().legacyTable; // trimmed and interned
String dmoName = rbuf.getDMOName().intern();
DataModelAst root = createDataModelAst(DataModelTokenTypes.CLASS, dmoName, p2oFile);
root.putAnnotation(P2OLookup.HISTORICAL, historical);
// build the ID property. Normally, in conversion we use the name configured in p2j.cfg.xml
// accessed via Configuration.getSchemaConfig().getPrimaryKeyName(), but since we have access
// to runtime directory, the latter will prevail
DataModelAst idProp = createDataModelAst(DataModelTokenTypes.PRIMARY,
DatabaseManager.PRIMARY_KEY,
p2oFile);
idProp.putAnnotation(P2OLookup.DATATYPE, "Long");
root.addChild(idProp);
Map<String, DataModelAst> propMap = new HashMap<>();
// go through all non-extent properties
Class<? extends DataModelObject> dmoIface = rbuf.getDMOInterface();
DmoMeta dmoInfo = DmoMetadataManager.getDmoInfo(dmoIface);
for (Iterator<Property> it = dmoInfo.getFields(true); it.hasNext(); )
{
Property fieldAnn = it.next();
Class<?> fieldType = fieldAnn._fwdType;
if (!BaseDataType.class.isAssignableFrom(fieldType))
{
continue;
}
DataModelAst propAst = buildProperty(fieldAnn, p2oFile);
root.addChild(propAst);
String histName = (String) propAst.getAnnotation(P2OLookup.HISTORICAL);
propMap.put(TextOps.rightTrimLower(histName), propAst);
}
// // add the extent properties
// Map<String, Integer> extentMap = rbuf.getExtentMap();
// Class<?>[] innerClasses = dmoClass.getDeclaredClasses();
// for (Class<?> inner : innerClasses)
// {
// int extent = 0;
//
// fields = inner.getDeclaredFields();
// for (Field field : fields)
// {
// if (!BaseDataType.class.isAssignableFrom(field.getType()))
// {
// continue;
// }
//
// if (extent == 0)
// {
// String fname = field.getName();
// extent = extentMap.get(fname);
// }
//
// root.addChild(buildProperty(buf, field, extent, p2oFile));
// }
// }
// composite info is not needed by P2OLookup
Iterator<P2JIndex> idxIter = rbuf.getDmoInfo().getDatabaseIndexes();
while (idxIter.hasNext())
{
P2JIndex index = idxIter.next();
String idx = DBUtils.extractIndexName(index.getName());
if (index.getLegacyName() != null)
{
// include only legacy indexes
root.addChild(buildIndex(buf, rbuf, index, idx, propMap, p2oFile));
}
}
// DataModelTokenTypes.UNIQUE, ONE_TO_ONE and ONE_TO_MANY nodes are not needed
return root;
}
/**
* Create a {@link DataModelAst property} AST for the specified DMO java field.
*
* @param fieldAnn
* The annotation for the field defining this property at the DMO.
* @param p2oFile
* The name of the {@link com.goldencode.p2j.schema.DataModelAst} tree with the
* temp-table objects.
*
* @return A {@link DataModelAst} property definition.
*/
private static DataModelAst buildProperty(Property fieldAnn, String p2oFile)
{
String property = fieldAnn.name;
String historical = fieldAnn.legacy;
DataModelAst root = createDataModelAst(DataModelTokenTypes.PROPERTY, property, p2oFile);
root.putAnnotation(P2OLookup.HISTORICAL, historical);
root.putAnnotation(P2OLookup.DATATYPE, fieldAnn._fwdType.getSimpleName().intern());
if (fieldAnn.extent != 0)
{
root.putAnnotation(P2OLookup.EXTENT, Long.valueOf(fieldAnn.extent));
root.putAnnotation("original", fieldAnn.original);
root.putAnnotation("index", Long.valueOf(fieldAnn.index));
}
if (fieldAnn._isCharacter && fieldAnn.caseSensitive)
{
root.putAnnotation(P2OLookup.CASE_SENSITIVE, true);
}
// INITIAL is not needed for P2OLookup
return root;
}
/**
* Build a {@link DataModelAst index} for the specified buffer.
*
* @param buf
* The {@link Buffer} instance to which the index belongs.
* @param rbuf
* The {@link RecordBuffer} instance.
* @param index
* The index definition.
* @param idxName
* The index name.
* @param propMap
* A map with the {@link DataModelAst property} ASTs by their legacy name.
* @param p2oFile
* The name of the {@link com.goldencode.p2j.schema.DataModelAst} tree with the
* temp-table objects.
*
* @return A {@link DataModelAst index} definition.
*/
private static DataModelAst buildIndex(Buffer buf,
RecordBuffer rbuf,
P2JIndex index,
String idxName,
Map<String, DataModelAst> propMap,
String p2oFile)
{
DataModelAst root = createDataModelAst(DataModelTokenTypes.INDEX, idxName, p2oFile);
root.putAnnotation(P2OLookup.HISTORICAL, index.getLegacyName().intern());
if (index.isUnique())
{
root.putAnnotation(P2OLookup.UNIQUE, true);
}
if (index.isPrimary())
{
root.putAnnotation(P2OLookup.PRIMARY, true);
}
if (index.isWord())
{
root.putAnnotation(P2OLookup.WORD, true);
}
ArrayList<P2JIndexComponent> comps = index.components();
for (int i = 0; i < comps.size(); i++)
{
P2JIndexComponent comp = comps.get(i);
root.addChild(buildIndexComponent(comp, propMap.get(comp.getNormalizedName()), p2oFile));
}
return root;
}
/**
* Build a temp-table definition to be loaded in the {@link SchemaDictionary}. Use the given
* {@link DataModelAst data model object} AST as the source.
*
* @param p2o
* The data model object AST.
* @param rbuf
* The record buffer instance.
*
* @return See above.
*/
private static ProgressAst buildTempTable(DataModelAst p2o, RecordBuffer rbuf)
{
DmoMeta dmoInfo = rbuf.getDmoInfo();
String legacyTable = (String) p2o.getAnnotation("historical");
ProgressAst root = createProgressAst(dmoInfo.tempTable ? SchemaParserTokenTypes.TEMP_TABLE
: SchemaParserTokenTypes.TABLE,
legacyTable);
int fieldIdx = 0;
for (int i = 0; i < p2o.getNumImmediateChildren(); i++)
{
DataModelAst child = (DataModelAst) p2o.getChildAt(i);
Property property = dmoInfo.getFieldInfo(child.getText());
if (child.getType() == DataModelTokenTypes.PROPERTY)
{
String datatype = (String) child.getAnnotation("datatype");
int ftype = wrapperToTokenType.get(datatype);
String legacyField = (String) child.getAnnotation("historical"); // dmoProperty.legacy
ProgressAst field = createProgressAst(ftype, legacyField);
// the following properties are not needed by SchemaDictionary:
// 1. sqlWidth
// 2. extent
// 3. decimals
// 4. length
// 5. maxwidth
if (property.caseSensitive)
{
ProgressAst pcase = createProgressAst(SchemaParserTokenTypes.KW_CASE_SEN, "case-sensitive");
pcase.addChild(createProgressAst(SchemaParserTokenTypes.BOOL_TRUE, "true"));
field.addChild(pcase);
}
// add ORDER node
ProgressAst pord = createProgressAst(SchemaParserTokenTypes.ORDER, "ORDER");
pord.addChild(createProgressAst(SchemaParserTokenTypes.NUM_LITERAL, Long.toString(fieldIdx)));
fieldIdx = fieldIdx + 10;
field.addChild(pord);
Long extent = (Long) child.getAnnotation("extent");
if (extent != null)
{
ProgressAst pext = createProgressAst(SchemaParserTokenTypes.KW_EXTENT, "extent");
pext.addChild(createProgressAst(SchemaParserTokenTypes.NUM_LITERAL, extent.toString()));
field.addChild(pext);
}
root.addChild(field);
}
else if (child.getType() == DataModelTokenTypes.INDEX)
{
// add a new index
String legacyIndex = (String) child.getAnnotation("historical");
ProgressAst rootIdx = createProgressAst(SchemaParserTokenTypes.INDEX, legacyIndex);
// build index properties
ProgressAst props = createProgressAst(SchemaParserTokenTypes.PROPERTIES, "");
if (child.isAnnotation(P2OLookup.UNIQUE))
{
props.addChild(createProgressAst(SchemaParserTokenTypes.KW_UNIQUE, "UNIQUE"));
}
if (child.isAnnotation(P2OLookup.PRIMARY))
{
props.addChild(createProgressAst(SchemaParserTokenTypes.KW_PRIMARY, "PRIMARY"));
}
if (child.isAnnotation(P2OLookup.WORD))
{
props.addChild(createProgressAst(SchemaParserTokenTypes.KW_WORD_IDX, "WORD"));
}
rootIdx.addChild(props);
// build index components
DataModelAst idxCol = (DataModelAst) child.getImmediateChild(DataModelTokenTypes.INDEX_COL, null);
while (idxCol != null)
{
String legacyField = (String) idxCol.getAnnotation("historical");
ProgressAst idxField = createProgressAst(SchemaParserTokenTypes.INDEX_FIELD, legacyField);
if (idxCol.isAnnotation(P2OLookup.ABBREVIATED))
{
idxField.addChild(createProgressAst(SchemaParserTokenTypes.KW_ABBV, "ABBREVIATED"));
}
if (idxCol.isAnnotation(P2OLookup.DESCEND))
{
idxField.addChild(createProgressAst(SchemaParserTokenTypes.KW_DESCEND, "DESCENDING"));
}
else
{
idxField.addChild(createProgressAst(SchemaParserTokenTypes.KW_ASCEND, "ASCENDING"));
}
rootIdx.addChild(idxField);
idxCol = (DataModelAst) child.getImmediateChild(DataModelTokenTypes.INDEX_COL, idxCol);
}
root.addChild(rootIdx);
}
}
return root;
}
/**
* Create a {@link ProgressAst} with the given token type and text.
*
* @param type
* The node's token type.
* @param text
* The node's text.
*
* @return A {@link ProgressAst} instance setup as noted above.
*/
private static ProgressAst createProgressAst(int type, String text)
{
return new ProgressAst(new CommonToken(type, text));
}
/**
* Injects a new variable in place of a literal or field reference. This method makes the
* following changes in the structure of the {@code progAst} tree:
* <ul>
* <li>created the DEFINE statement structure/subtree of a new {@code varName} variable of
* the type required</li>
* <li>adds all necessary annotations to the newly created nodes as the
* {@code post-parse-fixup.xml} was run against it.</li>
* <li>replaces the literal or field reference with variable reference (morph it by changing
* its text/type and) the required annotations (including refid to newly created
* DEFINE VAR stmt)</li>
* <li>infers the type of the variable and converts the old text of the literal to a
* {@link BaseDataType} that will be returned. This will be used to initialize the
* variable just created.</li>
* </ul>
*
* @param litAst
* The AST of a literal or field references to be replaced by a variable.
* @param varName
* The name of the variable to be created.
* @param progAst
* The root of the procedure. It is only needed to inject the definition of the new
* variable.
*
* @return The value as {@link BaseDataType} of the literal replaced by the new variable. If
* a field reference is replaced, then unknown value of proper {@link BaseDataType} is
* returned.
*/
static Object injectVariable(ProgressAst litAst, String varName, ProgressAst progAst)
{
return injectVariable(new ProgressAst[]{litAst}, varName, progAst);
}
/**
* Injects a new variable in place of a literal or field reference. This method makes the
* following changes in the structure of the {@code progAst} tree:
* <ul>
* <li>created the DEFINE statement structure/subtree of a new {@code varName} variable of
* the type required</li>
* <li>adds all necessary annotations to the newly created nodes as the
* {@code post-parse-fixup.xml} was run against it.</li>
* <li>replaces the literal or field reference with variable reference (morph it by changing
* its text/type and) the required annotations (including refid to newly created
* DEFINE VAR stmt)</li>
* <li>infers the type of the variable and converts the old text of the literal to a
* {@link BaseDataType} that will be returned. This will be used to initialize the
* variable just created.</li>
* </ul>
*
* @param litAsts
* The list of ASTs of literals or field references to be replaced by a variable. All
* ASTs are replaced with the same variable.
* @param varName
* The name of the variable to be created.
* @param progAst
* The root of the procedure. It is only needed to inject the definition of the new
* variable.
*
* @return The value as {@link BaseDataType} of the literal replaced by the new variable. If
* a field reference is replaced, then unknown value of proper {@link BaseDataType} is
* returned.
*/
static Object injectVariable(ProgressAst[] litAsts, String varName, ProgressAst progAst)
{
BaseDataType ret = null;
String className = null;
ProgressAst stmt = new ProgressAst();
stmt.setType(ProgressParserTokenTypes.STATEMENT);
stmt.setText("statement");
progAst.graftAt(stmt, 0); // insert in front, before buffer definitions
ProgressAst define = new ProgressAst();
define.setType(ProgressParserTokenTypes.DEFINE_VARIABLE);
define.setText("DEFINE");
define.putAnnotation("name", varName);
define.putAnnotation("vardef", true);
stmt.graft(define);
ProgressAst symbol = new ProgressAst();
symbol.setType(ProgressParserTokenTypes.SYMBOL);
symbol.setText(varName);
define.graft(symbol);
ProgressAst as = new ProgressAst();
as.setType(ProgressParserTokenTypes.KW_AS);
as.setText("AS");
define.graft(as);
// step 1: evaluate the current literal as a BDT with type identical to original literal
ProgressAst litAst = litAsts[0];
String text = litAst.getText();
int literalType = litAst.getType();
switch (literalType)
{
// TODO: all literal types from common-progress.rules:compiler_constants need to be treated here
case ProgressParserTokenTypes.KW_ROW_UMOD:
literalType = ProgressParserTokenTypes.NUM_LITERAL;
ret = int64.of((long)BufferImpl.ROW_UNMODIFIED);
break;
case ProgressParserTokenTypes.KW_ROW_DELD:
literalType = ProgressParserTokenTypes.NUM_LITERAL;
ret = int64.of((long)BufferImpl.ROW_DELETED);
break;
case ProgressParserTokenTypes.KW_ROW_MODD:
literalType = ProgressParserTokenTypes.NUM_LITERAL;
ret = int64.of((long)BufferImpl.ROW_MODIFIED);
break;
case ProgressParserTokenTypes.KW_ROW_CRTD:
literalType = ProgressParserTokenTypes.NUM_LITERAL;
ret = int64.of((long)BufferImpl.ROW_CREATED);
break;
case ProgressParserTokenTypes.STRING:
ret = new character(processString(text, EnvironmentOps.isUnderWindowsFamily()));
break;
case ProgressParserTokenTypes.NUM_LITERAL:
ret = new int64(text);
break;
case ProgressParserTokenTypes.DATE_LITERAL:
ret = new date(text);
break;
case ProgressParserTokenTypes.DATETIME_LITERAL:
ret = datetime.fromLiteral(text);
break;
case ProgressParserTokenTypes.DATETIME_TZ_LITERAL:
ret = datetimetz.fromLiteral(text);
break;
case ProgressParserTokenTypes.DEC_LITERAL:
ret = decimal.fromLiteral(text);
break;
case ProgressParserTokenTypes.HEX_LITERAL:
literalType = ProgressParserTokenTypes.DEC_LITERAL;
ret = decimal.fromUnsignedHexLiteral(text);
break;
case ProgressParserTokenTypes.BOOL_TRUE:
case ProgressParserTokenTypes.BOOL_FALSE:
ret = logical.of(literalType == ProgressParserTokenTypes.BOOL_TRUE);
break;
case ProgressParserTokenTypes.UNKNOWN_VAL: /* disabled */
ret = new unknown();
break;
default:
// is field reference
}
// step 2. create the TYPE node of a type required by the sibling. If needed, an implicit
// conversion is applied to value obtained in step 1. If sibling does not exist or
// both operands are of the same type, the conversion is skipped and the type of
// the original literal is used.
int var4glType = literalType;
switch (litAst.getParent().getType())
{
case ProgressParserTokenTypes.KW_EQ:
case ProgressParserTokenTypes.EQUALS:
case ProgressParserTokenTypes.KW_NE:
case ProgressParserTokenTypes.NOT_EQ:
case ProgressParserTokenTypes.KW_LT:
case ProgressParserTokenTypes.LT:
case ProgressParserTokenTypes.KW_LTE:
case ProgressParserTokenTypes.LTE:
case ProgressParserTokenTypes.KW_GT:
case ProgressParserTokenTypes.GT:
case ProgressParserTokenTypes.KW_GTE:
case ProgressParserTokenTypes.GTE:
ProgressAst sibling = getSibling(litAst);
if (sibling != null)
{
if (sibling.getType() > ProgressParserTokenTypes.BEGIN_FIELDTYPES &&
sibling.getType() < ProgressParserTokenTypes.END_FIELDTYPES)
{
// only fields are taken into consideration to avoid database CASTing issues
var4glType = sibling.getType();
}
else if (sibling.getType() == ProgressParserTokenTypes.FUNC_RECID)
{
// we want our operand to be bigint to match the record.id field in db
var4glType = ProgressParserTokenTypes.NUM_LITERAL;
}
}
break;
}
ProgressAst type = new ProgressAst();
int varType = 0;
InjectVariableContext methodArg = new InjectVariableContext();
methodArg.className = className;
methodArg.var4glType = var4glType;
methodArg.literalType = literalType;
methodArg.baseDataTypeObj = ret;
methodArg.varType = varType;
methodArg.type = type;
methodArg.text = text;
if (literalType == ProgressParserTokenTypes.STRING)
{
final BaseDataType[] retTempOutput = new BaseDataType[1];
ErrorManager.silent(() ->
{
// assign unknown to the result initially, in case conversion fails
retTempOutput[0] = new character();
// try to convert argument to the type of the field, but catch any thrown exceptions
retTempOutput[0] = injectVariableHelper(methodArg);
});
ret = retTempOutput[0];
}
else
{
ret = injectVariableHelper(methodArg);
}
varType = methodArg.varType;
type = methodArg.type;
text = methodArg.text;
className = methodArg.className;
as.graft(type);
// morphing the literal node to variable
for (ProgressAst ast : litAsts)
{
ast.setText(varName);
ast.setType(varType);
ast.removeAnnotation("is-literal"); // remove old annotation
ast.putAnnotation("oldtype", Long.valueOf(ProgressParserTokenTypes.SYMBOL));
ast.putAnnotation("refid", define.getId());
}
// annotate the variable definition with correct type
define.putAnnotation("type", Long.valueOf(varType));
define.putAnnotation("javaname", varName);
define.putAnnotation("classname", className);
return ret;
}
/**
* Helper method for {@link #injectVariable(ProgressAst[], String, ProgressAst)}.
* This method could sometimes throw an exception depending on the validity
* of the input data, but that error should sometimes be caught in the caller
* method.
*
* @param context
* An object containing all the data necessary for returning the result.
*
* @return The conversion result from the given object in the
* {@link InjectVariableContext} to the type of the field, also
* specified in the given argument.
*/
private static BaseDataType injectVariableHelper(InjectVariableContext context)
{
BaseDataType ret = context.baseDataTypeObj;
ProgressAst type = context.type;
String className = context.className;
String text = context.text;
int var4glType = context.var4glType;
int literalType = context.literalType;
int varType = context.varType;
try
{
switch (var4glType)
{
case ProgressParserTokenTypes.STRING:
case ProgressParserTokenTypes.FIELD_CHAR:
type.setType(ProgressParserTokenTypes.KW_CHAR);
type.setText("CHARACTER");
className = "character";
varType = ProgressParserTokenTypes.VAR_CHAR;
if (literalType != ProgressParserTokenTypes.STRING)
{
ErrorManager.recordOrShowError(223, true, false);
}
break;
case ProgressParserTokenTypes.NUM_LITERAL:
case ProgressParserTokenTypes.FIELD_INT:
case ProgressParserTokenTypes.FIELD_INT64:
type.setType(ProgressParserTokenTypes.KW_INT64);
type.setText("INT64");
className = "int64";
varType = ProgressParserTokenTypes.VAR_INT64;
if (literalType != ProgressParserTokenTypes.NUM_LITERAL)
{
if (literalType == ProgressParserTokenTypes.BOOL_TRUE ||
literalType == ProgressParserTokenTypes.BOOL_FALSE ||
literalType == ProgressParserTokenTypes.DATETIME_TZ_LITERAL ||
literalType == ProgressParserTokenTypes.DATETIME_LITERAL ||
literalType == ProgressParserTokenTypes.DATE_LITERAL)
{
ErrorManager.recordOrShowError(223, true, false);
}
//bad conversion when ret is of type rowid
ret = new int64(ret);
}
break;
case ProgressParserTokenTypes.DATE_LITERAL:
case ProgressParserTokenTypes.FIELD_DATE:
type.setType(ProgressParserTokenTypes.KW_DATE);
type.setText("DATE");
className = "date";
varType = ProgressParserTokenTypes.VAR_DATE;
if (literalType != ProgressParserTokenTypes.DATE_LITERAL)
{
if (literalType != ProgressParserTokenTypes.STRING)
{
ErrorManager.recordOrShowError(223, true, false);
}
BaseDataType[] retTempOutput = new BaseDataType[1];
BaseDataType retTemp = ret;
ErrorManager.silent(() ->
{
// assign unknown to the result initially, in case conversion fails
retTempOutput[0] = new date();
// try to convert argument to the type of the field,
// but catch any thrown exceptions
retTempOutput[0] = new date(retTemp);
});
ret = retTempOutput[0];
}
break;
case ProgressParserTokenTypes.DATETIME_LITERAL:
case ProgressParserTokenTypes.FIELD_DATETIME:
type.setType(ProgressParserTokenTypes.KW_DATETIME);
type.setText("DATETIME");
className = "datetime";
varType = ProgressParserTokenTypes.VAR_DATETIME;
if (literalType != ProgressParserTokenTypes.DATETIME_LITERAL)
{
if (literalType != ProgressParserTokenTypes.STRING)
{
ErrorManager.recordOrShowError(223, true, false);
}
ret = new datetime(ret);
}
break;
case ProgressParserTokenTypes.DATETIME_TZ_LITERAL:
case ProgressParserTokenTypes.FIELD_DATETIME_TZ:
type.setType(ProgressParserTokenTypes.KW_DATE_TZ);
type.setText("DATETIME-TZ");
className = "datetimetz";
varType = ProgressParserTokenTypes.VAR_DATETIME_TZ;
if (literalType != ProgressParserTokenTypes.DATETIME_TZ_LITERAL)
{
if (literalType != ProgressParserTokenTypes.STRING)
{
ErrorManager.recordOrShowError(223, true, false);
}
ret = new datetimetz(ret);
}
break;
case ProgressParserTokenTypes.DEC_LITERAL:
case ProgressParserTokenTypes.FIELD_DEC:
type.setType(ProgressParserTokenTypes.KW_DEC);
type.setText("DECIMAL");
className = "decimal";
varType = ProgressParserTokenTypes.VAR_DEC;
if (literalType != ProgressParserTokenTypes.DEC_LITERAL)
{
if (literalType == ProgressParserTokenTypes.BOOL_TRUE ||
literalType == ProgressParserTokenTypes.BOOL_FALSE ||
literalType == ProgressParserTokenTypes.DATETIME_LITERAL ||
literalType == ProgressParserTokenTypes.DATETIME_TZ_LITERAL ||
literalType == ProgressParserTokenTypes.DATE_LITERAL)
{
ErrorManager.recordOrShowError(223, true, false);
}
//still wrong in case ret is of type rowid
ret = new decimal(ret);
}
break;
case ProgressParserTokenTypes.BOOL_TRUE:
case ProgressParserTokenTypes.BOOL_FALSE:
case ProgressParserTokenTypes.FIELD_LOGICAL:
type.setType(ProgressParserTokenTypes.KW_LOGICAL);
type.setText("LOGICAL");
className = "logical";
varType = ProgressParserTokenTypes.VAR_LOGICAL;
if (literalType != ProgressParserTokenTypes.BOOL_TRUE &&
literalType != ProgressParserTokenTypes.BOOL_FALSE)
{
if (literalType != ProgressParserTokenTypes.STRING)
{
ErrorManager.recordOrShowError(223, true, false);
}
BaseDataType[] retTempOutput = new BaseDataType[1];
BaseDataType retTemp = ret;
ErrorManager.silent(() ->
{
// assign unknown to the result initially, in case conversion fails
retTempOutput[0] = new logical();
// try to convert argument to the type of the field,
// but catch any thrown exceptions
retTempOutput[0] = new logical(retTemp);
});
ret = retTempOutput[0];
}
break;
case ProgressParserTokenTypes.FIELD_RECID:
type.setType(ProgressParserTokenTypes.KW_RECID);
type.setText("RECID");
className = "recid";
varType = ProgressParserTokenTypes.VAR_RECID;
if (literalType == ProgressParserTokenTypes.BOOL_TRUE ||
literalType == ProgressParserTokenTypes.BOOL_FALSE ||
literalType == ProgressParserTokenTypes.DATETIME_TZ_LITERAL ||
literalType == ProgressParserTokenTypes.DATETIME_LITERAL ||
literalType == ProgressParserTokenTypes.DATE_LITERAL ||
literalType == ProgressParserTokenTypes.DEC_LITERAL)
{
ErrorManager.recordOrShowError(223, true, false);
}
BaseDataType[] retTempOutput = new BaseDataType[1];
ErrorManager.silent(() ->
{
// assign unknown to the result initially, in case conversion fails
retTempOutput[0] = new recid();
// try to convert argument to the type of the field,
// but catch any thrown exceptions
// still improper conversion when text holds a rowid value.
retTempOutput[0] = new recid(text);
});
ret = retTempOutput[0];
break;
case ProgressParserTokenTypes.FIELD_ROWID:
type.setType(ProgressParserTokenTypes.KW_ROWID);
type.setText("ROWID");
className = "rowid";
varType = ProgressParserTokenTypes.VAR_ROWID;
if (literalType != ProgressParserTokenTypes.STRING)
{
ErrorManager.recordOrShowError(223, true, false);
}
ret = new rowid(text);
break;
default:
// throw exception?
}
}
finally
{
context.className = className;
context.literalType = literalType;
context.varType = varType;
context.baseDataTypeObj = ret;
context.type = type;
context.var4glType = var4glType;
context.text = text;
}
return ret;
}
/**
* Preprocess a literal extracted from a converted query. The String literals encountered in a AST tree
* were already (partially) processed by the 4GL lexer. This method adjusts the string literals back to
* what was the expected value as the resul to be identical to 4GL.
*
* @param text
* The text to be processed.
* @param windows
* Emulate windows. If {@code true} the {@code \} is used as a normal character, file separator.
* Otherwise, honour the Linux convention, where the {@code \} is an escape character with same
* role as {@code ~} character.
*
* @return The processed text.
*/
private static String processString(String text, boolean windows)
{
// test if starts with ' or "
char ch = text.charAt(0);
if (ch != '"' && ch != '\'')
{
throw new IllegalArgumentException("Missing opening quote (" + ch + ").");
}
int lastCharPos = text.lastIndexOf(ch);
if (lastCharPos <= 0)
{
throw new IllegalArgumentException("Missing closing quote (" + ch + ").");
}
lastCharPos--;
StringBuilder sb = new StringBuilder();
for (int k = 1; k <= lastCharPos; k++)
{
ch = text.charAt(k);
char next = k < lastCharPos ? text.charAt(k + 1) : '\0';
switch (ch)
{
case 10:
// the chr(10) ('\n') seems to be silently dropped in dynamic queries
continue;
case '\\':
if (windows)
{
break;
}
// else: Linux: '\' is used with same value as '~', break through...
case '~':
switch (next)
{
case '"': sb.append('"'); k++; break;
case '\'': sb.append('\''); k++; break;
case '~': sb.append('~'); k++; break;
case '{': sb.append('{'); k++; break;
case 't': sb.append('\t'); k++; break; // tab
case 'r': sb.append('\r'); k++; break; // CR
case 'n': sb.append('\n'); k++; break; // LF
case 'E': sb.append((char) 27); k++; break; // escape
case 'b': sb.append('\b'); k++; break; // bell
case 'f': sb.append('\f'); k++; break; // form feed
case 'u':
if (k + 4 < lastCharPos)
{
char hexVal = getEncodedChar(text, k + 2, 4, 16);
if (hexVal != 0xFFFF)
{
sb.append(hexVal);
k += 5; // 'u' + 4 hex digits
}
}
break;
case 'U':
if (k + 6 < lastCharPos)
{
char hexVal = getEncodedChar(text, k + 2, 6, 16);
if (hexVal != 0xFFFF)
{
sb.append(hexVal);
k += 7; // 'U' + 6 hex digits
}
}
break;
default:
if (next >= '0' && next <= '9' && k + 3 < lastCharPos)
{
char octVal = getEncodedChar(text, k + 1, 3, 8);
if (octVal != 0xFFFF)
{
sb.append(octVal);
k += 3; // 3 oct digits
}
}
break;
}
continue; // do not append '~'
case '"':
sb.append(ch);
k = text.charAt(0) == '"' ? k + 1 : k;
continue;
case '\'':
sb.append(ch);
k = text.charAt(0) == '\'' ? k + 1 : k;
continue;
}
sb.append(ch);
}
return sb.toString();
}
/**
* Extract an encoded character on a specified number of digits ina specific base.
*
* @param text
* The text which contains the encoded character.
* @param k
* The offset where the encoding begins.
* @param n
* The number of digits used.
* @param base
* The base to be used when parsing the character.
*
* @return The decoded character or {@code 0xFFFF} on error.
*/
private static char getEncodedChar(String text, int k, int n, int base)
{
try
{
return (char) Integer.parseInt(text.substring(k, k + n), base);
}
catch (Exception e)
{
return 0xFFFF;
}
}
/**
* Process the definitions of the temp-tables (associated with the temporary buffers in the
* given list) and those from mutable databases to create the associated dictionary and p2o ASTs.
* <p>
* Also, it appends to the progress code string any needed 4GL code (like DEFINE BUFFER
* statements for explicit buffers, to refer back the originating table.
*
* @param dict
* The schema dictionary where the table definitions will be loaded.
* @param pcode
* Append any new progress code here.
* @param p2oFile
* The name of the {@link DataModelAst} tree with the temp-table objects for the query or
* validation statement.
* @param dictFile
* The name of the {@link ProgressAst} tree with the temp-table objects for conversion
* {@link SchemaDictionary}
* @param buffers
* The buffers used by the query or validation statement.
* @param tempTableNames
* The mapping of the new table names (to avoid temp-table name conflicts).
*
* @throws PersistenceException
* In case of problems during preparation of the temp-table schema and p2o ASTs.
*/
static void prepareDynamicTables(SchemaDictionary dict,
StringBuilder pcode,
String p2oFile,
String dictFile,
ArrayList<Buffer> buffers,
Map<Buffer, String> tempTableNames)
throws PersistenceException
{
Map<String, Object[]> astRoots = new HashMap<>();
long tableOrder = 0;
for (int i = 0; i < buffers.size(); i++)
{
BufferImpl impl = (BufferImpl) buffers.get(i);
RecordBuffer rbuf = impl.buffer();
rbuf.initialize();
String legacyBufName = impl.buffer().getLegacyName();
String legacyTableName = rbuf.getDmoInfo().legacyTable;
boolean renameTable = tempTableNames != null && rbuf.getDmoInfo().tempTable;
if (renameTable)
{
legacyTableName += "_tt_" + rbuf.getDmoInfo().sqlTable; // this should make it unique
tempTableNames.put(impl, legacyTableName);
}
if (rbuf.isTemporary() || rbuf.isMutable())
{
String dbName = rbuf.isTemporary() ? DatabaseManager.TEMP_TABLE_SCHEMA
: impl.getDbName().toJavaType();
Object[] rootObjs = astRoots.get(dbName);
DataModelAst p2oRoot;
ProgressAst schemaRoot;
StringBuilder sb;
if (rootObjs == null)
{
p2oRoot = buildRootDataModel(dbName, p2oFile);
p2oRoot.brainwash(p2oFile);
schemaRoot = buildRootSchema(dbName);
sb = new StringBuilder();
if (rbuf.isMutable())
{
// mark ASTs as mutable database
p2oRoot.putAnnotation(SchemaDictionary.MUTABLE_ANNOTATION, true);
schemaRoot.putAnnotation(SchemaDictionary.MUTABLE_ANNOTATION, true);
}
astRoots.put(dbName, new Object[] {p2oRoot, schemaRoot, sb});
}
else
{
p2oRoot = (DataModelAst) rootObjs[0];
schemaRoot = (ProgressAst) rootObjs[1];
sb = (StringBuilder) rootObjs[2];
}
sb.append(rbuf.getDMOAlias())
.append('-')
.append(rbuf.getDMOImplementationClass().getName())
.append(';');
// create the schema and p2o definition for this temp-table
DataModelAst model = rbuf.getDataModelAst();
if (model == null)
{
model = buildDataModelClass(impl, rbuf, p2oFile);
rbuf.setDataModelAst(model);
}
else
{
model.setParent(null);
model.setNextSibling(null);
}
if (renameTable)
{
model.putAnnotation(P2OLookup.HISTORICAL, legacyTableName);
}
ProgressAst table = rbuf.getSchemaTableAst();
if (table == null)
{
table = buildTempTable(model, rbuf);
rbuf.setSchemaTableAst(table);
}
else
{
table.setParent(null);
table.setNextSibling(null);
}
table.putAnnotation("lastOrder", tableOrder);
tableOrder = tableOrder + 10;
schemaRoot.graft(table);
p2oRoot.graft(model);
}
// must create explicit buffers, if the legacy buffer name is not the same as the legacy
// default buffer name (which is the same as the legacy table name).
if (!legacyBufName.equalsIgnoreCase(legacyTableName))
{
pcode.append("DEFINE BUFFER ").append(legacyBufName).append(" FOR ");
if (rbuf.isTemporary())
{
pcode.append("TEMP-TABLE ");
}
pcode.append(legacyTableName).append(".\n");
}
}
// save the P2O tree, to expose it to the conversion rules
for (Map.Entry<String, Object[]> entry : astRoots.entrySet())
{
AstManager.get().saveTree((DataModelAst) entry.getValue()[0], p2oFile, true);
ProgressAst schemaRoot = (ProgressAst) entry.getValue()[1];
AstManager.get().saveTree(schemaRoot, dictFile, true);
if (schemaRoot.isAnnotation(SchemaDictionary.MUTABLE_ANNOTATION))
{
// merge into existing schema (it may be partially statically defined)
dict.mergeFromAst(schemaRoot);
}
else
{
// load the temp schema
String key = entry.getValue()[2].toString();
if (!dict.reuseCachedScope(key))
{
dict.loadFromAst(schemaRoot, false, true);
dict.cacheScope(key);
}
}
}
}
/**
* Prepare the query AST by placing the appropriate annotations with the converted java names
* for the external program and also by created bogus
* {@link ProgressParserTokenTypes#DEFINE_TEMP_TABLE} nodes (which would be created from a
* {@code DEFINE TEMP-TABLE tt.} statement, with no fields); the latter is needed to allow
* proper conversion of the default buffer name.
*
* @param root
* The root AST of the converted progress code.
* @param buffers
* The buffers used by the query or validation statement.
* @param fileName
* The file name of the class containing the converted code.
* @param javaClassName
* The name of the generated java class.
* @param localTableName
* A map with local table names, in case of a name conflict.
*/
static void prepareTree(AnnotatedAst root,
ArrayList<Buffer> buffers,
String fileName,
String javaClassName,
Map<Buffer, String> localTableName)
{
// Create a bogus DEFINE TEMP-TABLE for each temp buffer (just to let their converted
// buffer names resolve properly).
// Dynamic buffers have unique DMO aliases, so "some-buffer" name may be converted to the
// alias like "someTable__1", while aliases in predicate will be converted using standard
// P2J rules, which will result to the converted alias "someTable". So below we explicitly
// specify converted DMO alias like "someTable__1" for the conversion rules using
// "force_dmo_alias" annotation.
for (int i = 0; i < buffers.size(); i++)
{
BufferImpl impl = (BufferImpl) buffers.get(i);
RecordBuffer rbuf = impl.buffer();
String legacyTableName = localTableName == null ? rbuf.getDmoInfo().legacyTable.toLowerCase() :
localTableName.getOrDefault(impl, rbuf.getDmoInfo().legacyTable.toLowerCase());
if (!rbuf.isTemporary())
{
continue;
}
ProgressAst stmt = createProgressAst(ProgressParserTokenTypes.STATEMENT, "statement");
stmt.putAnnotation("reachable", true);
ProgressAst defChild = createProgressAst(ProgressParserTokenTypes.DEFINE_TEMP_TABLE, "DEFINE");
defChild.putAnnotation("reachable", true);
defChild.putAnnotation("schemaname", legacyTableName);
defChild.putAnnotation("bufname", legacyTableName);
defChild.putAnnotation("dbname", "");
// the dynamic conversion might come up with a different buffer name than the one from the caller.
// Force the name of the buffer name to be the same as in statically converted code.
defChild.putAnnotation("force_dmo_alias", rbuf.getDMOAlias());
if (rbuf.isDynamic())
{
if (DynamicTablesHelper.isDynamicTableBuffer(rbuf))
{
defChild.putAnnotation("is_dynamic_table", true);
}
}
stmt.graft(defChild);
ProgressAst ttChild = createProgressAst(ProgressParserTokenTypes.KW_TEMP_TAB, "TEMP-TABLE");
ttChild.putAnnotation("reachable", true);
defChild.graft(ttChild);
ProgressAst symChild = createProgressAst(ProgressParserTokenTypes.SYMBOL, legacyTableName);
symChild.putAnnotation("reachable", true);
ttChild.graft(symChild);
root.graftAt(stmt, 0);
}
// put annotation related to name, package, etc
root.putAnnotation("file", fileName);
root.putAnnotation("classname", javaClassName);
root.putAnnotation("javaname", javaClassName);
root.putAnnotation("relative-name", fileName);
root.putAnnotation("basename", fileName);
root.putAnnotation("package-base", "");
root.putAnnotation("pkgname", "com.goldencode.p2j.persist.dynquery");
// make "force_dmo_alias" annotations, errors are ignored at this point, they will
// be raised later anyway
Iterator<Aast> iter = root.iterator();
while (iter.hasNext())
{
Aast chAst = iter.next();
if (chAst.getType() == ProgressParserTokenTypes.RECORD_PHRASE ||
chAst.getType() == ProgressParserTokenTypes.DEFINE_BUFFER)
{
Aast bufAst = (Aast) chAst.getFirstChild();
String bufName = (String) bufAst.getAnnotation("bufname");
if (bufName != null)
{
int dotPos = bufName.lastIndexOf(".");
if (dotPos >= 0)
{
bufName = bufName.substring(dotPos + 1);
}
int idx = locateBuffer(buffers, bufName);
if (idx >= 0)
{
BufferImpl impl = (BufferImpl) buffers.get(idx);
bufAst.putAnnotation("force_dmo_alias", impl.buffer().getDMOAlias());
if (DynamicTablesHelper.isDynamicTableBuffer(impl.buffer()))
{
bufAst.putAnnotation("is_dynamic_table", true);
}
}
}
}
}
}
/**
* Locate a {@link Buffer} with the given {@code name}, from the specified list of buffers,
* and return its position.
*
* @param buffers
* List of buffers to be searched.
* @param name
* The buffer name.
*
* @return The 0-based position of this buffer or -1 if not found.
*/
static int locateBuffer(ArrayList<Buffer> buffers, String name)
{
for (int i = 0; i < buffers.size(); i++)
{
if (name.equalsIgnoreCase(((BufferImpl) buffers.get(i)).buffer().getLegacyName()))
{
return i;
}
}
return -1;
}
/**
* Helper inner class used to send necesary data when calling
* {@link #injectVariableHelper(InjectVariableContext)}, and also be able to remember the
* changes made to the values of the class instance.
*/
private static class InjectVariableContext
{
/** The object to be converted. */
private BaseDataType baseDataTypeObj;
/** Variable used to save the new data type of the object. */
private ProgressAst type;
/** Variable used to save the name of the class to which the object was converted to. */
private String className;
/** String value of the object to be converted. Used in the cases of rowid and recid. */
private String text;
/**
* {@link ProgressParserTokenTypes} correspondent of the data type the object need
* to be converted to.
*/
private int var4glType;
/** The current type of the object that need to be converted. */
private int literalType;
/**
* {@link ProgressParserTokenTypes} value that corresponds to a variable with the type
* the objects needs converting to.
*/
private int varType;
}
}