org.datanucleus.store.rdbms.table.MapTable Maven / Gradle / Ivy
/**********************************************************************
Copyright (c) 2002 Kelly Grizzle and others. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Contributors:
2002 Mike Martin - unknown changes
2003 Andy Jefferson - added localiser
2003 Andy Jefferson - replaced TableMetadata with identifier
2004 Marco Schulze - added advance-check via TypeManager.isSupportedType(...)
2005 Andy Jefferson - only create ADPT column when necessary
2005 Andy Jefferson - enabled ability to have embedded keys/values
...
**********************************************************************/
package org.datanucleus.store.rdbms.table;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.datanucleus.ClassLoaderResolver;
import org.datanucleus.exceptions.NucleusUserException;
import org.datanucleus.metadata.AbstractClassMetaData;
import org.datanucleus.metadata.AbstractMemberMetaData;
import org.datanucleus.metadata.ColumnMetaData;
import org.datanucleus.metadata.FieldRole;
import org.datanucleus.metadata.ForeignKeyMetaData;
import org.datanucleus.metadata.IdentityType;
import org.datanucleus.metadata.IndexMetaData;
import org.datanucleus.metadata.KeyMetaData;
import org.datanucleus.metadata.MapMetaData;
import org.datanucleus.metadata.PrimaryKeyMetaData;
import org.datanucleus.metadata.UniqueMetaData;
import org.datanucleus.metadata.ValueMetaData;
import org.datanucleus.store.rdbms.exceptions.NoTableManagedException;
import org.datanucleus.store.rdbms.RDBMSPropertyNames;
import org.datanucleus.store.rdbms.RDBMSStoreManager;
import org.datanucleus.store.rdbms.identifier.DatastoreIdentifier;
import org.datanucleus.store.rdbms.key.CandidateKey;
import org.datanucleus.store.rdbms.key.ForeignKey;
import org.datanucleus.store.rdbms.key.Index;
import org.datanucleus.store.rdbms.mapping.java.EmbeddedKeyPCMapping;
import org.datanucleus.store.rdbms.mapping.java.EmbeddedValuePCMapping;
import org.datanucleus.store.rdbms.mapping.java.JavaTypeMapping;
import org.datanucleus.store.rdbms.mapping.java.PersistableMapping;
import org.datanucleus.store.rdbms.mapping.java.ReferenceMapping;
import org.datanucleus.util.ClassUtils;
import org.datanucleus.util.Localiser;
import org.datanucleus.util.NucleusLogger;
/**
* Representation of a join table for a Map. A Map covers a wide range of possibilities
* in terms of whether it allows duplicates or not, whether it allows nulls or not, whether it supports
* ordering via indexes etc. Consequently the join table can vary depending on the required capabilities.
* JoinTable Mappings
*
* The join table consists of the following mappings :-
*
* - ownerMapping linking back to the owning class with the Collection.
* - keyMapping either being an FK link to the key table or being an embedded/serialised key
* stored wholely in this table.
* - valueMapping either being an FK link to the value table or being an embedded/serialised value
* stored wholely in this table.
* - orderMapping which may be null, or otherwise stores an index for the keys.
* This is either to provide uniqueness or ordering (and part of the PK).
*
*/
public class MapTable extends JoinTable
{
protected Table ownerTable;
/** Mapping to the key object. */
private JavaTypeMapping keyMapping;
/** Mapping to the value object. */
private JavaTypeMapping valueMapping;
/**
* Mapping to allow ordering (of keys) or to allow duplicates. Can be used when the key is not suitable
* for use as part of the PK and a PK is required for this join table.
*/
private JavaTypeMapping orderMapping;
/**
* Constructor.
* @param ownerTable Table of the owner of this member
* @param tableName Identifier name of the table
* @param mmd MetaData for the member of the owner
* @param storeMgr The Store Manager managing these tables.
*/
public MapTable(Table ownerTable, DatastoreIdentifier tableName, AbstractMemberMetaData mmd, RDBMSStoreManager storeMgr)
{
super(ownerTable, tableName, mmd, storeMgr);
}
/**
* Method to initialise the table definition.
* @param clr The ClassLoaderResolver
*/
@Override
public void initialize(ClassLoaderResolver clr)
{
assertIsUninitialized();
MapMetaData mapmd = mmd.getMap();
if (mapmd == null)
{
throw new NucleusUserException(Localiser.msg("057017",mmd));
}
PrimaryKeyMetaData pkmd = (mmd.getJoinMetaData() != null ? mmd.getJoinMetaData().getPrimaryKeyMetaData() : null);
boolean pkColsSpecified = (pkmd != null && pkmd.getColumnMetaData() != null);
boolean pkRequired = requiresPrimaryKey();
// Add owner mapping
ColumnMetaData[] ownerColmd = null;
if (mmd.getJoinMetaData() != null && mmd.getJoinMetaData().getColumnMetaData() != null && mmd.getJoinMetaData().getColumnMetaData().length > 0)
{
// Column mappings defined at this side (1-N, M-N)
// When specified at this side they use the tag
ownerColmd = mmd.getJoinMetaData().getColumnMetaData();
}
ownerMapping = ColumnCreator.createColumnsForJoinTables(clr.classForName(ownerType), mmd, ownerColmd, storeMgr, this, pkRequired, false, FieldRole.ROLE_OWNER, clr, null);
if (NucleusLogger.DATASTORE.isDebugEnabled())
{
logMapping(mmd.getFullFieldName()+".[OWNER]", ownerMapping);
}
String keyValueFieldName = (mmd.getKeyMetaData() != null ? mmd.getKeyMetaData().getMappedBy() : null);
String valueKeyFieldName = (mmd.getValueMetaData() != null ? mmd.getValueMetaData().getMappedBy() : null);
// Add key mapping
boolean keyPC = (mmd.hasMap() && mmd.getMap().keyIsPersistent());
Class keyCls = clr.classForName(mapmd.getKeyType());
if (keyValueFieldName != null && isEmbeddedValuePC())
{
// Added in value code
}
else if (isSerialisedKey() || isEmbeddedKeyPC() || (isEmbeddedKey() && !keyPC) || ClassUtils.isReferenceType(keyCls))
{
// Key = PC(embedded), PC(serialised), Non-PC(serialised), Non-PC(embedded), Reference
keyMapping = storeMgr.getMappingManager().getMapping(this, mmd, clr, FieldRole.ROLE_MAP_KEY);
if (Boolean.TRUE.equals(mmd.getContainer().allowNulls()))
{
// Make all key col(s) nullable so we can store null elements
for (int i=0;i 0)
{
// Column mappings defined at this side (1-N, M-N)
keyColmd = keymd.getColumnMetaData();
}
keyMapping = ColumnCreator.createColumnsForJoinTables(keyCls, mmd, keyColmd, storeMgr, this, false, false, FieldRole.ROLE_MAP_KEY, clr, null);
if (mmd.getContainer().allowNulls() == Boolean.TRUE)
{
// Make all key col(s) nullable so we can store null elements
for (int i=0;i 0)
{
// Column mappings defined at this side (1-N, M-N)
valueColmd = valuemd.getColumnMetaData();
}
valueMapping = ColumnCreator.createColumnsForJoinTables(clr.classForName(mapmd.getValueType()), mmd, valueColmd, storeMgr, this, false, true, FieldRole.ROLE_MAP_VALUE, clr, null);
if (mmd.getContainer().allowNulls() == Boolean.TRUE)
{
// Make all value col(s) nullable so we can store null elements
for (int i=0;i 1)
{
orderRequired = true;
}
}
else if (!(keyMapping instanceof PersistableMapping))
{
// Non-PC, so depends if the key column can be used as part of a PK
// TODO This assumes the keyMapping has a single column but what if it is Color with 4 cols?
Column elementCol = keyMapping.getColumnMapping(0).getColumn();
if (!storeMgr.getDatastoreAdapter().isValidPrimaryKeyType(elementCol.getJdbcType()))
{
// Not possible to use this Non-PC type as part of the PK
orderRequired = true;
}
}
}
if (orderRequired)
{
// Order/Adapter (index) column is required (integer based)
ColumnMetaData orderColmd = null;
if (mmd.getOrderMetaData() != null && mmd.getOrderMetaData().getColumnMetaData() != null && mmd.getOrderMetaData().getColumnMetaData().length > 0)
{
// Specified "order" column info
orderColmd = mmd.getOrderMetaData().getColumnMetaData()[0];
if (orderColmd.getName() == null)
{
// No column name so use default
orderColmd = new ColumnMetaData(orderColmd);
DatastoreIdentifier id = storeMgr.getIdentifierFactory().newIndexFieldIdentifier(mmd);
orderColmd.setName(id.getName());
}
}
else
{
// No column name so use default
DatastoreIdentifier id = storeMgr.getIdentifierFactory().newIndexFieldIdentifier(mmd);
orderColmd = new ColumnMetaData();
orderColmd.setName(id.getName());
}
orderMapping = storeMgr.getMappingManager().getMapping(int.class); // JDO2 spec [18.5] order column is assumed to be "int"
ColumnCreator.createIndexColumn(orderMapping, storeMgr, clr, this, orderColmd, pkRequired && !pkColsSpecified);
if (NucleusLogger.DATASTORE.isDebugEnabled())
{
logMapping(mmd.getFullFieldName()+".[ORDER]", orderMapping);
}
}
// Define primary key of the join table (if any)
if (pkRequired)
{
if (pkColsSpecified)
{
// Apply the users PK specification
applyUserPrimaryKeySpecification(pkmd);
}
else
{
// Define PK using internal rules
if (orderRequired)
{
// Order column specified so owner+order are the PK
orderMapping.getColumnMapping(0).getColumn().setPrimaryKey();
}
else
{
// No order column specified so owner+key are the PK
for (int i=0;i getExpectedForeignKeys(ClassLoaderResolver clr)
{
assertIsInitialized();
boolean autoMode = false;
if (storeMgr.getStringProperty(RDBMSPropertyNames.PROPERTY_RDBMS_CONSTRAINT_CREATE_MODE).equals("DataNucleus"))
{
autoMode = true;
}
List foreignKeys = new ArrayList<>();
try
{
// FK from join table to owner table
DatastoreClass referencedTable = storeMgr.getDatastoreClass(ownerType, clr);
if (referencedTable != null)
{
// Take from
ForeignKeyMetaData fkmd = null;
if (mmd.getJoinMetaData() != null)
{
fkmd = mmd.getJoinMetaData().getForeignKeyMetaData();
}
if (fkmd != null || autoMode)
{
ForeignKey fk = new ForeignKey(ownerMapping,dba,referencedTable, true);
fk.setForMetaData(fkmd);
foreignKeys.add(fk);
}
}
if (!isSerialisedValuePC())
{
if (isEmbeddedValuePC())
{
// Add any FKs for the fields of the (embedded) value
EmbeddedValuePCMapping embMapping = (EmbeddedValuePCMapping)valueMapping;
for (int i=0;i fks = TableUtils.getForeignKeysForReferenceField(embFieldMapping, embFmd, autoMode, storeMgr, clr);
foreignKeys.addAll(fks);
}
else if (storeMgr.getNucleusContext().getMetaDataManager().getMetaDataForClass(embFmd.getType(), clr) != null &&
embFieldMapping.getNumberOfColumnMappings() > 0 &&
embFieldMapping instanceof PersistableMapping)
{
// Field is for a PC class with the FK at this side, so add a FK to the table of this PC
ForeignKey fk = TableUtils.getForeignKeyForPCField(embFieldMapping, embFmd,
autoMode, storeMgr, clr);
if (fk != null)
{
foreignKeys.add(fk);
}
}
}
}
else if (mmd.getMap().valueIsPersistent())
{
// FK from join table to value table
referencedTable = storeMgr.getDatastoreClass(mmd.getMap().getValueType(), clr);
if (referencedTable != null)
{
// Take from
ForeignKeyMetaData fkmd = null;
if (mmd.getValueMetaData() != null)
{
fkmd = mmd.getValueMetaData().getForeignKeyMetaData();
}
if (fkmd != null || autoMode)
{
ForeignKey fk = new ForeignKey(valueMapping, dba, referencedTable, true);
fk.setForMetaData(fkmd);
foreignKeys.add(fk);
}
}
}
}
if (!isSerialisedKeyPC())
{
if (isEmbeddedKeyPC())
{
// Add any FKs for the fields of the (embedded) key
EmbeddedKeyPCMapping embMapping = (EmbeddedKeyPCMapping)keyMapping;
for (int i=0;i fks = TableUtils.getForeignKeysForReferenceField(embFieldMapping, embFmd, autoMode, storeMgr, clr);
foreignKeys.addAll(fks);
}
else if (storeMgr.getNucleusContext().getMetaDataManager().getMetaDataForClass(embFmd.getType(), clr) != null &&
embFieldMapping.getNumberOfColumnMappings() > 0 && embFieldMapping instanceof PersistableMapping)
{
// Field is for a PC class with the FK at this side, so add a FK to the table of this PC
ForeignKey fk = TableUtils.getForeignKeyForPCField(embFieldMapping, embFmd, autoMode, storeMgr, clr);
if (fk != null)
{
foreignKeys.add(fk);
}
}
}
}
else if (mmd.getMap().keyIsPersistent())
{
// FK from join table to key table
referencedTable = storeMgr.getDatastoreClass(mmd.getMap().getKeyType(), clr);
if (referencedTable != null)
{
// Take from
ForeignKeyMetaData fkmd = null;
if (mmd.getKeyMetaData() != null)
{
fkmd = mmd.getKeyMetaData().getForeignKeyMetaData();
}
if (fkmd != null || autoMode)
{
ForeignKey fk = new ForeignKey(keyMapping, dba, referencedTable, true);
fk.setForMetaData(fkmd);
foreignKeys.add(fk);
}
}
}
}
}
catch (NoTableManagedException e)
{
// expected when no table exists
}
return foreignKeys;
}
/**
* Accessor for the indices for this table.
* This includes both the user-defined indices (via MetaData), and the ones required by foreign keys.
* @param clr The ClassLoaderResolver
* @return The indices
*/
@Override
protected Set getExpectedIndices(ClassLoaderResolver clr)
{
Set indices = new HashSet<>();
// Index for FK back to owner
if (mmd.getIndexMetaData() != null)
{
Index index = TableUtils.getIndexForField(this, mmd.getIndexMetaData(), ownerMapping);
if (index != null)
{
indices.add(index);
}
}
else if (mmd.getJoinMetaData() != null && mmd.getJoinMetaData().getIndexMetaData() != null)
{
Index index = TableUtils.getIndexForField(this, mmd.getJoinMetaData().getIndexMetaData(), ownerMapping);
if (index != null)
{
indices.add(index);
}
}
else
{
// Fallback to an index for the foreign-key to the owner
Index index = TableUtils.getIndexForField(this, null, ownerMapping);
if (index != null)
{
indices.add(index);
}
}
// Index for the key FK (if required)
if (keyMapping instanceof EmbeddedKeyPCMapping)
{
// Add all indices required by fields of the embedded key
EmbeddedKeyPCMapping embMapping = (EmbeddedKeyPCMapping)keyMapping;
for (int i=0;i getExpectedCandidateKeys()
{
// The indices required by foreign keys (BaseTable)
List candidateKeys = super.getExpectedCandidateKeys();
if (keyMapping instanceof EmbeddedKeyPCMapping)
{
// Add all candidate keys required by fields of the embedded key
EmbeddedKeyPCMapping embMapping = (EmbeddedKeyPCMapping)keyMapping;
for (int i=0;i