TempTableHelper.java
/*
** Module : TempTableHelper.java
** Abstract : Helper class which generates temp table DDL
**
** Copyright (c) 2004-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- -----------------------------------Description-----------------------------------
** 001 ECF 20080311 @37509 Created initial version. Extracted classes
** from DatabaseManager. Added computed column
** support. Added capability of making index
** names unique by session.
** 002 CA 20080424 @38106 Refactored to allow generation of global temp
** tables and indices.
** 003 CA 20080430 @38159 Refactored to allow correct generation for
** indices: the indices must be retrieved from
** the "main" database or the dmo_index.xml file
** depending on the database type: dirty or not.
** 004 ECF 20080512 @38505 Refactored to allow access from sub-package.
** Made class and several static methods public.
** 005 ECF 20080611 @38699 Removed queryIndexData(). The common, static
** variant of this method in Persistence is now
** used instead.
** 006 CA 20080815 @39469 Support API change in DBUtils.
** 007 ECF 20081107 @40406 Moved multiplex ID position in index. It used
** to be the first element in any index, but
** that sometimes resulted in a less efficient
** index being selected for a query plan. Now it
** is either the last element, or second to last
** (if the primary key is part of the index).
** 008 SVL 20120331 Upgraded to Hibernate 4.
** 009 SVL 20140106 Changed access modifier for get() function.
** 010 OM 20140410 Fixed support case-sensitive fields in indexes.
** 011 ECF 20140822 Made non-null columns optional (decided by subclasses). Replaced
** Apache commons logging with J2SE logging.
** 012 EVL 20160223 Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
** 013 OM 20160608 Changed signature of dialect.getComputedColumnFormula().
** 014 OM 20190226 Work around for extent fields in word indexes.
** 015 CA 20200529 Added TRANSACTIONAL to CREATE INDEX, DROP TABLE/INDEX and ALTER TABLE temp-table
** schema commands. Requires new patched 1.4.197 H2 version.
** CA 20200622 Small performance improvement - use IdentityHashMap if the key is java.lang.Class.
** CA 20200714 Added the implicit index name, used during temp-table parameter copy.
** OM 20200924 P2JIndexComponent carries multiple information to avoid map lookups for them.
** OM 20201001 Improved DMO manipulation performance by caching slow Property annotation access.
** CA 20201005 Do not emit NULLS option in the index for a _multiplex or PK column (as these are
** mandatory). Removed the parent__id index for a child extent table (otherwise H2
** will not properly match on the (parent__id, list__index) index.
** OM 20200304 Normalized Property.scale to default value of 10.
** IAS 20210317 Word tables support for _temp db.
** OM 20220721 Added partial conversion-time MariaDb dialect support.
** OM 20220727 FieldId and PropertyId are different for denormalized extent fields.
** ECF 20220822 Fixed compile errors from WordTable changes.
** OM 20220913 Enhanced dialect API for sorting nulls.
** OM 20221012 Fixed regression in previous commit.
** OM 20221027 H2 dialect requires that the index names to be truly unique.
** SVL 20230110 P2JIndex.components() provides direct access to the components array in order to
** improve performance.
** 016 AL2 20230210 Mark the computed columns as INVISIBLE. This works as a hint for the H2 engine.
** RAA 20230208 Added "no-undo" as a temp table postfix in constructor.
** AL2 20230303 Optimized the use of the invisible specificator use.
** DDF 20230306 Removed usage of computed columns. Added a parameter to getSqlMappedType()
** (refs: #7108).
** 017 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
** 018 RAA 20230630 The direction of recid now matches the last component's direction.
** 019 AL2 20231012 Added recid as trailing component even to unique indexes. Added WITH_MULTIPLEX.
** AL2 20231018 Renamed WITH_MULTIPLEX into MULTIPLEXED.
** AL2 20231020 Allow MULTIPLEXED keyword only for temp database, exluding dirty.
** AL2 20231020 Allow MULTIPLEXED keyword only non-extent tables.
** 020 RAA 20231127 Added isDirty condition in constructor.
** 021 OM 20240901 Improved Database API.
*/
/*
** 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 java.util.*;
import java.util.logging.*;
import com.goldencode.p2j.persist.dialect.*;
import com.goldencode.p2j.persist.orm.*;
import com.goldencode.p2j.persist.orm.WordTable.*;
import com.goldencode.p2j.util.*;
import com.goldencode.p2j.util.logging.CentralLogger;
import com.goldencode.util.*;
/**
* Abstract helper class which generates and caches SQL phrases to create and
* drop temporary tables, and to create indexes in those tables. This class
* contains the following service methods intended for use by external
* classes:
* <ul>
* <li>{@link #sqlTempTableCreate} - gets a dialect-specific SQL statement
* to create a particular temp table.
* <li>{@link #sqlTempTableDrop} - gets a dialect-specific SQL statement
* to drop a particular temp table.
* <li>{@link #sqlTempTableIndexCreate} - gets a list of dialect-specific
* SQL statements to create indexes for a particular temp table.
* <li>{@link #sqlTempTableIndexDrop} - gets a list of dialect-specific
* SQL statements to drop indexes for a particular temp table.
* </ul>
*/
public abstract class TempTableHelper
{
/** Key which will be replaced by a unique suffix in certain DDL */
public static final String UNIQUE_SUFFIX_KEY = "1";
/** Logger */
private static final CentralLogger LOG = CentralLogger.get(TempTableHelper.class);
/** Map of DMO interfaces to helper objects */
private static final Map<Class<?>, TempTableHelper> cache = new IdentityHashMap<>();
/** Name of table schema */
protected final String schema;
/** The used database */
protected final Database database;
/** Dialect-specific SQL statement(s) to create temp table(s) */
private final List<String> sqlCreateTables;
/** Dialect-specific SQL statement(s) to drop temp table(s) */
private final List<String> sqlDropTables;
/** Dialect-specific SQL statement(s) to create temp table index(es) */
private final List<String> sqlCreateIndexes;
/** Dialect-specific SQL statement(s) to drop temp table index(es) */
private final List<String> sqlDropIndexes;
/** DMO interface which provides the public API to the temp table */
private final Class<? extends DataModelObject> dmoIface;
/** The implicit SQL index name for this table (the primary index, first in alphabetical order or 'recid'). */
private final String implicitSqlIndexName;
/** The implicit index name for this table (the primary index, first in alphabetical order or 'recid'). */
private final String implicitIndexName;
/**
* Return the SQL statement text used to create a temporary table for the
* given DMO class, using the specified database dialect.
*
* @param database
* The {@code dmoIface}'s backing database.
* @param dmoIface
* DMO interface which represents the temp table.
*
* @return SQL statement(s) for the temp table create operation(s), using
* syntax specific to the dialect of the given Persistence
* instance.
*
* @throws RuntimeException
* if any error occurs generating the SQL statement. This will
* generally indicate a configuration problem, rather than a
* condition which should be handled by application code.
*/
public static Iterator<String> sqlTempTableCreate(Database database,
Class<? extends DataModelObject> dmoIface)
{
return get(database, dmoIface).sqlCreateTables.iterator();
}
/**
* Return the SQL statement text used to drop a temporary table for the
* given DMO class, using the specified database dialect.
*
* @param database
* The {@code dmoIface}'s backing database.
* @param dmoIface
* DMO interface which represents the temp table.
*
* @return SQL statement(s) for the table drop operation, using syntax
* specific to the given dialect.
*/
public static Iterator<String> sqlTempTableDrop(Database database,
Class<? extends DataModelObject> dmoIface)
{
return get(database, dmoIface).sqlDropTables.iterator();
}
/**
* Return a list of SQL statements used to create the indexes for a
* temporary table for the given DMO class, using the specified database
* dialect. The list may be empty.
*
* @param database
* The {@code dmoIface}'s backing database.
* @param dmoIface
* DMO interface which represents the temp table.
*
* @return SQL statements for the temp table index create operations,
* using syntax specific to the given dialect.
*
* @throws RuntimeException
* if any error occurs generating the SQL statements. This will
* generally indicate a configuration problem, rather than a
* condition which should be handled by application code.
*/
public static Iterator<String> sqlTempTableIndexCreate(Database database,
Class<? extends DataModelObject> dmoIface)
{
return get(database, dmoIface).sqlCreateIndexes.iterator();
}
/**
* Return a list of SQL statements used to drop the indexes for a
* temporary table for the given DMO class, using the specified database
* dialect. The list may be empty.
*
* @param database
* The {@code dmoIface}'s backing database.
* @param dmoIface
* DMO interface which represents the temp table.
*
* @return SQL statements for the temp table index create operations,
* using syntax specific to the given dialect.
*
* @throws RuntimeException
* if any error occurs generating the SQL statements. This will
* generally indicate a configuration problem, rather than a
* condition which should be handled by application code.
*/
public static Iterator<String> sqlTempTableIndexDrop(Database database,
Class<? extends DataModelObject> dmoIface)
{
return get(database, dmoIface).sqlDropIndexes.iterator();
}
/**
* Get the {@link #implicitIndexName}.
*
* @param database
* The {@code dmoIface}'s backing database.
* @param dmoIface
* DMO interface which represents the temp table.
*
* @return See above.
*/
public static String getImplicitIndexName(Database database, Class<? extends DataModelObject> dmoIface)
{
return get(database, dmoIface).implicitIndexName;
}
/**
* Get the {@link #implicitSqlIndexName}.
*
* @param database
* The {@code dmoIface}'s backing database.
* @param dmoIface
* DMO interface which represents the temp table.
*
* @return See above.
*/
public static String getImplicitSqlIndexName(Database database, Class<? extends DataModelObject> dmoIface)
{
return get(database, dmoIface).implicitSqlIndexName;
}
/**
* Get the backing temp table name associated with the given DMO interface, primarily for
* debugging and error reporting purposes.
*
* @param dmoIface
* DMO interface for which a table name is needed.
*
* @return Name of the temp table associated with the given interface.
*/
static String getTableName(Class<? extends DataModelObject> dmoIface)
{
DmoMeta dmoInfo = DmoMetadataManager.getDmoInfo(dmoIface);
return dmoInfo.getSqlTableName();
}
/**
* Factory method for obtaining a helper. First checks for an entry
* in the cache, creating a new instance if necessary.
* <p>
* If the associated database is the {@link DatabaseManager#TEMP_TABLE_DB
* database}, an instance of class {@link LocalTempTableHelper} will be
* returned. Else, an instance of class {@link DirtyTempTableHelper} will
* be returned.
*
* @param database
* The {@code dmoIface}'s backing database.
* @param dmoIface
* DMO interface which provides the public API to the temp table.
*
* @return Helper object associated with the given DMO interface.
*/
static TempTableHelper get(Database database, Class<? extends DataModelObject> dmoIface)
{
synchronized (cache)
{
// Note: it is safe to share a common cache between "local" temp tables and "dirty" temp
// tables because there is no overlap of DMO types between these two uses.
TempTableHelper helper = cache.get(dmoIface);
if (helper == null)
{
helper = database.isTemporary() ? new LocalTempTableHelper(dmoIface)
: new DirtyTempTableHelper(dmoIface, database);
// cache it only if the DMO was resolved; the only case when the DMO is not resolved
// is when the ORMHandler.mapClass will need to get the database index metadata before
// the associated HBM file is mapped (in DatabaseManager.registerDatabase)
if (helper.dmoIface != null)
{
cache.put(dmoIface, helper);
}
}
return helper;
}
}
/**
* Constructor.<br>
* Generates the full set of DDL statement(s) for creating the backing table, including
* indexes for the dialect of the specified database and store them in private final members.
*
* @param dmoIface
* DMO interface which provides the public API to the temp table.
* @param database
* The database for which we generate the DDLs.
*/
TempTableHelper(Class<? extends DataModelObject> dmoIface, Database database)
{
this.schema = DatabaseManager.getSchema(database);
this.database = database;
this.dmoIface = dmoIface;
List<String> ddlCreateTables = new ArrayList<>();
List<String> ddlCreateIndexes = new ArrayList<>();
List<String> ddlDropTables = new ArrayList<>();
List<String> ddlDropIndexes = new ArrayList<>();
List<WordTable> wordTables = new ArrayList<>();
// compute DDLs:
final String tab = " ";
Dialect dialect = DatabaseManager.getDialect(database);
boolean isTemp = Temporary.class.isAssignableFrom(dmoIface);
boolean isDirty = database.isDirty();
Map<Integer, StringBuilder> tables = new HashMap<>();
List<String> constraintsDDLs = new ArrayList<>();
Set<Property> indexedProperties = new HashSet<>();
int idxCount = 0;
DmoMeta dmo = DmoMetadataManager.getDmoInfo(dmoIface);
Iterator<P2JIndex> databaseIndexes = dmo.getDatabaseIndexes();
Map<CaseInsensitiveString, String> explicitIndexes = new TreeMap<>();
String primaryIndexName = null;
String primarySqlIndexName = null;
String createTemporaryTablePostfix = dialect.getCreateTemporaryTablePostfix(false);
String createTemporaryNoUndoTablePostfix = dialect.getCreateTemporaryTablePostfix(true);
String delimiter = dialect.getDelimiter();
String suffix = createTemporaryTablePostfix + delimiter;
while (databaseIndexes.hasNext())
{
P2JIndex index = databaseIndexes.next();
if (index.isWord())
{
// skip WORD indexes, they are not yet supported in FWD, but their 'computed columns' still need
// to exist.
ArrayList<P2JIndexComponent> components = index.components();
if (components.size() == 0)
{
throw new IllegalStateException("No components for word index " +
dmo.legacyTable + "." + index.getName());
}
P2JIndexComponent comp = components.get(0);
Property property = dmo.getFieldInfo(comp.getPropertyName());
indexedProperties.add(property);
/*
* Generating word tables, their indexes and constraints
* Due to a problem with triggers at this moment
* is disable and wordTables will be always empty.
*/
// TODO: enable when a replacement for database triggers will be implemented
if (false && "_temp".equals(schema))
{
wordTables.add(new Builder(NameBuilder.PLAIN,
dmo.getSqlTableName(),
property.column,
property.extent,
property.caseSensitive).build(dialect));
}
continue;
}
++idxCount;
// the index name must be unique for local temp-tables, because unlike the tables
// which can (and are) be declared 'local' to the session, the indexes cannot.
// We will use StringHelper.sweep before executing the DDL to replace the ${1}
// suffix/placeholder with the user's SecurityManager session ID, to ensure the
// resource names are unique. The dirty temp-tables indexes do not need the suffix.
String sqlIdxName = dialect.getSqlIndexName(index) + getAdditionalIndexSuffix();
if (index.isPrimary())
{
primaryIndexName = index.getName();
primarySqlIndexName = sqlIdxName;
}
explicitIndexes.put(new CaseInsensitiveString(index.getName()), sqlIdxName);
StringBuilder sb = new StringBuilder();
sb.append(dialect.getCreateIndexString(false, index.isUnique() && !isDirty)).append(sqlIdxName)
// to enable UNIQUE constraint on temp-table indexes, replace the above line with following:
// sb.append(dialect.getCreateIndexString(index.isUnique() && !isDirty)).append(sqlIdxName)
.append(" on ").append(dmo.getSqlTableName()).append(" (");
if (isTemp)
{
// always all [_multiplex] for temp tables:
Property fieldMultiplex = ReservedProperty._MULTIPLEX;
sb.append(fieldMultiplex.column);
sb.append(", ");
}
boolean lastDesc = false;
ArrayList<P2JIndexComponent> comps = index.components();
for (int i = 0; i < comps.size(); i++)
{
P2JIndexComponent comp = comps.get(i);
if (i > 0)
{
sb.append(", ");
}
Property property = dmo.getFieldInfo(comp.getPropertyName());
indexedProperties.add(property);
String sortCrit;
if (property._isCharacter)
{
// wrap it using functions/prefixes according to dialect
sortCrit = dialect.getProcessedCharacterColumnName(property.column, !property.caseSensitive);
}
else
{
sortCrit = property.column;
}
dialect.orderByNulls(sb, sortCrit, null, !comp.isDescending(), false);
lastDesc = comp.isDescending();
}
// temp-tables require a trailing recid component to satisfy the order of the records when iterating
// even if unique:
// * FWD can store equal records that weren't nursed in the database, violating the unique index
// * FWD should be able to iterate indexes in dynamic mode; if a record was replaced with another:
// ** if the new record reclaimed the same recid, the record is skipped
// ** if the new record claimed a fresh recid and has the same unique data as the old record,
// it is iterated only if it claimed a higher recid (or lower if defined with desc). See #7535.
if (!isDirty)
{
// add [id] to make it unique:
Property fieldId = ReservedProperty.ID;
sb.append(", ").append(fieldId.column);
if (lastDesc)
{
sb.append(" desc");
}
if (index.isUnique() && dialect.allowDBUniqueCheck())
{
sb.append(" soft_unique");
}
}
sb.append(")").append(createTemporaryTablePostfix).append(delimiter);
ddlCreateIndexes.add(sb.toString());
ddlDropIndexes.add(dialect.getDropIndexString(true, sqlIdxName, dmo.getSqlTableName()) +
createTemporaryTablePostfix);
}
if (idxCount == 0 && isTemp)
{
// the index name must be unique for local temp-tables, because unlike the tables
// which can (and are) be declared 'local' to the session, the indexes cannot.
// We will use StringHelper.sweep before executing the DDL to replace the ${1}
// suffix/placeholder with the user's SecurityManager session ID, to ensure the
// resource names are unique. The dirty temp-tables indexes do not need the suffix.
implicitSqlIndexName = "idx_mpid__" + dmo.getSqlTableName() + getAdditionalIndexSuffix();
implicitIndexName = null;
String sb = dialect.getCreateIndexString(false, !isDirty) + implicitSqlIndexName + " on " + dmo.getSqlTableName() +
" (" + ReservedProperty._MULTIPLEX.column + ", " + Session.PK + " )" +
suffix;
ddlCreateIndexes.add(sb);
ddlDropIndexes.add(dialect.getDropIndexString(true, implicitSqlIndexName, dmo.getSqlTableName()) +
createTemporaryTablePostfix);
}
else
{
if (primaryIndexName != null)
{
implicitIndexName = primaryIndexName;
implicitSqlIndexName = primarySqlIndexName;
}
else if (!explicitIndexes.isEmpty())
{
CaseInsensitiveString first = explicitIndexes.keySet().iterator().next();
implicitIndexName = first.getValue();
implicitSqlIndexName = explicitIndexes.get(first);
}
else
{
implicitSqlIndexName = null;
implicitIndexName = null;
}
}
StringBuilder main = new StringBuilder();
main.append(getCreateTableStatement()).append(dmo.getSqlTableName()).append(" (\n");
tables.put(0, main);
ddlDropTables.add(dialect.getDropTableString(true, dmo.getSqlTableName()) + suffix);
Iterator<Property> allFields = dmo.getFields(true);
String invisible = dialect.getInvisibleSpecificator();
while (allFields.hasNext())
{
Property fieldMeta = allFields.next();
// if the extent property was denormalized, treat it as a simple field (it will be
// 'stored' in the main table)
int extent = fieldMeta.index > 0 ? 0 : fieldMeta.extent;
StringBuilder sb = tables.get(extent);
if (sb == null)
{
sb = new StringBuilder();
String compositeTableName = dmo.getSqlTableName() + "__" + extent;
sb.append(getCreateTableStatement()).append(compositeTableName).append(" (\n");
tables.put(extent, sb);
ddlDropTables.add(dialect.getDropTableString(true, compositeTableName) +
suffix);
// TODO: fix this workaround:
Property fieldMetaParent = ReservedProperty.PARENT__ID;
sb.append(tab).append(fieldMetaParent.column).append(" ")
.append(dialect.getSqlMappedType(int64.class, 0, null))
.append(fieldMetaParent.mandatory ? " not null,\n" : ",\n");
Property fieldMetaIndex = ReservedProperty.LIST__INDEX;
sb.append(tab).append(fieldMetaIndex.column).append(" ")
.append(dialect.getSqlMappedType(integer.class, 0, null))
.append(fieldMetaIndex.mandatory ? " not null,\n" : ",\n");
// add the constraint; the name is the FK prefix followed by the composite table name
String fkName = "FK_" + compositeTableName.toUpperCase();
constraintsDDLs.add(dialect.getAddForeignKeyConstraintString(
compositeTableName, fkName, "parent__id", dmo.getSqlTableName(), Session.PK, tab, "\n"));
}
boolean mandatoryColumn = fieldMeta.propId < 0 && fieldMeta.mandatory && isNotNullAllowed();
int normalizedScale = fieldMeta.scale;
if (normalizedScale == 0)
{
// if Property.scale is 0 (which means no option was specified when declared the field) then we
// normalize it to the default value of 10 (digits)
normalizedScale = 10;
}
// the dirty databases accept [null] values for mandatory fields (but do not have reserved props)
// the temp-tables enforces not-null values only for reserved properties (PK and _multiplex)
sb.append(tab).append(fieldMeta.column).append(" ")
.append(dialect.getSqlMappedType(fieldMeta._fwdType, normalizedScale, fieldMeta.caseSensitive))
.append(mandatoryColumn ? " not null,\n" : ",\n");
if (dialect.injectComputedColumns() &&
indexedProperties.contains(fieldMeta) &&
fieldMeta._isCharacter)
{
String sqlType = dialect.getSqlMappedType(fieldMeta._fwdType, normalizedScale, fieldMeta.caseSensitive);
String ccFormula = dialect.getComputedColumnFormula(fieldMeta.column, !fieldMeta.caseSensitive);
sb.append(tab)
.append(dialect.getComputedColumnPrefix(fieldMeta.caseSensitive)).append(fieldMeta.column)
.append(" ").append(sqlType)
.append(invisible)
.append(" as ").append(ccFormula)
.append(",\n");
}
// in case of datetime-tz, use a dual-column, in order to salvage the original offset
// in case it is lost
if (fieldMeta._isDatetimeTz)
{
sb.append(tab)
.append(fieldMeta.column).append(DDLGeneratorWorker.DTZ_OFFSET).append(" ")
.append(dialect.getSqlMappedType("integer", 0, null))
.append(mandatoryColumn ? " not null,\n" : ",\n");
}
}
List<Integer> extents = new ArrayList<>(tables.keySet());
extents.sort(Integer::compareTo);
for (Integer extent : extents)
{
// finish temp-table before printing out
StringBuilder table = tables.get(extent);
if (extent != 0)
{
table.append(tab).append("primary key (parent__id, list__index)\n");
}
else
{
table.append(tab).append("primary key (").append(Session.PK).append(")\n");
}
table.append(")").append(createTemporaryTablePostfix);
if (dmo.noUndo)
{
table.append(createTemporaryNoUndoTablePostfix);
}
if (extent == 0 && isTemp)
{
// this keyword is used in FWD-H2 extension to mark that this is a table that
// multiplexes the records inside. It will hint H2 to use the first column as id
// as second column as multiplex
table.append(" MULTIPLEXED");
}
table.append(delimiter);
String tableDDL = table.toString();
ddlCreateTables.add(tableDDL);
}
Property fieldMetaParent = ReservedProperty.PARENT__ID;
Property fieldMetaIndex = ReservedProperty.LIST__INDEX;
for (WordTable wordTable: wordTables)
{
ddlDropTables.add(dialect.getDropTableString(true, wordTable.tableName) +
suffix);
StringBuilder wt = new StringBuilder();
wt.append(getCreateTableStatement()).append(wordTable.tableName).append(" (\n");
wt.append(tab).append(fieldMetaParent.column).append(" ")
.append(dialect.getSqlMappedType(int64.class, 0, null))
.append(" not null,\n");
if (wordTable.extent > 0)
{
wt.append(tab).append(fieldMetaIndex.column).append(" ")
.append(dialect.getSqlMappedType(integer.class, 0, null))
.append(" not null,\n");
}
wt.append(tab).append("word").append(" ")
.append(dialect.getSqlMappedType(String.class, 0, null))
.append(" not null\n");
wt.append(")").
append(createTemporaryTablePostfix).
append(delimiter);
String tableDDL = wt.toString();
ddlCreateTables.add(tableDDL);
constraintsDDLs.add(wordTable.createPK("\n", delimiter));
ddlCreateIndexes.add(wordTable.createFkIndex("\n", suffix));
constraintsDDLs.add(wordTable.createFK("\n"));
ddlCreateIndexes.add(wordTable.createIndex("\n", suffix));
}
/** Generating triggers
* With current version of H2 (1.4.200) triggers cannot be used with temporary tables
* because triggers are executed with a separate new connection (see TriggerObject.fireRow)
* This is why the code below is commented
*/
// try(StringOutputStream triggers = new StringOutputStream())
// {
// try(PrintStream ps = new PrintStream(triggers))
// {
// dialect.generateWordTablesDDLImpl(schema, ps, "\n", wordTables);
// }
// constraintsDDLs.add(triggers.getData());
// }
// catch (IOException e)
// {
// // Never happens
// }
// save the result:
ddlCreateTables.addAll(constraintsDDLs);
// debug:
if (LOG.isLoggable(Level.FINE))
{
LOG.log(Level.FINE, "New TEMP-TABLE defined using DDLs:");
for (String table : ddlCreateTables)
{
LOG.log(Level.FINE, table);
}
for (String idx : ddlCreateIndexes)
{
LOG.log(Level.FINE, idx);
}
for (String table : ddlDropTables)
{
LOG.log(Level.FINE, table);
}
for (String idx : ddlDropIndexes)
{
LOG.log(Level.FINE, idx);
}
}
this.sqlCreateTables = Collections.unmodifiableList(ddlCreateTables);
this.sqlCreateIndexes = Collections.unmodifiableList(ddlCreateIndexes);
this.sqlDropTables = Collections.unmodifiableList(ddlDropTables);
this.sqlDropIndexes = Collections.unmodifiableList(ddlDropIndexes);
}
/**
* Abstract method which will return the needed create table string:
* "create local temporary table" or "create temporary table".
*
* @return The create table string.
*/
abstract String getCreateTableStatement();
/**
* Abstract method which will return the an additional index suffix (to
* make the index unique across sessions), if needed.
*
* @return An additional index suffix, if needed.
*/
abstract String getAdditionalIndexSuffix();
/**
* Abstract method which will return the indexes associated with a certain DMO.
*
* @param schema
* The schema used for this DMO.
* @param dmoIface
* The DMO for which to retrieve the indexes.
*
* @return An iterator for the indexes associated with this DMO.
*/
abstract Iterator<P2JIndex> getIndexes(String schema,
Class<? extends DataModelObject> dmoIface);
/**
* Indicate whether non-nullable columns are permitted.
*
* @return {@code true} if non-nullable columns are permitted, else {@code false}.
*/
abstract boolean isNotNullAllowed();
}