OPENJPA-340: Support for @UniqueConstraints on @TableGenerator, @SecondaryTable and @JoinTable.

git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@672038 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Pinaki Poddar 2008-06-26 22:04:06 +00:00
parent 2b3f7ec90c
commit ff4a96e707
17 changed files with 568 additions and 109 deletions

View File

@ -28,6 +28,7 @@ import java.util.HashMap;
import javax.transaction.NotSupportedException; import javax.transaction.NotSupportedException;
import org.apache.commons.lang.StringUtils;
import org.apache.openjpa.jdbc.conf.JDBCConfiguration; import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
import org.apache.openjpa.jdbc.conf.JDBCConfigurationImpl; import org.apache.openjpa.jdbc.conf.JDBCConfigurationImpl;
import org.apache.openjpa.jdbc.meta.ClassMapping; import org.apache.openjpa.jdbc.meta.ClassMapping;
@ -38,6 +39,7 @@ import org.apache.openjpa.jdbc.schema.SchemaGroup;
import org.apache.openjpa.jdbc.schema.SchemaTool; import org.apache.openjpa.jdbc.schema.SchemaTool;
import org.apache.openjpa.jdbc.schema.Schemas; import org.apache.openjpa.jdbc.schema.Schemas;
import org.apache.openjpa.jdbc.schema.Table; import org.apache.openjpa.jdbc.schema.Table;
import org.apache.openjpa.jdbc.schema.Unique;
import org.apache.openjpa.jdbc.sql.DBDictionary; import org.apache.openjpa.jdbc.sql.DBDictionary;
import org.apache.openjpa.jdbc.sql.RowImpl; import org.apache.openjpa.jdbc.sql.RowImpl;
import org.apache.openjpa.jdbc.sql.SQLBuffer; import org.apache.openjpa.jdbc.sql.SQLBuffer;
@ -49,6 +51,8 @@ import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.lib.util.Options; import org.apache.openjpa.lib.util.Options;
import org.apache.openjpa.meta.JavaTypes; import org.apache.openjpa.meta.JavaTypes;
import org.apache.openjpa.util.InvalidStateException; import org.apache.openjpa.util.InvalidStateException;
import org.apache.openjpa.util.UserException;
import serp.util.Numbers; import serp.util.Numbers;
import serp.util.Strings; import serp.util.Strings;
@ -86,6 +90,7 @@ public class TableJDBCSeq
private String _table = "OPENJPA_SEQUENCE_TABLE"; private String _table = "OPENJPA_SEQUENCE_TABLE";
private String _seqColumnName = "SEQUENCE_VALUE"; private String _seqColumnName = "SEQUENCE_VALUE";
private String _pkColumnName = "ID"; private String _pkColumnName = "ID";
private String[] _uniqueColumnNames;
private Column _seqColumn = null; private Column _seqColumn = null;
private Column _pkColumn = null; private Column _pkColumn = null;
@ -191,6 +196,20 @@ public class TableJDBCSeq
public void setInitialValue(int intValue) { public void setInitialValue(int intValue) {
_intValue = intValue; _intValue = intValue;
} }
/**
* Sets the names of the columns on which a unique constraint is set.
* @param columnsNames are passed as a single String concatenated with
* a '|' character. This method parses it back to array of Strings.
*/
public void setUniqueColumns(String columnNames) {
_uniqueColumnNames = (StringUtils.isEmpty(columnNames))
? null : StringUtils.split(columnNames, '|');
}
public String getUniqueColumns() {
return StringUtils.join(_uniqueColumnNames, '|');
}
/** /**
* @deprecated Use {@link #setAllocate}. Retained for backwards * @deprecated Use {@link #setAllocate}. Retained for backwards
@ -235,7 +254,12 @@ public class TableJDBCSeq
if (schema == null) if (schema == null)
schema = group.addSchema(schemaName); schema = group.addSchema(schemaName);
schema.importTable(_pkColumn.getTable()); Table copy = schema.importTable(_pkColumn.getTable());
// importTable() does not import unique constraints
Unique[] uniques = _pkColumn.getTable().getUniques();
for (Unique u : uniques) {
copy.importUnique(u);
}
// we need to reset the table name in the column with the // we need to reset the table name in the column with the
// fully qualified name for matching the table name from the // fully qualified name for matching the table name from the
// Column. // Column.
@ -244,7 +268,6 @@ public class TableJDBCSeq
// some databases require to create an index for the sequence table // some databases require to create an index for the sequence table
_conf.getDBDictionaryInstance().createIndexIfNecessary(schema, _conf.getDBDictionaryInstance().createIndexIfNecessary(schema,
_table, _pkColumn); _table, _pkColumn);
} }
} }
@ -361,6 +384,19 @@ public class TableJDBCSeq
(_seqColumnName, table)); (_seqColumnName, table));
_seqColumn.setType(dict.getPreferredType(Types.BIGINT)); _seqColumn.setType(dict.getPreferredType(Types.BIGINT));
_seqColumn.setJavaType(JavaTypes.LONG); _seqColumn.setJavaType(JavaTypes.LONG);
if (_uniqueColumnNames != null) {
String uniqueName = dict.getValidUniqueName("UNQ", table);
Unique u = table.addUnique(uniqueName);
for (String columnName : _uniqueColumnNames) {
if (!table.containsColumn(columnName))
throw new UserException(_loc.get("unique-missing-column",
columnName, table.getName(), table.getColumnNames()));
Column col = table.getColumn(columnName);
u.addColumn(col);
}
}
} }
/** /**

View File

@ -19,13 +19,13 @@
package org.apache.openjpa.jdbc.meta; package org.apache.openjpa.jdbc.meta;
import java.io.File; import java.io.File;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Collection; import java.util.Map.Entry;
import java.util.ArrayList;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.openjpa.jdbc.meta.strats.FullClassStrategy; import org.apache.openjpa.jdbc.meta.strats.FullClassStrategy;
@ -38,6 +38,7 @@ import org.apache.openjpa.jdbc.schema.Unique;
import org.apache.openjpa.lib.meta.SourceTracker; import org.apache.openjpa.lib.meta.SourceTracker;
import org.apache.openjpa.lib.util.Localizer; import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.lib.xml.Commentable; import org.apache.openjpa.lib.xml.Commentable;
import org.apache.openjpa.meta.MetaDataContext;
import org.apache.openjpa.util.UserException; import org.apache.openjpa.util.UserException;
/** /**
@ -64,7 +65,8 @@ public class ClassMappingInfo
private File _file = null; private File _file = null;
private int _srcType = SRC_OTHER; private int _srcType = SRC_OTHER;
private String[] _comments = null; private String[] _comments = null;
private Collection _uniques = null;//Unique // Unique constraints indexed by primary or secondary table name
private Map<String,List<Unique>> _uniques;
/** /**
* The described class name. * The described class name.
@ -221,11 +223,12 @@ public class ClassMappingInfo
_seconds = new HashMap(); _seconds = new HashMap();
_seconds.put(tableName, cols); _seconds.put(tableName, cols);
} }
/** /**
* Return the table for the given class. * Return the named table for the given class.
*/ */
public Table getTable(final ClassMapping cls, boolean adapt) { public Table getTable(final ClassMapping cls, String tableName,
boolean adapt) {
Table t = createTable(cls, new TableDefaults() { Table t = createTable(cls, new TableDefaults() {
public String get(Schema schema) { public String get(Schema schema) {
// delay this so that we don't do schema reflection for unique // delay this so that we don't do schema reflection for unique
@ -233,13 +236,20 @@ public class ClassMappingInfo
return cls.getMappingRepository().getMappingDefaults(). return cls.getMappingRepository().getMappingDefaults().
getTableName(cls, schema); getTableName(cls, schema);
} }
}, _schemaName, _tableName, adapt); }, _schemaName, tableName, adapt);
t.setComment(cls.getTypeAlias() == null t.setComment(cls.getTypeAlias() == null
? cls.getDescribedType().getName() ? cls.getDescribedType().getName()
: cls.getTypeAlias()); : cls.getTypeAlias());
return t; return t;
} }
/**
* Return the primary table for the given class.
*/
public Table getTable(final ClassMapping cls, boolean adapt) {
return getTable(cls, _tableName, adapt);
}
/** /**
* Return the datastore identity columns for the given class, based on the * Return the datastore identity columns for the given class, based on the
* given templates. * given templates.
@ -340,51 +350,87 @@ public class ClassMappingInfo
_seconds.put(key, cinfo._seconds.get(key)); _seconds.put(key, cinfo._seconds.get(key));
} }
} }
if (cinfo._uniques != null) if (cinfo._uniques != null) {
_uniques = new ArrayList(cinfo._uniques); if (_uniques == null)
} _uniques = new HashMap<String, List<Unique>>();
for (Entry<String, List<Unique>> entry : cinfo._uniques.entrySet())
if (!_uniques.containsKey(entry.getKey()))
_uniques.put(entry.getKey(), entry.getValue());
}
public void addUnique(Unique unique) { }
if (unique == null)
return; /**
* Add a unique constraint for the given table.
* @param table must be primary table or secondary table name added a
* priori to this receiver.
* @param unique the unique constraint. null means no-op.
*/
public void addUnique(String table, Unique unique) {
if (!StringUtils.equals(_tableName, table) &&
(_seconds == null || !_seconds.containsKey(table))) {
throw new UserException(_loc.get("unique-no-table",
new Object[]{table, _className, _tableName,
((_seconds == null) ? "" : _seconds.keySet())}));
}
if (unique == null)
return;
if (_uniques == null) if (_uniques == null)
_uniques = new ArrayList(); _uniques = new HashMap<String,List<Unique>>();
_uniques.add(unique); unique.setTableName(table);
List<Unique> uniques = _uniques.get(table);
if (uniques == null) {
uniques = new ArrayList<Unique>();
uniques.add(unique);
_uniques.put(table, uniques);
} else {
uniques.add(unique);
}
} }
public Unique[] getUniques() { /**
return (_uniques == null) ? new Unique[0] : * Get the unique constraints of the given primary or secondary table.
(Unique[])_uniques.toArray(new Unique[_uniques.size()]); */
public Unique[] getUniques(String table) {
if (_uniques == null || _uniques.isEmpty()
|| _uniques.containsKey(table))
return new Unique[0];
List<Unique> uniques = _uniques.get(table);
return uniques.toArray(new Unique[uniques.size()]);
} }
public Unique[] getUniques(ClassMapping cm, boolean adapt) { /**
* Get all the unique constraints associated with both the primary and/or
* secondary tables.
*
*/
public Unique[] getUniques(MetaDataContext cm, boolean adapt) {
if (_uniques == null || _uniques.isEmpty()) if (_uniques == null || _uniques.isEmpty())
return new Unique[0]; return new Unique[0];
List<Unique> result = new ArrayList<Unique>();
Iterator uniqueConstraints = _uniques.iterator(); for (String tableName : _uniques.keySet()) {
Table table = cm.getTable(); List<Unique> uniqueConstraints = _uniques.get(tableName);
Collection result = new ArrayList(); for (Unique template : uniqueConstraints) {
while (uniqueConstraints.hasNext()) { Column[] templateColumns = template.getColumns();
Unique template = (Unique) uniqueConstraints.next(); Column[] uniqueColumns = new Column[templateColumns.length];
Column[] templateColumns = template.getColumns(); Table table = getTable((ClassMapping)cm, tableName, adapt);
Column[] uniqueColumns = new Column[templateColumns.length]; for (int i=0; i<uniqueColumns.length; i++) {
boolean missingColumn = true; String columnName = templateColumns[i].getName();
for (int i=0; i<uniqueColumns.length; i++) { if (!table.containsColumn(columnName)) {
String columnName = templateColumns[i].getName(); throw new UserException(_loc.get("unique-missing-column",
Column uniqueColumn = table.getColumn(columnName); new Object[]{cm, columnName, tableName,
missingColumn = (uniqueColumn == null); table.getColumnNames()}));
if (missingColumn) { }
throw new UserException(_loc.get("missing-unique-column", Column uniqueColumn = table.getColumn(columnName);
cm, table, columnName)); uniqueColumns[i] = uniqueColumn;
} }
uniqueColumns[i] = uniqueColumn; Unique unique = createUnique(cm, "unique", template,
} uniqueColumns, adapt);
Unique unique = super.createUnique(cm, "unique", template, if (unique != null)
uniqueColumns, adapt); result.add(unique);
if (unique != null) }
result.add(unique);
} }
return (Unique[]) result.toArray(new Unique[result.size()]); return result.toArray(new Unique[result.size()]);
} }
public File getSourceFile() { public File getSourceFile() {

View File

@ -70,7 +70,8 @@ public class FieldMapping
private Index _idx = null; private Index _idx = null;
private boolean _outer = false; private boolean _outer = false;
private int _fetchMode = Integer.MAX_VALUE; private int _fetchMode = Integer.MAX_VALUE;
private Unique[] _joinTableUniques; // Unique constraints on JoinTable
/** /**
* Constructor. * Constructor.
*/ */
@ -183,6 +184,14 @@ public class FieldMapping
_unq = unq; _unq = unq;
} }
public Unique[] getJoinTableUniques() {
return _joinTableUniques;
}
public void setJoinTableUniques(Unique[] unqs) {
_joinTableUniques = unqs;
}
/** /**
* Index on join foreign key columns. * Index on join foreign key columns.
*/ */
@ -252,6 +261,13 @@ public class FieldMapping
_val.refSchemaComponents(); _val.refSchemaComponents();
_key.refSchemaComponents(); _key.refSchemaComponents();
_elem.refSchemaComponents(); _elem.refSchemaComponents();
if (_joinTableUniques != null) {
for (Unique joinUnique : _joinTableUniques) {
for (Column col : joinUnique.getColumns()) {
col.ref();
}
}
}
} }
/** /**
@ -538,6 +554,7 @@ public class FieldMapping
_io = _info.getColumnIO(); _io = _info.getColumnIO();
_outer = _info.isJoinOuter(); _outer = _info.isJoinOuter();
_unq = _info.getJoinUnique(this, false, adapt); _unq = _info.getJoinUnique(this, false, adapt);
_joinTableUniques = _info.getJoinTableUniques(this, false, adapt);
_idx = _info.getJoinIndex(this, adapt); _idx = _info.getJoinIndex(this, adapt);
} }
} }

View File

@ -18,6 +18,8 @@
*/ */
package org.apache.openjpa.jdbc.meta; package org.apache.openjpa.jdbc.meta;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import org.apache.openjpa.jdbc.schema.Column; import org.apache.openjpa.jdbc.schema.Column;
@ -31,7 +33,9 @@ import org.apache.openjpa.jdbc.schema.Unique;
import org.apache.openjpa.lib.util.Localizer; import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.lib.xml.Commentable; import org.apache.openjpa.lib.xml.Commentable;
import org.apache.openjpa.meta.JavaTypes; import org.apache.openjpa.meta.JavaTypes;
import org.apache.openjpa.meta.MetaDataContext;
import org.apache.openjpa.util.MetaDataException; import org.apache.openjpa.util.MetaDataException;
import org.apache.openjpa.util.UserException;
/** /**
* Information about the mapping from a field to the schema, in raw form. * Information about the mapping from a field to the schema, in raw form.
@ -40,6 +44,7 @@ import org.apache.openjpa.util.MetaDataException;
* with the relevant pieces of information filled in. * with the relevant pieces of information filled in.
* *
* @author Abe White * @author Abe White
* @author Pinaki Poddar
*/ */
public class FieldMappingInfo public class FieldMappingInfo
extends MappingInfo extends MappingInfo
@ -53,6 +58,7 @@ public class FieldMappingInfo
private Column _orderCol = null; private Column _orderCol = null;
private boolean _canOrderCol = true; private boolean _canOrderCol = true;
private String[] _comments = null; private String[] _comments = null;
private List<Unique> _joinTableUniques; // Unique constraints on the JoinTable
/** /**
* The user-supplied name of the table for this field. * The user-supplied name of the table for this field.
@ -185,8 +191,47 @@ public class FieldMappingInfo
getJoinUnique(field, fk.getTable(), fk.getColumns()); getJoinUnique(field, fk.getTable(), fk.getColumns());
return createUnique(field, "join", unq, fk.getColumns(), adapt); return createUnique(field, "join", unq, fk.getColumns(), adapt);
} }
/** /**
* Add Unique Constraint to the Join Table.
*/
public void addJoinTableUnique(Unique u) {
if (_joinTableUniques == null)
_joinTableUniques = new ArrayList<Unique>();
_joinTableUniques.add(u);
}
/**
* Get the unique constraints associated with the Sequence table.
*/
public Unique[] getJoinTableUniques(FieldMapping field, boolean def,
boolean adapt) {
return getUniques(field, _joinTableUniques, def, adapt);
}
private Unique[] getUniques(FieldMapping field, List<Unique> uniques,
boolean def, boolean adapt) {
if (uniques == null || uniques.isEmpty())
return new Unique[0];
Collection<Unique> result = new ArrayList<Unique>();
for (Unique template : uniques) {
Column[] templateColumns = template.getColumns();
Column[] uniqueColumns = new Column[templateColumns.length];
Table table = getTable(field, true, adapt);
for (int i=0; i<uniqueColumns.length; i++) {
String columnName = templateColumns[i].getName();
Column uniqueColumn = table.getColumn(columnName);
uniqueColumns[i] = uniqueColumn;
}
Unique unique = createUnique(field, "unique", template,
uniqueColumns, adapt);
if (unique != null)
result.add(unique);
}
return result.toArray(new Unique[result.size()]);
}
/**
* Index on the field join. * Index on the field join.
*/ */
public Index getJoinIndex(FieldMapping field, boolean adapt) { public Index getJoinIndex(FieldMapping field, boolean adapt) {
@ -261,6 +306,7 @@ public class FieldMappingInfo
syncIndex(field, field.getJoinIndex()); syncIndex(field, field.getJoinIndex());
syncUnique(field, field.getJoinUnique()); syncUnique(field, field.getJoinUnique());
syncJoinTableUniques(field, field.getJoinTableUniques());
syncOrderColumn(field); syncOrderColumn(field);
syncStrategy(field); syncStrategy(field);
} }
@ -290,6 +336,24 @@ public class FieldMappingInfo
else else
_orderCol = null; _orderCol = null;
} }
/**
* Sets internal constraint information to match given mapped constraint.
*/
protected void syncJoinTableUniques(MetaDataContext context, Unique[] unqs) {
if (unqs == null) {
_joinTableUniques = null;
return;
}
_joinTableUniques = new ArrayList<Unique>();
for (Unique unique:unqs) {
Unique copy = new Unique();
copy.setName(unique.getName());
copy.setDeferred(unique.isDeferred());
_joinTableUniques.add(unique);
}
}
public boolean hasSchemaComponents() { public boolean hasSchemaComponents() {
return super.hasSchemaComponents() || _tableName != null return super.hasSchemaComponents() || _tableName != null

View File

@ -927,7 +927,7 @@ public abstract class MappingInfo
context, dict.platform)); context, dict.platform));
deferred = false; deferred = false;
} }
Unique unq = table.addUnique(name); Unique unq = table.addUnique(name);
unq.setDeferred(deferred); unq.setDeferred(deferred);
unq.setColumns(cols); unq.setColumns(cols);
@ -1534,7 +1534,7 @@ public abstract class MappingInfo
_unq.setName(unq.getName()); _unq.setName(unq.getName());
_unq.setDeferred(unq.isDeferred()); _unq.setDeferred(unq.isDeferred());
} }
/** /**
* Sets internal constraint and column information to match given mapped * Sets internal constraint and column information to match given mapped
* constraint. * constraint.

View File

@ -19,11 +19,16 @@
package org.apache.openjpa.jdbc.meta; package org.apache.openjpa.jdbc.meta;
import java.io.File; import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.apache.openjpa.jdbc.conf.JDBCSeqValue; import org.apache.openjpa.jdbc.conf.JDBCSeqValue;
import org.apache.openjpa.jdbc.kernel.ClassTableJDBCSeq; import org.apache.openjpa.jdbc.kernel.ClassTableJDBCSeq;
import org.apache.openjpa.jdbc.kernel.TableJDBCSeq; import org.apache.openjpa.jdbc.kernel.TableJDBCSeq;
import org.apache.openjpa.jdbc.kernel.ValueTableJDBCSeq; import org.apache.openjpa.jdbc.kernel.ValueTableJDBCSeq;
import org.apache.openjpa.jdbc.schema.Unique;
import org.apache.openjpa.lib.conf.PluginValue; import org.apache.openjpa.lib.conf.PluginValue;
import org.apache.openjpa.meta.SequenceMetaData; import org.apache.openjpa.meta.SequenceMetaData;
@ -55,13 +60,15 @@ public class SequenceMapping
private static final String PROP_SEQUENCE_COL = "SequenceColumn"; private static final String PROP_SEQUENCE_COL = "SequenceColumn";
private static final String PROP_PK_COL = "PrimaryKeyColumn"; private static final String PROP_PK_COL = "PrimaryKeyColumn";
private static final String PROP_PK_VALUE = "PrimaryKeyValue"; private static final String PROP_PK_VALUE = "PrimaryKeyValue";
private static final String PROP_UNIQUE = "UniqueColumns";
private File _mapFile = null; private File _mapFile = null;
private String _table = null; private String _table = null;
private String _sequenceColumn = null; private String _sequenceColumn = null;
private String _primaryKeyColumn = null; private String _primaryKeyColumn = null;
private String _primaryKeyValue = null; private String _primaryKeyValue = null;
private String[] _uniqueColumns = null;
public SequenceMapping(String name, MappingRepository repos) { public SequenceMapping(String name, MappingRepository repos) {
super(name, repos); super(name, repos);
} }
@ -138,6 +145,14 @@ public class SequenceMapping
_primaryKeyValue = primaryKeyValue; _primaryKeyValue = primaryKeyValue;
} }
public void setUniqueColumns(String[] cols) {
_uniqueColumns = cols;
}
public String[] getUniqueColumns() {
return _uniqueColumns;
}
protected PluginValue newPluginValue(String property) { protected PluginValue newPluginValue(String property) {
return new JDBCSeqValue(property); return new JDBCSeqValue(property);
} }
@ -148,5 +163,11 @@ public class SequenceMapping
appendProperty(props, PROP_SEQUENCE_COL, _sequenceColumn); appendProperty(props, PROP_SEQUENCE_COL, _sequenceColumn);
appendProperty(props, PROP_PK_COL, _primaryKeyColumn); appendProperty(props, PROP_PK_COL, _primaryKeyColumn);
appendProperty(props, PROP_PK_VALUE, _primaryKeyValue); appendProperty(props, PROP_PK_VALUE, _primaryKeyValue);
// Array of unique column names are passed to configuration
// as a single string "x|y|z". The configurable (TableJDBCSeq) must
// parse it back.
if (_uniqueColumns != null && _uniqueColumns.length > 0)
appendProperty(props, PROP_UNIQUE,
StringUtils.join(_uniqueColumns,'|'));
} }
} }

View File

@ -252,6 +252,11 @@ public class Table
return _rels; return _rels;
} }
public String[] getColumnNames() {
return _colMap == null ? new String[0] :
(String[])_colMap.keySet().toArray(new String[_colMap.size()]);
}
/** /**
* Return the column with the given name, or null if none. * Return the column with the given name, or null if none.
*/ */
@ -260,6 +265,17 @@ public class Table
return null; return null;
return (Column) _colMap.get(name.toUpperCase()); return (Column) _colMap.get(name.toUpperCase());
} }
/**
* Affirms if this table contains the column of the given name without any
* side-effect.
* @see Table#getColumn(String) can have side-effect of creating a column
* for dynamic table implementation.
*/
public boolean containsColumn(String name) {
return name != null && _colMap != null
&& _colMap.containsKey(name.toUpperCase());
}
/** /**
* Add a column to the table. * Add a column to the table.

View File

@ -18,6 +18,8 @@
*/ */
package org.apache.openjpa.jdbc.schema; package org.apache.openjpa.jdbc.schema;
import org.apache.commons.lang.StringUtils;
/** /**
* Represents a unique constraint. It can also represent a partial constraint. * Represents a unique constraint. It can also represent a partial constraint.
* *
@ -25,11 +27,12 @@ package org.apache.openjpa.jdbc.schema;
*/ */
public class Unique public class Unique
extends LocalConstraint { extends LocalConstraint {
private boolean _isAutoSetName = false;
/** /**
* Default constructor. * Default constructor.
*/ */
public Unique() { public Unique() {
_isAutoSetName = true;
} }
/** /**
@ -45,6 +48,26 @@ public class Unique
public boolean isLogical() { public boolean isLogical() {
return false; return false;
} }
public void addColumn(Column col) {
super.addColumn(col);
col.setNotNull(true);
if (_isAutoSetName && getTable() == null) {
String pre = StringUtils.isEmpty(getName()) ? "UNQ" : getName();
setName(pre + "_" + col.getName());
_isAutoSetName = true;
}
}
/**
* Set the name of the constraint. This method cannot be called if the
* constraint already belongs to a table.
*/
public void setName(String name) {
super.setName(name);
_isAutoSetName = false;
}
/** /**
* Return true if the structure of this primary key matches that of * Return true if the structure of this primary key matches that of

View File

@ -410,6 +410,8 @@ untraversable-path: Result path "{2}" in result type "{1}" of mapping "{0}" \
attempts to traverse through a non-relation field. attempts to traverse through a non-relation field.
num-cols-path: Result path "{2}" in result type "{1}" of mapping "{0}" \ num-cols-path: Result path "{2}" in result type "{1}" of mapping "{0}" \
attempts to map a field that does not have exactly 1 column. attempts to map a field that does not have exactly 1 column.
missing-unique-column: A unique constraint specified in mapping of class "{0}" \ unique-missing-column: The column "{1}" in a unique constraint in "{0}" on \
to table "{1}" includes a column "{2}". However, the column does not \ table "{2}" can not be found in the list of available columns "{3}".
exist in "{1}" table. unique-no-table: A unique constraint on table "{0}" can not be added to \
mapping of class "{1}" because the table does neither match its primary \
table "{2}" nor any of its secondary table(s) "{3}".

View File

@ -61,6 +61,7 @@ import org.apache.openjpa.jdbc.meta.ClassMapping;
import org.apache.openjpa.jdbc.meta.ClassMappingInfo; import org.apache.openjpa.jdbc.meta.ClassMappingInfo;
import org.apache.openjpa.jdbc.meta.Discriminator; import org.apache.openjpa.jdbc.meta.Discriminator;
import org.apache.openjpa.jdbc.meta.FieldMapping; import org.apache.openjpa.jdbc.meta.FieldMapping;
import org.apache.openjpa.jdbc.meta.FieldMappingInfo;
import org.apache.openjpa.jdbc.meta.MappingInfo; import org.apache.openjpa.jdbc.meta.MappingInfo;
import org.apache.openjpa.jdbc.meta.MappingRepository; import org.apache.openjpa.jdbc.meta.MappingRepository;
import org.apache.openjpa.jdbc.meta.QueryResultMapping; import org.apache.openjpa.jdbc.meta.QueryResultMapping;
@ -80,6 +81,7 @@ import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.meta.ClassMetaData; import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.FieldMetaData; import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.meta.JavaTypes; import org.apache.openjpa.meta.JavaTypes;
import org.apache.openjpa.meta.MetaDataContext;
import org.apache.openjpa.persistence.AnnotationPersistenceMetaDataParser; import org.apache.openjpa.persistence.AnnotationPersistenceMetaDataParser;
import static org.apache.openjpa.persistence.jdbc.MappingTag.*; import static org.apache.openjpa.persistence.jdbc.MappingTag.*;
import org.apache.openjpa.util.InternalException; import org.apache.openjpa.util.InternalException;
@ -244,10 +246,16 @@ public class AnnotationPersistenceMappingParser
meta.setAllocate(gen.allocationSize()); meta.setAllocate(gen.allocationSize());
meta.setSource(getSourceFile(), (el instanceof Class) ? el : null, meta.setSource(getSourceFile(), (el instanceof Class) ? el : null,
meta.SRC_ANNOTATIONS); meta.SRC_ANNOTATIONS);
//### EJB3 switch (gen.uniqueConstraints().length) {
if (gen.uniqueConstraints().length > 0 && log.isWarnEnabled()) case 0:
log.warn(_loc.get("unique-constraints", name)); break; // nothing to do
case 1:
meta.setUniqueColumns(gen.uniqueConstraints()[0].columnNames());
break;
default:
log.warn(_loc.get("unique-many-on-seq-unsupported", el, name));
}
} }
@Override @Override
@ -464,8 +472,7 @@ public class AnnotationPersistenceMappingParser
Log log = getLog(); Log log = getLog();
String name; String name;
List<Column> joins; List<Column> joins = null;
boolean warnUnique = false;
for (SecondaryTable table : tables) { for (SecondaryTable table : tables) {
name = table.name(); name = table.name();
if (StringUtils.isEmpty(name)) if (StringUtils.isEmpty(name))
@ -476,14 +483,10 @@ public class AnnotationPersistenceMappingParser
joins = new ArrayList<Column>(table.pkJoinColumns().length); joins = new ArrayList<Column>(table.pkJoinColumns().length);
for (PrimaryKeyJoinColumn join : table.pkJoinColumns()) for (PrimaryKeyJoinColumn join : table.pkJoinColumns())
joins.add(newColumn(join)); joins.add(newColumn(join));
info.setSecondaryTableJoinColumns(name, joins); }
} info.setSecondaryTableJoinColumns(name, joins);
warnUnique |= table.uniqueConstraints().length > 0; addUniqueConstraints(name, cm, info, table.uniqueConstraints());
} }
//### EJB3
if (warnUnique && log.isWarnEnabled())
log.warn(_loc.get("unique-constraints", cm));
} }
/** /**
@ -494,10 +497,38 @@ public class AnnotationPersistenceMappingParser
if (tableName != null) if (tableName != null)
cm.getMappingInfo().setTableName(tableName); cm.getMappingInfo().setTableName(tableName);
for (UniqueConstraint uniqueConstraint:table.uniqueConstraints()) { addUniqueConstraints(tableName, cm, cm.getMappingInfo(),
Unique unique = newUnique(cm, null, uniqueConstraint.columnNames()); table.uniqueConstraints());
cm.getMappingInfo().addUnique(unique); }
}
Unique createUniqueConstraint(MetaDataContext ctx, UniqueConstraint anno) {
String[] columnNames = anno.columnNames();
if (columnNames == null || columnNames.length == 0)
throw new UserException(_loc.get("unique-no-column", ctx));
Unique uniqueConstraint = new Unique();
for (int i=0; i<columnNames.length; i++) {
if (StringUtils.isEmpty(columnNames[i]))
throw new UserException(_loc.get("unique-empty-column",
Arrays.toString(columnNames), ctx));
Column column = new Column();
column.setName(columnNames[i]);
uniqueConstraint.addColumn(column);
}
return uniqueConstraint;
}
void addUniqueConstraints(String table, MetaDataContext ctx,
MappingInfo info, UniqueConstraint...uniqueConstraints) {
for (UniqueConstraint anno : uniqueConstraints) {
Unique unique = createUniqueConstraint(ctx, anno);
unique.setTableName(table);
if (info instanceof ClassMappingInfo)
((ClassMappingInfo)info).addUnique(table, unique);
else if (info instanceof FieldMappingInfo)
((FieldMappingInfo)info).addJoinTableUnique(unique);
else
throw new InternalException();
}
} }
/** /**
@ -1261,8 +1292,7 @@ public class AnnotationPersistenceMappingParser
} }
unique |= (pcols[i].unique()) ? TRUE : FALSE; unique |= (pcols[i].unique()) ? TRUE : FALSE;
secondary = trackSecondaryTable(fm, secondary, secondary = trackSecondaryTable(fm, secondary, pcols[i].table(), i);
pcols[i].table(), i);
} }
setColumns(fm, fm.getValueInfo(), cols, unique); setColumns(fm, fm.getValueInfo(), cols, unique);
@ -1337,11 +1367,13 @@ public class AnnotationPersistenceMappingParser
* Parse @JoinTable. * Parse @JoinTable.
*/ */
private void parseJoinTable(FieldMapping fm, JoinTable join) { private void parseJoinTable(FieldMapping fm, JoinTable join) {
fm.getMappingInfo().setTableName(toTableName(join.schema(), FieldMappingInfo info = fm.getMappingInfo();
join.name())); info.setTableName(toTableName(join.schema(), join.name()));
parseJoinColumns(fm, fm.getMappingInfo(), false, join.joinColumns()); parseJoinColumns(fm, info, false, join.joinColumns());
parseJoinColumns(fm, fm.getElementMapping().getValueInfo(), false, parseJoinColumns(fm, fm.getElementMapping().getValueInfo(), false,
join.inverseJoinColumns()); join.inverseJoinColumns());
addUniqueConstraints(info.getTableName(), fm, info,
join.uniqueConstraints());
} }
/** /**
@ -1617,21 +1649,4 @@ public class AnnotationPersistenceMappingParser
col.setFlag (Column.FLAG_UNUPDATABLE, !join.updatable ()); col.setFlag (Column.FLAG_UNUPDATABLE, !join.updatable ());
return col; return col;
} }
private static Unique newUnique(ClassMapping cm, String name,
String[] columnNames) {
if (columnNames == null || columnNames.length == 0)
return null;
Unique uniqueConstraint = new Unique();
uniqueConstraint.setName(name);
for (int i=0; i<columnNames.length; i++) {
if (StringUtils.isEmpty(columnNames[i]))
throw new UserException(_loc.get("empty-unique-column",
Arrays.toString(columnNames), cm));
Column column = new Column();
column.setName(columnNames[i]);
uniqueConstraint.addColumn(column);
}
return uniqueConstraint;
}
} }

View File

@ -221,12 +221,12 @@ public class AnnotationPersistenceMappingSerializer
AnnotationBuilder abTable = addAnnotation(Table.class, mapping); AnnotationBuilder abTable = addAnnotation(Table.class, mapping);
serializeTable(info.getTableName(), Strings serializeTable(info.getTableName(), Strings
.getClassName(mapping.getDescribedType()), null, .getClassName(mapping.getDescribedType()), null,
info.getUniques(), abTable); info.getUniques(info.getTableName()), abTable);
serializeColumns(info, ColType.PK_JOIN, null, abTable, cls); serializeColumns(info, ColType.PK_JOIN, null, abTable, cls);
for (String second : info.getSecondaryTableNames()) { for (String second : info.getSecondaryTableNames()) {
AnnotationBuilder abSecTable = AnnotationBuilder abSecTable =
addAnnotation(SecondaryTable.class, mapping); addAnnotation(SecondaryTable.class, mapping);
serializeTable(second, null, info, null, abSecTable); serializeTable(second, null, info, info.getUniques(second), abSecTable);
} }
} }

View File

@ -39,6 +39,7 @@ import org.apache.openjpa.jdbc.meta.ClassMapping;
import org.apache.openjpa.jdbc.meta.ClassMappingInfo; import org.apache.openjpa.jdbc.meta.ClassMappingInfo;
import org.apache.openjpa.jdbc.meta.DiscriminatorMappingInfo; import org.apache.openjpa.jdbc.meta.DiscriminatorMappingInfo;
import org.apache.openjpa.jdbc.meta.FieldMapping; import org.apache.openjpa.jdbc.meta.FieldMapping;
import org.apache.openjpa.jdbc.meta.MappingInfo;
import org.apache.openjpa.jdbc.meta.MappingRepository; import org.apache.openjpa.jdbc.meta.MappingRepository;
import org.apache.openjpa.jdbc.meta.QueryResultMapping; import org.apache.openjpa.jdbc.meta.QueryResultMapping;
import org.apache.openjpa.jdbc.meta.SequenceMapping; import org.apache.openjpa.jdbc.meta.SequenceMapping;
@ -56,6 +57,8 @@ import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.meta.JavaTypes; import org.apache.openjpa.meta.JavaTypes;
import org.apache.openjpa.meta.MetaDataRepository; import org.apache.openjpa.meta.MetaDataRepository;
import org.apache.openjpa.persistence.XMLPersistenceMetaDataParser; import org.apache.openjpa.persistence.XMLPersistenceMetaDataParser;
import org.apache.openjpa.util.InternalException;
import static org.apache.openjpa.persistence.jdbc.MappingTag.*; import static org.apache.openjpa.persistence.jdbc.MappingTag.*;
/** /**
@ -293,6 +296,9 @@ public class XMLPersistenceMappingParser
case COLUMN_NAME: case COLUMN_NAME:
endColumnName(); endColumnName();
break; break;
case TABLE_GEN:
endTableGenerator();
break;
} }
} }
@ -405,8 +411,13 @@ public class XMLPersistenceMappingParser
Object scope = (cur instanceof ClassMetaData) Object scope = (cur instanceof ClassMetaData)
? ((ClassMetaData) cur).getDescribedType() : null; ? ((ClassMetaData) cur).getDescribedType() : null;
seq.setSource(getSourceFile(), scope, seq.SRC_XML); seq.setSource(getSourceFile(), scope, seq.SRC_XML);
pushElement(seq);
return true; return true;
} }
private void endTableGenerator() {
popElement();
}
/** /**
* Parse inheritance. * Parse inheritance.
@ -880,13 +891,9 @@ public class XMLPersistenceMappingParser
*/ */
private boolean startUniqueConstraint(Attributes attrs) private boolean startUniqueConstraint(Attributes attrs)
throws SAXException { throws SAXException {
Object current = currentElement(); Unique unique = new Unique();
if (current instanceof ClassMapping && _secondaryTable == null) { pushElement(unique);
Unique unique = new Unique(); return true;
pushElement(unique);
return true;
}
return false;
} }
/** /**
@ -897,9 +904,23 @@ public class XMLPersistenceMappingParser
*/ */
private void endUniqueConstraint() { private void endUniqueConstraint() {
Unique unique = (Unique) popElement(); Unique unique = (Unique) popElement();
Object current = currentElement(); Object ctx = currentElement();
if (current instanceof ClassMapping && _secondaryTable == null) String tableName = "?";
((ClassMapping) current).getMappingInfo().addUnique(unique); ClassMappingInfo info = null;
if (ctx instanceof ClassMapping) {
info = ((ClassMapping) ctx).getMappingInfo();
tableName = (_secondaryTable != null) ? info.getTableName() : _secondaryTable;
info.addUnique(tableName, unique);
} else if (ctx instanceof FieldMapping) {// JoinTable
info = ((FieldMapping)ctx).getDeclaringMapping().getMappingInfo();
tableName = info.getTableName();
info.addUnique(tableName, unique);
} else if (ctx instanceof SequenceMapping) {
tableName = ((SequenceMapping)ctx).getTable();
unique.setTableName(tableName);
} else {
throw new InternalException();
}
} }
/** /**

View File

@ -186,9 +186,9 @@ public class XMLPersistenceMappingSerializer
ClassMappingInfo info = cls.getMappingInfo(); ClassMappingInfo info = cls.getMappingInfo();
serializeTable(info.getTableName(), "table", Strings serializeTable(info.getTableName(), "table", Strings
.getClassName(mapping.getDescribedType()), null, .getClassName(mapping.getDescribedType()), null,
info.getUniques()); info.getUniques(info.getTableName()));
for (String second : info.getSecondaryTableNames()) for (String second : info.getSecondaryTableNames())
serializeTable(second, "secondary-table", null, info, null); serializeTable(second, "secondary-table", null, info, info.getUniques(second));
serializeColumns(info, ColType.PK_JOIN, null); serializeColumns(info, ColType.PK_JOIN, null);
} }

View File

@ -46,8 +46,13 @@ second-version: Version property "{0}" cannot map to a secondary table column. \
Version columns must always be in the primary table of the class. Version columns must always be in the primary table of the class.
not-embedded: Attempt to declare mapping overrides on non-embedded field "{0}". not-embedded: Attempt to declare mapping overrides on non-embedded field "{0}".
no-gen-table: No generated table found at "{0}". no-gen-table: No generated table found at "{0}".
empty-unique-column: A unique constraint "{0}" specified in mapping of class \ unique-no-column: A unique constraint specified in mapping of "{0}" specified \
no column.
unique-empty-column: A unique constraint "{0}" specified in mapping of class \
"{1}" includes an empty column. "{1}" includes an empty column.
unique-many-on-seq-unsupported: More than one unique constraints is specified \
on sequence generator "{1}" in "{0}". But multiple unique constraint on \
sequence generator is currently not supported.
discriminator-on-abstract-class: A discriminator value has been specified for \ discriminator-on-abstract-class: A discriminator value has been specified for \
the abstract class "{0}". The discriminator will never be used and may be \ the abstract class "{0}". The discriminator will never be used and may be \
safely removed. safely removed.

View File

@ -0,0 +1,78 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
package org.apache.openjpa.persistence.jdbc.unique;
import java.util.Arrays;
import java.util.List;
import javax.persistence.EntityManager;
import org.apache.openjpa.persistence.test.SQLListenerTestCase;
public class TestUnique extends SQLListenerTestCase {
@Override
public void setUp(Object... props) {
super.setUp(UniqueA.class, UniqueB.class);
}
public void testMapping() {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
em.getTransaction().commit();
em.close();
// The above should trigger schema definition
List<String> sqls = super.sql;
assertSQLFragnment(sqls, "CREATE TABLE UNIQUE_A",
"UNIQUE (a1, a2)",
"UNIQUE (a3, a4)");
assertSQLFragnment(sqls, "CREATE TABLE UNIQUE_B",
"UNIQUE (b1, b2)");
assertSQLFragnment(sqls, "CREATE TABLE UNIQUE_SECONDARY",
"UNIQUE (sa1)");
assertSQLFragnment(sqls, "CREATE TABLE UNIQUE_GENERATOR",
"UNIQUE (GEN1, GEN2)");
assertSQLFragnment(sqls, "CREATE TABLE UNIQUE_JOINTABLE",
"UNIQUE (UNIQUEA_AID, BS_BID)");
}
void assertSQLFragnment(List<String> list, String...keys) {
for (String sql : list) {
String SQL = sql.toUpperCase();
boolean matched = true;
for (String key : keys) {
String KEY = key.toUpperCase();
if (SQL.indexOf(KEY) == -1) {
matched = false;
break;
}
}
if (matched)
return;
}
int i = 0;
for (String sql : list) {
i++;
System.out.println(""+i+":"+sql);
}
fail("None of the above SQL contains all keys " + Arrays.toString(keys));
}
}

View File

@ -0,0 +1,77 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
package org.apache.openjpa.persistence.jdbc.unique;
import java.util.Collection;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.SecondaryTable;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
/**
* Data structures for testing unique constraint settings
* on ORM Annotatations.
*
* Unique columns must be non-nullable.
*
* @author Pinaki Poddar
*
*/
@Entity
@Table(name="UNIQUE_A",
uniqueConstraints={@UniqueConstraint(columnNames={"a1","a2"}),
@UniqueConstraint(columnNames={"a3","a4"})})
@SecondaryTable(name="UNIQUE_SECONDARY",
uniqueConstraints=@UniqueConstraint(columnNames={"sa1"}))
public class UniqueA {
@Id
private int aid;
@Column(unique=true, nullable=false)
private int a1;
@Column(nullable=false)
private int a2;
@Column(nullable=false)
private int a3;
@Column(nullable=false)
private int a4;
private int a5;
private int a6;
@Column(table="UNIQUE_SECONDARY", nullable=false)
private short sa1;
@Column(table="UNIQUE_SECONDARY")
private short sa2;
@ManyToMany
@JoinTable(name="UNIQUE_JOINTABLE",
uniqueConstraints=@UniqueConstraint(columnNames={"UNIQUEA_AID","BS_BID"}))
private Collection<UniqueB> bs;
}

View File

@ -0,0 +1,38 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
package org.apache.openjpa.persistence.jdbc.unique;
import javax.persistence.*;
@Entity
@Table(name="UNIQUE_B",
uniqueConstraints={@UniqueConstraint(columnNames={"b1","b2"})})
public class UniqueB {
@Id
@GeneratedValue(strategy=GenerationType.TABLE, generator="testGenerator")
@TableGenerator(name="testGenerator", table="UNIQUE_GENERATOR",
pkColumnName="GEN1", valueColumnName="GEN2",
uniqueConstraints={@UniqueConstraint(columnNames={"GEN1","GEN2"})})
private int bid;
@Column(nullable=false)
private int b1;
@Column(nullable=false)
private int b2;
}