OPENJPA-340: UniqueConstraint supported via XML Descriptors

git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@672406 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Pinaki Poddar 2008-06-27 22:02:34 +00:00
parent ff4a96e707
commit 6868b1cc4a
12 changed files with 414 additions and 56 deletions

View File

@ -20,6 +20,7 @@ package org.apache.openjpa.jdbc.meta;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
@ -212,9 +213,17 @@ public class ClassMappingInfo
}
return (cols == null) ? Collections.EMPTY_LIST : cols;
}
/**
* Adds a Secondary table of given name to this mapping. A secondary table
* must be known before unique constraints are added to a Secondary table.
*/
public void addSecondaryTable(String second) {
setSecondaryTableJoinColumns(second, null);
}
/**
* Declare the given class-level join.
* Declare the given class-level join to the named (secondary) table.
*/
public void setSecondaryTableJoinColumns(String tableName, List cols) {
if (cols == null)
@ -419,7 +428,7 @@ public class ClassMappingInfo
if (!table.containsColumn(columnName)) {
throw new UserException(_loc.get("unique-missing-column",
new Object[]{cm, columnName, tableName,
table.getColumnNames()}));
Arrays.toString(table.getColumnNames())}));
}
Column uniqueColumn = table.getColumn(columnName);
uniqueColumns[i] = uniqueColumn;

View File

@ -928,6 +928,11 @@ public abstract class MappingInfo
deferred = false;
}
if (StringUtils.isEmpty(name)) {
name = cols[0].getName();
name = repos.getDBDictionary().getValidUniqueName(name, table);
}
Unique unq = table.addUnique(name);
unq.setDeferred(deferred);
unq.setColumns(cols);

View File

@ -24,48 +24,83 @@ import org.apache.commons.lang.StringUtils;
* Represents a unique constraint. It can also represent a partial constraint.
*
* @author Abe White
* @author Pinaki Poddar
*/
public class Unique
extends LocalConstraint {
private boolean _isAutoSetName = false;
/**
* Default constructor.
private boolean _autoNaming = false;
/**
* Default constructor without a name.
* Assumes that this constraint will set its own name automatically from
* the names of the columns added to it.
*/
public Unique() {
_isAutoSetName = true;
_autoNaming = true;
}
/**
* Constructor.
*
* Construct with given name.
* Assumes that this constraint will not set its own name.
*
* @param name the name of the constraint, if any
* @param table the table of the constraint
*/
public Unique(String name, Table table) {
super(name, table);
_autoNaming = false;
}
public boolean isLogical() {
return false;
}
/**
* Adds the given column.
* The added column is set to non-nullable because a unique constraint
* on the database requires that its constituent columns are NOT NULL.
* @see Column#setNotNull(boolean)
* If this instance is constructing its own name, then this method also
* has the side effect of changing its own name by appending the newly
* added column name to its own name.
*/
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;
if (_autoNaming && getTable() == null) {
String prefix = createPrefix();
setName(prefix + "_" + chop(col.getName(), 4));
_autoNaming = true;
}
}
private String createPrefix() {
String currentName = getName();
if (StringUtils.isEmpty(currentName)) {
String tname = getTableName();
if (StringUtils.isEmpty(tname))
return "UNQ";
else
return "UNQ_" + chop(tname, 3);
}
return currentName;
}
private String chop(String name, int head) {
if (StringUtils.isEmpty(name))
return name;
return name.substring(0, Math.min(Math.max(1,head), name.length()));
}
/**
* Set the name of the constraint. This method cannot be called if the
* constraint already belongs to a table.
* constraint already belongs to a table. Calling this method also has the
* side-effect of implying that the instance will not auto-generate its
* name.
*/
public void setName(String name) {
super.setName(name);
_isAutoSetName = false;
_autoNaming = false;
}
@ -76,4 +111,13 @@ public class Unique
public boolean equalsUnique(Unique unq) {
return equalsLocalConstraint(unq);
}
/*
* Affirms if this instance is currently generating its own name. No
* mutator because auto-naming is switched off as side-effect of user
* calling setName() directly.
*/
public boolean isAutoNaming() {
return _autoNaming;
}
}

View File

@ -483,8 +483,10 @@ public class AnnotationPersistenceMappingParser
joins = new ArrayList<Column>(table.pkJoinColumns().length);
for (PrimaryKeyJoinColumn join : table.pkJoinColumns())
joins.add(newColumn(join));
}
info.setSecondaryTableJoinColumns(name, joins);
info.setSecondaryTableJoinColumns(name, joins);
} else {
info.addSecondaryTable(name);
}
addUniqueConstraints(name, cm, info, table.uniqueConstraints());
}
}

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.DiscriminatorMappingInfo;
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.MappingRepository;
import org.apache.openjpa.jdbc.meta.QueryResultMapping;
@ -359,6 +360,8 @@ public class XMLPersistenceMappingParser
throws SAXException {
_secondaryTable = toTableName(attrs.getValue("schema"),
attrs.getValue("name"));
((ClassMapping)currentElement()).getMappingInfo()
.addSecondaryTable(_secondaryTable);
return true;
}
@ -906,18 +909,23 @@ public class XMLPersistenceMappingParser
Unique unique = (Unique) popElement();
Object ctx = currentElement();
String tableName = "?";
ClassMappingInfo info = null;
if (ctx instanceof ClassMapping) {
info = ((ClassMapping) ctx).getMappingInfo();
tableName = (_secondaryTable != null) ? info.getTableName() : _secondaryTable;
ClassMappingInfo 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);
FieldMappingInfo info = ((FieldMapping)ctx).getMappingInfo();
info.addJoinTableUnique(unique);
} else if (ctx instanceof SequenceMapping) {
tableName = ((SequenceMapping)ctx).getTable();
unique.setTableName(tableName);
SequenceMapping seq = (SequenceMapping)ctx;
unique.setTableName(seq.getTable());
Column[] uniqueColumns = unique.getColumns();
String[] columnNames = new String[uniqueColumns.length];
int i = 0;
for (Column uniqueColumn : uniqueColumns)
columnNames[i++] = uniqueColumn.getName();
seq.setUniqueColumns(columnNames);
} else {
throw new InternalException();
}

View File

@ -0,0 +1,66 @@
/*
* 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;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.regexp.RE;
/**
* Utility class to verify whether a set of fragments appear in a list of
* possible SQL statement.
*
* @author Pinaki Poddar
*
*/
public class SQLSniffer {
private static Map<String, RE> cache = new HashMap<String, RE>();
/**
* Checks that the given set of regular expressions occur in at least one of
* the given input SQL.
*/
public static boolean matches(List<String> SQLs, String...regexes) {
if (SQLs == null || regexes == null)
return false;
for (String sql : SQLs) {
boolean matched = true;
for (String key : regexes) {
RE regex = getRegularExpression(key);
if (!regex.match(sql)) {
matched = false;
break;
}
}
if (matched)
return true;
}
return false;
}
private static RE getRegularExpression(String regex) {
if (cache.containsKey(regex))
return cache.get(regex);
RE re = new RE(regex);
cache.put(regex, re);
return re;
}
}

View File

@ -23,12 +23,24 @@ import java.util.List;
import javax.persistence.EntityManager;
import org.apache.openjpa.persistence.jdbc.SQLSniffer;
import org.apache.openjpa.persistence.test.SQLListenerTestCase;
public class TestUnique extends SQLListenerTestCase {
/**
* Tests unique constraints specified via annotations for primary/secondary
* table, sequence generator, join tables have been defined on database by
* examining DDL statements.
*
* @see resources/org/apache/openjpa/persistence/jdbc/unique/orm.xml defines
* the ORM mapping.
*
* @author Pinaki Poddar
*
*/
public class TestUniqueConstraint extends SQLListenerTestCase {
@Override
public void setUp(Object... props) {
super.setUp(UniqueA.class, UniqueB.class);
super.setUp(DROP_TABLES, UniqueA.class, UniqueB.class);
}
public void testMapping() {
@ -40,39 +52,28 @@ public class TestUnique extends SQLListenerTestCase {
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)");
assertSQLFragnments(sqls, "CREATE TABLE UNIQUE_A",
"UNIQUE \\w*\\(a1, a2\\)",
"UNIQUE \\w*\\(a3, a4\\)");
assertSQLFragnments(sqls, "CREATE TABLE UNIQUE_B",
"UNIQUE \\w*\\(b1, b2\\)");
assertSQLFragnments(sqls, "CREATE TABLE UNIQUE_SECONDARY",
"UNIQUE \\w*\\(sa1\\)");
assertSQLFragnments(sqls, "CREATE TABLE UNIQUE_GENERATOR",
"UNIQUE \\w*\\(GEN1, GEN2\\)");
assertSQLFragnments(sqls, "CREATE TABLE UNIQUE_JOINTABLE",
"UNIQUE \\w*\\(FK_A, FK_B\\)");
}
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;
}
void assertSQLFragnments(List<String> list, String... keys) {
if (SQLSniffer.matches(list, keys))
return;
int i = 0;
for (String sql : list) {
i++;
System.out.println(""+i+":"+sql);
System.out.println("" + i + ":" + sql);
}
fail("None of the above SQL contains all keys " + Arrays.toString(keys));
fail("None of the " + sql.size() + " SQL contains all keys "
+ Arrays.toString(keys));
}
}

View File

@ -0,0 +1,86 @@
/*
* 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.jdbc.SQLSniffer;
import org.apache.openjpa.persistence.test.SQLListenerTestCase;
/**
* Tests unique constraints specified via XML Descriptor for primary/secondary
* table, sequence generator, join tables have been defined on database by
* examining DDL statements.
*
* @see resources/org/apache/openjpa/persistence/jdbc/unique/orm.xml
* defines the ORM mapping.
*
* @author Pinaki Poddar
*
*/
public class TestUniqueConstraintWithXMLDescriptor extends SQLListenerTestCase {
@Override
public void setUp(Object... props) {
super.setUp(DROP_TABLES, UniqueA.class, UniqueB.class);
}
protected String getPersistenceUnitName() {
return "test-unique-constraint";
}
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;
assertFalse("No SQL DDL registered", sqls.isEmpty());
// Following verification techniques is fragile as databases DDL
// syntax vary greatly on UNIQUE CONSTRAINT
assertSQLFragnments(sqls, "CREATE TABLE UNIQUE_A_XML",
"UNIQUE \\w*\\(a1x, a2x\\)",
"UNIQUE \\w*\\(a3x, a4x\\)");
assertSQLFragnments(sqls, "CREATE TABLE UNIQUE_B_XML",
"UNIQUE \\w*\\(b1x, b2x\\)");
assertSQLFragnments(sqls, "CREATE TABLE UNIQUE_SECONDARY_XML",
"UNIQUE \\w*\\(sa1x\\)");
assertSQLFragnments(sqls, "CREATE TABLE UNIQUE_GENERATOR_XML",
"UNIQUE \\w*\\(GEN1_XML, GEN2_XML\\)");
assertSQLFragnments(sqls, "CREATE TABLE UNIQUE_JOINTABLE_XML",
"UNIQUE \\w*\\(FK_A_XML, FK_B_XML\\)");
}
void assertSQLFragnments(List<String> list, String... keys) {
if (SQLSniffer.matches(list, keys))
return;
int i = 0;
for (String sql : list) {
i++;
System.out.println("" + i + ":" + sql);
}
fail("None of the " + sql.size() + " SQL contains all keys "
+ Arrays.toString(keys));
}
}

View File

@ -23,6 +23,7 @@ import java.util.Collection;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.SecondaryTable;
@ -72,6 +73,8 @@ public class UniqueA {
@ManyToMany
@JoinTable(name="UNIQUE_JOINTABLE",
uniqueConstraints=@UniqueConstraint(columnNames={"UNIQUEA_AID","BS_BID"}))
joinColumns={@JoinColumn(name="FK_A", nullable=false, referencedColumnName="aid")},
inverseJoinColumns={@JoinColumn(name="FK_B", nullable=false, referencedColumnName="bid")},
uniqueConstraints=@UniqueConstraint(columnNames={"FK_A","FK_B"}))
private Collection<UniqueB> bs;
}

View File

@ -47,6 +47,7 @@ public abstract class PersistenceTestCase
* database tables should be cleared.
*/
protected static final Object CLEAR_TABLES = new Object();
protected static final Object DROP_TABLES = new Object();
/**
* The {@link TestResult} instance for the current test run.
@ -95,6 +96,10 @@ public abstract class PersistenceTestCase
map.put("openjpa.jdbc.SynchronizeMappings",
"buildSchema(ForeignKeys=true,"
+ "SchemaAction='add,deleteTableContents')");
} else if (props[i] == DROP_TABLES) {
map.put("openjpa.jdbc.SynchronizeMappings",
"buildSchema(ForeignKeys=true,"
+ "SchemaAction='drop,add')");
} else if (props[i] instanceof Class)
types.add((Class) props[i]);
else if (props[i] != null)

View File

@ -88,5 +88,12 @@
<property name="openjpa.jdbc.SynchronizeMappings"
value="buildSchema(ForeignKeys=true)"/>
</properties>
</persistence-unit>
</persistence-unit>
<persistence-unit name="test-unique-constraint">
<mapping-file>org/apache/openjpa/persistence/jdbc/unique/orm.xml</mapping-file>
<class>org.apache.openjpa.persistence.jdbc.unique.UniqueA</class>
<class>org.apache.openjpa.persistence.jdbc.unique.UniqueB</class>
</persistence-unit>
</persistence>

View File

@ -0,0 +1,122 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm orm_1_0.xsd"
version="1.0">
<persistence-unit-metadata>
<xml-mapping-metadata-complete>
</xml-mapping-metadata-complete>
</persistence-unit-metadata>
<package>org.apache.openjpa.persistence.jdbc.unique</package>
<entity name="UniqueA" class="UniqueA">
<table name="UNIQUE_A_XML">
<unique-constraint>
<column-name>a1x</column-name>
<column-name>a2x</column-name>
</unique-constraint>
<unique-constraint>
<column-name>a3x</column-name>
<column-name>a4x</column-name>
</unique-constraint>
</table>
<secondary-table name="UNIQUE_SECONDARY_XML">
<unique-constraint>
<column-name>sa1x</column-name>
</unique-constraint>
</secondary-table>
<attributes>
<id name="aid">
</id>
<basic name="a1">
<column name="a1x"/>
</basic>
<basic name="a2">
<column name="a2x"/>
</basic>
<basic name="a3">
<column name="a3x"/>
</basic>
<basic name="a4">
<column name="a4x"/>
</basic>
<basic name="a5">
<column name="a5x"/>
</basic>
<basic name="a6">
<column name="a6x"/>
</basic>
<basic name="sa1">
<column name="sa1x" table="UNIQUE_SECONDARY_XML" />
</basic>
<basic name="sa2">
<column name="sa2x" table="UNIQUE_SECONDARY_XML" />
</basic>
<many-to-many name="bs">
<join-table name="UNIQUE_JOINTABLE_XML">
<join-column name="FK_A_XML" referenced-column-name="aid" nullable="false">
</join-column>
<inverse-join-column name="FK_B_XML" referenced-column-name="bid" nullable="false">
</inverse-join-column>
<unique-constraint>
<column-name>FK_A_XML</column-name>
<column-name>FK_B_XML</column-name>
</unique-constraint>
</join-table>
</many-to-many>
</attributes>
</entity>
<entity name="UniqueB" class="UniqueB">
<table name="UNIQUE_B_XML">
<unique-constraint>
<column-name>b1x</column-name>
<column-name>b2x</column-name>
</unique-constraint>
</table>
<attributes>
<id name="bid">
<generated-value strategy="TABLE"
generator="testGeneratorXML" />
<table-generator name="testGeneratorXML"
table="UNIQUE_GENERATOR_XML" pk-column-name="GEN1_XML"
value-column-name="GEN2_XML">
<unique-constraint>
<column-name>GEN1_XML</column-name>
<column-name>GEN2_XML</column-name>
</unique-constraint>
</table-generator>
</id>
<basic name="b1">
<column name="b1x"/>
</basic>
<basic name="b2">
<column name="b2x"/>
</basic>
</attributes>
</entity>
</entity-mappings>