TableHints.java
/*
** Module : TableHints.java
** Abstract : manages hints read from a UAST hints file about a schema table
**
** Copyright (c) 2004-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- -----------------------------------Description-----------------------------------
** 001 ECF 20050713 @21702 Created initial version. Contains info to override name conversions
** at the table and field level, and to define new associations between
** tables, or to provide supplemental data for existing associations.
** 002 NVS 20050816 @22154 Added DROP attribute and DISPOSITION element definition and
** processing. These define how the table or table fields are imported.
** Added methods to query DROP attribute for table and fields, as well
** as DISPOSITIONs.
** 003 NVS 20050817 @22159 Changed methods returning dispositions from Set to List as these are
** readily accepted by putAnnotation() method.
** 004 ECF 20060828 @29081 Added 'encoded' attribute to field node. Indicates whether a
** character field must be stored base-64 encoded (and decoded on read).
** 005 ECF 20070303 @32266 Added 'temp' attribute to table node. This specifies an explicit
** name to apply to a temp/work-table defined like the permanent
** table for which the hint applies.
** 006 SVL 20080326 @37684 Added support for additional ("synthetic") indexes loaded from the
** hints file.
** 007 VMN 20131114 Added support for custom conversion of fields with extents.
** 008 VMN 20140221 Using ELEM_EXTENT_NAME instead of ELEM_EXTENT_NAMES for custom
** conversion of fields with extents. Added ExtentHintField support.
** 009 VMN 20140501 Added support for literal replacements.
** 010 OM 20140407 Added support for optional SQL length specified in hints file.
** Generified collections.
** 011 IAS 20160619 Added support for the "read-only" hint
** 012 OM 20200108 Added support for "dirty-read" hint/attribute.
** 013 OM 20200925 Added support for generating denormalized extent fields in dynamic temp-table
** conversion.
** IAS 20210219 Added custom extent adjustment.
** ECF 20220528 Added table attribute to disable match phrase replacement.
** OM 20220722 The indices in denormalized fields are separated by one underscore.
** 014 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
** 015 SVL 20230516 Support for dirty-intra-read annotation.
** 016 DDF 20230620 Replaced static initialization of values from the directory configuration with
** a method called at server bootstrap.
** DDF 20230627 Used a standard handling for directory binding.
*/
/*
** 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.schema;
import java.util.*;
import java.util.logging.*;
import com.goldencode.p2j.cfg.*;
import com.goldencode.p2j.convert.*;
import com.goldencode.p2j.directory.*;
import com.goldencode.p2j.uast.*;
import com.goldencode.p2j.util.*;
import org.w3c.dom.*;
/**
* Describes a variety of hint information for a single schema table.
*/
public class TableHints
{
/** Field element of table and association elements */
private static final String ELEM_FIELD = "field";
/** Phrase element to define a customized name conversion */
private static final String ELEM_PHRASE = "phrase";
/** Disposition element to define a type of import processing */
private static final String ELEM_DISP = "disposition";
/** Relational association between this table and another */
private static final String ELEM_ASSOCIATION = "association";
/** Synthetic index element */
private static final String ELEM_INDEX = "index";
/** Field element of index */
private static final String ELEM_INDEX_FIELD = "index-field";
/** Element to define custom processing table fields with extent */
private static final String ELEM_CUSTOM_EXTENT = "custom-extent";
/** Element to define custom naming conversion of extent field */
private static final String ELEM_EXTENT_NAME = "extent-field";
/** Name attribute of table and field elements */
private static final String ATTR_NAME = "name";
/** Read-only attribute of table */
private static final String ATTR_READ_ONLY = "read-only";
/** {@code dirty-read} attribute of table. */
private static final String ATTR_DIRTY_READ = "dirty-read";
/** {@code dirty-intra-read} attribute of table. */
private static final String ATTR_DIRTY_INTRA_READ = "dirty-intra-read";
/** Drop attribute of table and field elements */
private static final String ATTR_DROP = "drop";
/** Temp-table raw (pre-conversion) name attribute */
private static final String ATTR_TEMP = "temp";
/** Encoded attribute of field element */
private static final String ATTR_ENCODED = "encoded";
/** Unique attribute of index element */
private static final String ATTR_UNIQUE = "unique";
/** Direction attribute of index field element */
private static final String ATTR_DIRECTION = "direction";
/** Type attribute of association element */
private static final String ATTR_TYPE = "type";
/** Attribute to disable match phrase replacement, except for invalid names (table and all fields) */
private static final String ATTR_NO_REPLACE = "no-phrase-replace";
/** Match attribute of phrase element */
private static final String ATTR_MATCH = "match";
/** Replace attribute of phrase element */
private static final String ATTR_REPLACE = "replace";
/** No-inherit attribute of phrase element */
private static final String ATTR_NO_INHERIT = "no-inherit";
/** References attribute of phrase element */
private static final String ATTR_REFERENCES = "references";
/** Foreign attribute of association elements */
private static final String ATTR_FOREIGN = "foreign";
/** Dmo attribute of table element */
private static final String ATTR_DMO = "dmo";
/** Sql-table attribute of table element */
private static final String ATTR_SQL_TABLE = "sql-table";
/** Property attribute of field element */
private static final String ATTR_PROPERTY = "property";
/** Sql-column attribute of field element */
private static final String ATTR_SQL_COLUMN = "sql-column";
/** SQL-size attribute to define a SQL size hint for character fields */
private static final String ATTR_SQL_SIZE = "sql-size";
/** Key under which join fields are stored in an association map */
private static final String KEY_FIELDS = "fields";
/** Key under which index fields are stored in the indexes map */
private static final String KEY_INDEX_FIELDS = "index-fields";
/** Value of type for many-to-one association */
private static final String TYPE_MANY_TO_ONE = "many-to-one";
/** Value of type for one-to-many association */
private static final String TYPE_ONE_TO_MANY = "one-to-many";
/** Value of type for one-to-one association */
private static final String TYPE_ONE_TO_ONE = "one-to-one";
/** Maximum size of a character field in Progress is about 32K. */
private static final int MAX_SQL_SIZE = 32 * 1024 * 1024;
/** Logger */
private static final ConversionStatus LOG = ConversionStatus.get(TableHints.class);
/**
* Flag for denormalized extent properties in dynamic temp-tables. When enabled from directory,
* {@code "persistence/denormalize-dynamic-temp-tables"}, the extent fields will NOT use secondary tables
* for SQL storage. <p>
* Default is disabled.
*/
private static boolean denormalizeDynamicTempTables = false;
/** Table escape attribute, null means that attribute is not set. */
private Boolean escaped = null;
/** Field escape attributes. */
private final Map<String, Boolean> escapedFields = new HashMap<>();
/**
* Dmo conversion replacement for the table. Specifies the exact DMO class name to generate
* for the Java DMO interface (and implementation class, with an appended "Impl")
*/
private String dmo;
/**
* SQL table conversion replacement for the table. Specifies the exact table name to
* generate for the DDL for this table
*/
private String sqlTable = null;
/**
* DMO properties conversion replacements for the table. Specifies the exact DMO property
* name to generate for the Java DMO interface and implementation class getter/setter methods.
*/
private final Map<String, String> properties = new HashMap<>();
/**
* SQL columns conversion replacements for the table. Specifies the exact column name to
* generate for the DDL for specified field
*/
private final Map<String, String> sqlColumns = new HashMap<>();
/** Name conversion replacements for the table inherited by fields */
private final Map<String, String> inheritPhrases = new HashMap<>();
/** Name conversion replacements for the table not inherited by fields */
private final Map<String, String> noInheritPhrases = new HashMap<>();
/** Name conversion replacements for fields within table */
private final Map<String, Map<String, String>> fieldPhrases = new HashMap<>();
/**
* SQL sizes for (character) fields extracted from .hints file.
* If some fields from this table are not in this map the maximum SQL size is used when
* declaring the DDL for creating current SQL table.
*/
private final Map<String, Integer> sqlSizes = new HashMap<>();
/** Fields which require base-64 encoding for storage */
private final Set<String> encodedFields = new HashSet<>();
/** Association definitions for this table */
private final Map<Object, Map<String, Object>> associations = new HashMap<>();
/** Table dispositions for this table */
private final Set<String> tableDisp = new HashSet<>();
/** Field drop flags for this table */
private final Set<String> fieldDrop = new HashSet<>();
/** Field dispositions for this table */
private final Map<String, Set<String>> fieldDisp = new HashMap<>();
/** Map containing information about synthetic indexes and their fields */
private final Map<String, Map<String, Object>> indexes = new HashMap<>();
/** Map containing information about custom conversion of fields with extents*/
private Map<String, List <ExtentHintField>> customExtents = null;
/** Name of table described by this hint instance */
private final String name;
/** Pre-conversion name of temp-table based upon underlying table */
private final String tempTableName;
/** Flag indicating that match phrase replacements are disabled (except the standard ones) */
private final boolean noPhraseReplace;
/** Table drop flag for this table */
private final boolean tableDrop;
/** Flag indicating that the table is read-only */
private final boolean readOnly;
/** Flag indicating that the table needs to be tracked by the dirty share manager across sessions. */
private final boolean dirtyRead;
/**
* Flag indicating that the table needs to be tracked by the dirty share manager within the same session.
*/
private final boolean dirtyIntraRead;
/**
* Method called at server bootstrap that initializes values from the directory
* configuration. Until this method is called, default values are used.
*/
public static void bootstrap()
{
DirectoryService ds = DirectoryService.getInstance();
if (ds != null)
{
if (!ds.bind())
{
throw new RuntimeException("Directory bind failed");
}
try
{
// configure denormalized extent in dynamic temp-tables, default is disabled, i.e. the extent
// fields will use secondary tables for SQL storage
String path = "persistence/denormalize-dynamic-temp-tables";
denormalizeDynamicTempTables = Utils.getDirectoryNodeBoolean(ds, path, false, false);
}
finally
{
ds.unbind();
}
}
}
/**
* Check whether the extent properties in dynamic temp-tables should be denormalized. When enabled from
* directory, {@code "persistence/denormalize-dynamic-temp-tables"}, the extent fields will NOT use
* secondary tables for SQL storage. <p>
*
* @return {@code true} if the extent properties in dynamic temp-tables should be denormalized.
*/
public static boolean isDenormalizeDynamicTempTables()
{
return denormalizeDynamicTempTables;
}
/**
* Constructor which configures hint from an XML element.
*
* @param table
* XML element describing the table hint. Corresponds with
* <code>table</code> tag within the hints file.
*/
public TableHints(Element table)
{
NodeList list;
int len;
name = table.getAttribute(ATTR_NAME);
readOnly = "true".equalsIgnoreCase(table.getAttribute(ATTR_READ_ONLY));
dirtyRead = "true".equalsIgnoreCase(table.getAttribute(ATTR_DIRTY_READ));
dirtyIntraRead = "true".equalsIgnoreCase(table.getAttribute(ATTR_DIRTY_INTRA_READ));
tableDrop = "true".equalsIgnoreCase(table.getAttribute(ATTR_DROP));
escaped = "true".equalsIgnoreCase(table.getAttribute(HintsConstants.ATTR_ESCAPE));
// Read temp table raw name.
String ttName = table.getAttribute(ATTR_TEMP);
tempTableName = (ttName == null || ttName.trim().length() == 0) ? null : ttName;
// Read table dmo.
if (table.hasAttribute(ATTR_DMO))
{
dmo = table.getAttribute(ATTR_DMO);
}
// Read table sql-table attribute.
if (table.hasAttribute(ATTR_SQL_TABLE))
{
sqlTable = table.getAttribute(ATTR_SQL_TABLE);
}
// Read property and sql-column field attributes.
list = table.getElementsByTagName(ELEM_FIELD);
len = list.getLength();
for (int i = 0; i < len; i++)
{
Element field = (Element) list.item(i);
String fieldName = field.getAttribute(ATTR_NAME);
if (field.hasAttribute(HintsConstants.ATTR_ESCAPE))
{
escapedFields.put(fieldName,
"true".equalsIgnoreCase(field.getAttribute(HintsConstants.ATTR_ESCAPE)));
}
if (field.hasAttribute(ATTR_PROPERTY))
{
properties.put(fieldName, field.getAttribute(ATTR_PROPERTY));
}
if (field.hasAttribute(ATTR_SQL_COLUMN))
{
sqlColumns.put(fieldName, field.getAttribute(ATTR_SQL_COLUMN));
}
}
// check for no-phrase-replace attribute at the table level, which disables match phrase replacement
// for the table and all its fields and indices (even if phrase elements appear in the table element)
this.noPhraseReplace = "true".equalsIgnoreCase(table.getAttribute(ATTR_NO_REPLACE));
// Read table and field phrases.
list = table.getElementsByTagName(ELEM_PHRASE);
len = list.getLength();
for (int i = 0; i < len; i++)
{
Element phrase = (Element) list.item(i);
Element parent = (Element) phrase.getParentNode();
Map<String, String> map;
// Is parent a field element...
if (parent.getTagName().equals(ELEM_FIELD))
{
String fieldName = parent.getAttribute(ATTR_NAME);
map = fieldPhrases.get(fieldName);
if (getProperty(fieldName) == null)
{
if (map == null)
{
map = new HashMap<>();
fieldPhrases.put(fieldName, map);
}
}
else
{
if (map != null && map.size() > 0)
{
LOG.log(Level.WARNING,
"Ignoring phrase hints for table " + name + ", " + "field " + fieldName + ", " +
"because at least one of sql-column or property attributes is set!");
map = null;
}
}
}
// ...or a table element?
else
{
map = "true".equals(phrase.getAttribute(ATTR_NO_INHERIT))
? noInheritPhrases
: inheritPhrases;
if (getDmo() != null && map.size() > 0)
{
LOG.log(Level.WARNING, "Ignoring phrase hints for table " + name +
" because at least one of sql-table or dmo attributes is set!");
map = null;
}
}
if (map != null)
{
map.put(phrase.getAttribute(ATTR_MATCH), phrase.getAttribute(ATTR_REPLACE));
}
}
// Read field SQL-SIZE hints. At this moment only for character will be handled,
// but sql sizes for other types can/will be added based on necessities.
// Syntax: to specify the SQL-SIZE, use the 'sql-size' attribute of the 'field' sub-element
// of a 'table' element. The value of the attribute is SQL length in datatype
// items (eg.: char(n) will take n bytes but nchar(n) will use 2*n bytes).
list = table.getElementsByTagName(ELEM_FIELD);
len = list.getLength();
for (int i = 0; i < len; i++)
{
Element field = (Element) list.item(i);
if (!field.hasAttribute(ATTR_SQL_SIZE))
{
continue;
}
String fieldSize = field.getAttribute(ATTR_SQL_SIZE);
int sqlSize = -1;
try
{
sqlSize = Integer.parseInt(fieldSize);
}
catch (NumberFormatException e)
{
// drop this exception, warning will be printed below because sqlSize remains -1.
}
if (sqlSize > 0 && sqlSize < MAX_SQL_SIZE)
{
sqlSizes.put(field.getAttribute(ATTR_NAME), sqlSize);
}
// otherwise print some warning ?
}
// Retrieve associations for table.
list = table.getElementsByTagName(ELEM_ASSOCIATION);
len = list.getLength();
for (int i = 0; i < len; i++)
{
int size;
Element assoc = (Element) list.item(i);
// Store attributes in a map, each of which defines the properties
// of an association and a simple key/value string pair.
NamedNodeMap attrs = assoc.getAttributes();
Map<String, Object> map = new HashMap<>();
size = attrs.getLength();
for (int j = 0; j < size; j++)
{
Attr attr = (Attr) attrs.item(j);
map.put(attr.getName(), attr.getValue());
}
// Handle fields which define the join specially. These are
// collected into a set and stored under the key "fields" in the
// associations map.
NodeList fields = assoc.getElementsByTagName(ELEM_FIELD);
size = fields.getLength();
Set<String> set = null;
for (int j = 0; j < size; j++)
{
Element field = (Element) fields.item(i);
if (set == null)
{
set = new HashSet<>();
}
set.add(field.getAttribute(ATTR_NAME));
}
if (set != null)
{
map.put(KEY_FIELDS, set);
}
// Map associations by the name of the table they reference.
associations.put(map.get(ATTR_REFERENCES), map);
}
// Read field drops and encoded fields.
list = table.getElementsByTagName(ELEM_FIELD);
len = list.getLength();
for (int i = 0; i < len; i++)
{
Element field = (Element) list.item(i);
String fieldName = field.getAttribute(ATTR_NAME);
if ("true".equalsIgnoreCase(field.getAttribute(ATTR_DROP)))
{
fieldDrop.add(fieldName);
}
if ("true".equalsIgnoreCase(field.getAttribute(ATTR_ENCODED)))
{
encodedFields.add(fieldName);
}
}
// Read table and field dispositions.
list = table.getElementsByTagName(ELEM_DISP);
len = list.getLength();
for (int i = 0; i < len; i++)
{
Element disp = (Element) list.item(i);
Set<String> set;
Element parent = (Element) disp.getParentNode();
// Is parent a field element...
if (parent.getTagName().equals(ELEM_FIELD))
{
String fieldName = parent.getAttribute(ATTR_NAME);
set = fieldDisp.get(fieldName);
if (set == null)
{
set = new HashSet<>();
fieldDisp.put(fieldName, set);
}
}
// ...or a table element?
else
{
set = tableDisp;
}
set.add(disp.getAttribute(ATTR_NAME));
}
/*
* Read custom extent elements into the map like field-name -> list of extent property names.
*
* Using this hint for fields with extent:
*
* a) customExtents == null
* It means that this table has no extent fields with custom conversion.
*
* b) customExtents.get(field-name) == null
* It means that this field has no custom conversion.
*
* c) customExtents.get(field-name) returns empty list
* It means that this field has custom conversion with default naming conversion
* (e.g., field(1)-->field_1, field(2)-->field2, ... field(N)-->fieldN).
*
* d) customExtents.get(field-name) returns non-empty list
* It means that this field has custom conversion with configurable, depending on customer
* preferences (e.g., allowing individual indexes to be named explicitly, as in,
* hours(1)-->monday_hours, hours(2)-->tuesday_hours, etc.).
*/
list = table.getElementsByTagName(ELEM_CUSTOM_EXTENT);
len = list.getLength();
if (len > 0)
{
customExtents = new HashMap<>();
for (int i = 0; i < len; i++)
{
Element extent = (Element) list.item(i);
NodeList fieldsList = extent.getElementsByTagName(ELEM_FIELD);
int fieldsListLen = fieldsList.getLength();
if (fieldsListLen == 0)
{
// it overrides custom extents for separate fields
customExtents.clear();
break;
}
else
{
for (int k = 0; k < fieldsListLen; k++)
{
Element field = (Element) fieldsList.item(k);
NodeList extentNodes = field.getElementsByTagName(ELEM_EXTENT_NAME);
List<ExtentHintField> extentHintFields = new ArrayList<>();
for (int j = 0; j < extentNodes.getLength(); j ++)
{
Element extentHintField = (Element) extentNodes.item(j);
extentHintFields.add(new ExtentHintField(
extentHintField.getAttribute("name"),
extentHintField.getAttribute("label"),
extentHintField.getAttribute("description")));
}
customExtents.put(field.getAttribute("name"), extentHintFields);
}
}
}
}
if (customExtents == null && denormalizeDynamicTempTables && Configuration.isRuntimeConfig())
{
customExtents = new HashMap<>();
}
/*
* Read table synthetic indexes into the map like
* index-name -> 'unique' -> true/false
* 'index-fields' -> field-name -> 'direction' -> asc/desc
* field-name2 -> ...
* index-name2 -> ...
*/
list = table.getElementsByTagName(ELEM_INDEX);
len = list.getLength();
for (int i = 0; i < len; i++)
{
Element index = (Element) list.item(i);
String indexName = index.getAttribute(ATTR_NAME);
Map<String, Object> map = new HashMap<>();
map.put(ATTR_UNIQUE, index.getAttribute(ATTR_UNIQUE));
NodeList fields = index.getElementsByTagName(ELEM_INDEX_FIELD);
Map<String, Map<String, String>> fieldsMap = new HashMap<>();
int fieldsLen = fields.getLength();
for (int j = 0; j < fieldsLen; j++)
{
Element field = (Element) fields.item(j);
String fieldName = field.getAttribute(ATTR_NAME);
Map<String, String> propertiesMap = new HashMap<>();
propertiesMap.put(ATTR_DIRECTION, field.getAttribute(ATTR_DIRECTION));
fieldsMap.put(fieldName, propertiesMap);
}
map.put(KEY_INDEX_FIELDS, fieldsMap);
indexes.put(indexName, map);
}
}
/**
* Get the name of the table described by this hint.
*
* @return Table name.
*/
public String getName()
{
return name;
}
/**
* Get the pre-conversion name of a temp table which is based upon the
* underlying table.
*
* @return Raw temp table name to be passed to the name converter, or
* <code>null</code> if no explicit temp-table name exists.
*/
public String getTempTableName()
{
return tempTableName;
}
/**
* Get the custom DMO conversion replacement for the table.
*
* @return Replacement DMO name for this table. Taken from "dmo" attribute or from
* "sql-table" attribute and <code>null</code> if property hint is not
* specified in any of these attributes.
*/
public String getDmo()
{
return dmo == null ? sqlTable : dmo;
}
/**
* Get the custom replacement name used for sql table conversion.
*
* @return Replacement name for this table. Taken from "sql-table" attribute or from
* "dmo" attribute and <code>null</code> if property hint is not
* specified in any of these attributes.
*/
public String getSqlTable()
{
return sqlTable == null ? dmo : sqlTable;
}
/**
* Get the custom name used for conversion of DMO property for the specified field in this
* table.
*
* @param field
* Name of the field for retrieve "property" attribute or
* "sqlColumn" attribute.
*
* @return Custom name used for conversion of DMO property for the specified field in this
* table. Taken from "property" attribute or from "sql-column"
* attribute and <code>null</code> if property hint is not specified in any of
* these attributes.
*/
public String getProperty(String field)
{
String property = properties.get(field);
return property == null ? sqlColumns.get(field) : property;
}
/**
* Get the custom name used for conversion of sql column for the specified field in this
* table.
*
* @param field
* Name of the field for retrieve "sqlColumn" attribute or
* "property" attribute.
*
* @return Custom name used for conversion of sql column for the specified field in this
* table. Taken from "sql-column" attribute or from "property"
* attribute and <code>null</code> if property hint is not specified in any of
* these attributes.
*/
public String getSqlColumn(String field)
{
String sqlColumn = sqlColumns.get(field);
return sqlColumn == null ? properties.get(field) : sqlColumn;
}
/**
* Indicate whether this hint disables match phrase replacement (except for the standard replacements
* which ensure a valid/legal name).
*
* @return {@code true} if replacements are disabled, else {@code false}.
*/
public boolean isNoPhraseReplace()
{
return noPhraseReplace;
}
/**
* Get the custom replacement map (if any) used for name conversion for
* this table. This is a combination of all inheritable and
* non-inheritable phrases for this table.
*
* @return Replacement map for this table, which may be empty if no
* phrase hints are defined, but will not be <code>null</code>.
*/
public Map<String, String> getNameReplacements()
{
Map<String, String> map = new HashMap<>();
map.putAll(inheritPhrases);
map.putAll(noInheritPhrases);
return map;
}
/**
* Get the custom replacement map (if any) used for name conversion for
* the specified field in this table. This is a combination of all
* inheritable phrases for this table and the phrases defined specifically
* for the named field.
*
* @param field
* Name of the field for which to retrieve replacement phrases.
*
* @return Replacement map for this table, which may be empty if no
* phrase hints are defined, but will not be <code>null</code>.
*/
public Map<String, String> getNameReplacements(String field)
{
Map<String, String> map = new HashMap<>();
map.putAll(inheritPhrases);
Map<String, String> fieldMap = fieldPhrases.get(field);
if (fieldMap != null)
{
map.putAll(fieldMap);
}
return map;
}
/**
* Get the SQL size hint for a (character) field. If not defined, <code>null</code> is
* returned.
*
* @param field
* Name of the field for which to retrieve sql size hint.
*
* @return The SQL size hint for requested field, or <code>null</code> if not defined.
*/
public Integer getSqLSizeHint(String field)
{
return sqlSizes.get(field);
}
/**
* Report whether the specified field must be base-64 encoded for data
* storage (and decoded upon retrieval).
*
* @param field
* Name of the field to test for the "encoded"
* attribute.
*
* @return <code>true</code> if data must be base-64 encoded/decoded.
*/
public boolean isFieldEncoded(String field)
{
return encodedFields.contains(field);
}
/**
* Report whether the database column name for specified field should be escaped.
*
* @param field
* Name of the field to test for escaping.
*
* @return <code>true</code> if column should be escaped, <code>false</code> if column
* should not be escaped, <code>null</code> if attribute "escape" is set
* neither for field nor for table.
*/
public Boolean isFieldEscaped(String field)
{
if (escapedFields.containsKey(field))
{
return escapedFields.get(field);
}
return escaped;
}
/**
* Report whether the database table name for this table should be escaped.
*
* @return <code>true</code> if table name should be escaped, <code>false</code> if table
* name should not be escaped, <code>null</code> if attribute "escape" is
* set not set for this table.
*/
public Boolean isEscaped()
{
return escaped;
}
/**
* Get an iterator on the names of all tables referenced by relational
* associations from this table, in no particular order.
*
* @return Iterator on referenced table names.
*/
public Iterator<?> associatedTables()
{
return associations.keySet().iterator();
}
/**
* Get the schema parser token type associated with the association (if
* any) described by this hint between this table and the table it
* references. Valid token types are:
* <ul>
* <li><code>MANY_TO_ONE</code>
* <li><code>ONE_TO_MANY</code>
* <li><code>ONE_TO_ONE</code>
* </ul>
*
* @param references
* Name of table which the association references.
*
* @return One of the above token types, or <code>-1</code> if either the
* association does not exist, or an unrecognized association
* type was found.
*/
public int getAssociationType(String references)
{
int ttype = -1;
Map<String, Object> map = associations.get(references);
if (map != null)
{
String type = (String) map.get(ATTR_TYPE);
switch (type)
{
case TYPE_MANY_TO_ONE:
ttype = SchemaParserTokenTypes.MANY_TO_ONE;
break;
case TYPE_ONE_TO_MANY:
ttype = SchemaParserTokenTypes.ONE_TO_MANY;
break;
case TYPE_ONE_TO_ONE:
ttype = SchemaParserTokenTypes.ONE_TO_ONE;
break;
}
}
return ttype;
}
/**
* Indicate whether the table described by this hint represents the
* foreign key end of an association with the specified table. This is
* only relevant for one-to-one associations.
*
* @param references
* Name of table which the association references.
*
* @return <code>true</code> if this table contains a foreign key for
* the referenced table. <code>false</code> if not, or if no
* association was found with table <code>references</code>.
*/
public boolean isForeignAssociation(String references)
{
Map<String, Object> map = associations.get(references);
return (map != null && "true".equals(map.get(ATTR_FOREIGN)));
}
/**
* Adjust custom extents' list
* @param fieldName
* field name
* @param extentSize
* extent size
* @return adjusted custom extents' list
*/
public List<ExtentHintField> adjustCustomExtents(String fieldName, int extentSize)
{
List<ExtentHintField> fhints = getCustomFieldExtent(fieldName);
if (fhints == null)
{
return fhints;
}
int nf = fhints.size();
if (nf >= extentSize)
{
return fhints;
}
String base = (nf == 0)
? ClassDefinition.nconvert.convert(fieldName, NameConverter.TYPE_COLUMN)
: fhints.get(nf - 1).getName();
while (fhints.size() < extentSize)
{
fhints.add(new ExtentHintField(String.format("%s_%d", base, fhints.size() + 1), "", ""));
}
if (nf == 0)
{
customExtents.put(fieldName, fhints);
}
return fhints;
}
/**
* Get the list of extent property names for given field with extent:
* several property names in case of custom extent conversion.
*
* @param fieldName
* Field name.
*
* @return list of property names.
*/
public List<ExtentHintField> getCustomFieldExtent(String fieldName)
{
if (customExtents == null)
{
return null;
}
else if (customExtents.size() == 0)
{
return new ArrayList<>();
}
else
{
return customExtents.get(fieldName);
}
}
/**
* Get the set of field names which are used to form a relational
* association between the table described by this hint and the table
* it references.
*
* @param references
* Name of table which the association references.
*
* @return Set of fields, or <code>null</code> if this hint does not
* describe the referenced association or describes only the
* basic attributes of the association and not the joining
* fields.
*/
public Set<?> getAssociatedFields(String references)
{
Map<String, Object> map = associations.get(references);
return (Set<?>) (map == null ? null : map.get(KEY_FIELDS));
}
/**
* Indicate whether the table described by this hint is to be dropped
* during the database import.
*
* @return <code>true</code> if this table is to be dropped.
*/
public boolean isTableDropped()
{
return tableDrop;
}
/**
* Indicate whether the table described by this hint is read-only
*
* @return {@code true} if this table is read-only.
*/
public boolean isReadOnly()
{
return readOnly;
}
/**
* Indicate whether the table described by this hint needs to be tracked by the dirty share
* manager across sessions.
*
* @return {@code true} if this table is dirty-read.
*/
public boolean isDirtyRead()
{
return dirtyRead;
}
/**
* Indicate whether the table described by this hint needs to be tracked by the dirty share
* manager within the same session.
*
* @return {@code true} if this table is dirty-intra-read.
*/
public boolean isDirtyIntraRead()
{
return dirtyIntraRead;
}
/**
* Indicate whether the field of the table described by this hint is
* to be dropped during the database import.
*
* @param field
* field name to query.
*
* @return <code>true</code> if this table is to be dropped.
*/
public boolean isFieldDropped(String field)
{
return fieldDrop.contains(field);
}
/**
* Get the list of table dispositions defined for the table described
* by this hint.
*
* @return List of dispositions, possibly empty.
*/
public List<String> getTableDispositions()
{
List<String> list = new LinkedList<>();
list.addAll(tableDisp);
return list;
}
/**
* Get the list of field dispositions defined for the table described by this hint.
*
* @param field
* field name to get the dispositions for.
*
* @return List of dispositions, possibly empty.
*/
public List<String> getFieldDispositions(String field)
{
List<String> list = new LinkedList<>();
Set<String> set = fieldDisp.get(field);
if (set != null)
{
list.addAll(set);
}
return list;
}
/**
* Get the set of synthetic index names.
*
* @return Set of index names, possibly empty.
*/
public Set<String> getIndexNames()
{
return indexes.keySet();
}
/**
* Determine whether the specified field in the specified index is sorted
* in the descending order.
*
* @param indexName
* The name of the index that contains the required field.
*
* @param fieldName
* Field name to query.
*
* @return <code>true</code> if the required field is sorted in the
* descending order. <code>false</code> otherwise.
*/
public boolean isIndexFieldDescending(String indexName, String fieldName)
{
Map<String, Object> indexMap = indexes.get(indexName);
Map<?,?> fieldMap = (Map<?,?>) ((Map<?,?>)indexMap.get(KEY_INDEX_FIELDS)).get(fieldName);
return "desc".equalsIgnoreCase((String)fieldMap.get(ATTR_DIRECTION)) ||
"descend".equalsIgnoreCase((String)fieldMap.get(ATTR_DIRECTION));
}
/**
* Determine whether the specified index is unique.
*
* @param indexName
* Index to query.
*
* @return <code>true</code> if the required index is unique.
* <code>false</code> otherwise.
*/
public boolean isIndexUnique(String indexName)
{
Map<String, Object> indexMap = indexes.get(indexName);
return "true".equalsIgnoreCase((String)indexMap.get(ATTR_UNIQUE));
}
/**
* Get the set of field names of the specified index.
*
* @param indexName
* The name of the index to get the field names for.
*
* @return Set of index field names, possibly empty.
*/
public Set<?> getIndexFieldNames(String indexName)
{
Map<String, Object> indexMap = indexes.get(indexName);
return ((Map<?,?>)indexMap.get(KEY_INDEX_FIELDS)).keySet();
}
}