This is an automated email from the ASF dual-hosted git repository. ntimofeev pushed a commit to branch STABLE-4.2 in repository https://gitbox.apache.org/repos/asf/cayenne.git
The following commit(s) were added to refs/heads/STABLE-4.2 by this push: new c56c9fa87 CAY-2838 Vertical Inheritance: Problem setting db attribute to null via flattened path c56c9fa87 is described below commit c56c9fa870537bebd4623203707ac453203b59b5 Author: Nikita Timofeev <stari...@gmail.com> AuthorDate: Thu Feb 29 17:08:19 2024 +0400 CAY-2838 Vertical Inheritance: Problem setting db attribute to null via flattened path --- .../cayenne/dba/sqlserver/SQLServerAdapter.java | 54 +++++++++++++++++++++- .../cayenne/access/VerticalInheritanceIT.java | 29 +++++------- 2 files changed, 65 insertions(+), 18 deletions(-) diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java index 0b71a2028..93efb47ea 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java @@ -20,8 +20,12 @@ package org.apache.cayenne.dba.sqlserver; import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; import java.util.List; +import java.util.stream.Collectors; +import org.apache.cayenne.CayenneRuntimeException; import org.apache.cayenne.access.DataNode; import org.apache.cayenne.access.sqlbuilder.sqltree.SQLTreeProcessor; import org.apache.cayenne.access.types.ExtendedType; @@ -31,6 +35,8 @@ import org.apache.cayenne.configuration.Constants; import org.apache.cayenne.configuration.RuntimeProperties; import org.apache.cayenne.dba.sybase.SybaseAdapter; import org.apache.cayenne.di.Inject; +import org.apache.cayenne.map.DbAttribute; +import org.apache.cayenne.map.DbEntity; import org.apache.cayenne.query.Query; import org.apache.cayenne.query.SQLAction; import org.apache.cayenne.resource.ResourceLocator; @@ -114,7 +120,7 @@ public class SQLServerAdapter extends SybaseAdapter { public boolean supportsGeneratedKeysForBatchInserts() { return false; } - + /** * @since 4.2 */ @@ -157,4 +163,50 @@ public class SQLServerAdapter extends SybaseAdapter { public void setVersion(Integer version) { this.version = version; } + + /** + * Generates DDL to create unique index that allows multiple NULL values to comply with ANSI SQL, + * that is default behaviour for other RDBMS. + * <br> + * Example: + * <pre> + * {@code + * CREATE UNIQUE NONCLUSTERED INDEX _idx_entity_attribute + * ON entity(attribute) + * WHERE attribute IS NOT NULL + * } + * </pre> + * + * @param source entity for the index + * @param columns source columns for the index + * @return DDL to create unique index + * + * @since 4.2.1 + */ + @Override + public String createUniqueConstraint(DbEntity source, Collection<DbAttribute> columns) { + if (columns == null || columns.isEmpty()) { + throw new CayenneRuntimeException("Can't create UNIQUE constraint - no columns specified."); + } + + return "CREATE UNIQUE NONCLUSTERED INDEX " + uniqueIndexName(source, columns) + " ON " + + quotingStrategy.quotedFullyQualifiedName(source) + + "(" + + columns.stream().map(quotingStrategy::quotedName).collect(Collectors.joining(", ")) + + ") WHERE " + + columns.stream().map(quotingStrategy::quotedName) + .map(n -> n + " IS NOT NULL") + .collect(Collectors.joining(" AND ")); + } + + private String uniqueIndexName(DbEntity source, Collection<DbAttribute> columns) { + return "_idx_unique_" + + source.getName().replace(' ', '_').toLowerCase() + + "_" + + columns.stream() + .map(DbAttribute::getName) + .map(String::toLowerCase) + .map(n -> n.replace(' ', '_')) + .collect(Collectors.joining("_")); + } } diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/VerticalInheritanceIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/VerticalInheritanceIT.java index eba76fb85..6f17d11c8 100644 --- a/cayenne-server/src/test/java/org/apache/cayenne/access/VerticalInheritanceIT.java +++ b/cayenne-server/src/test/java/org/apache/cayenne/access/VerticalInheritanceIT.java @@ -33,6 +33,7 @@ import org.apache.cayenne.testdo.inheritance_vertical.*; import org.apache.cayenne.unit.di.server.CayenneProjects; import org.apache.cayenne.unit.di.server.ServerCase; import org.apache.cayenne.unit.di.server.UseServerRuntime; +import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -72,6 +73,15 @@ public class VerticalInheritanceIT extends ServerCase { .setColumnTypes(Types.INTEGER, Types.VARCHAR, Types.INTEGER); } + @After + public void cleanUpConcrete() throws SQLException { + ivConcreteTable.deleteAll(); + ivAbstractTable.deleteAll(); + + assertEquals(0, ivAbstractTable.getRowCount()); + assertEquals(0, ivConcreteTable.getRowCount()); + } + @Test public void testInsert_Root() throws Exception { @@ -630,8 +640,8 @@ public class VerticalInheritanceIT extends ServerCase { assertEquals(parent2, child.getParent()); // Manually delete child to prevent a foreign key constraint failure while cleaning MySQL db - ivConcreteTable.deleteAll(); - ivAbstractTable.deleteAll(); + context.deleteObject(child); + context.commitChanges(); } /** @@ -654,9 +664,6 @@ public class VerticalInheritanceIT extends ServerCase { IvConcrete concreteFetched = SelectById.query(IvConcrete.class, id).selectOne(cleanContext); assertNull(concreteFetched.getName()); } - - ivConcreteTable.deleteAll(); - ivAbstractTable.deleteAll(); } @Test @@ -739,14 +746,6 @@ public class VerticalInheritanceIT extends ServerCase { @Test public void testNullifyFlattenedRelationshipConcreteToAbstract() throws SQLException { - TableHelper ivAbstractTable = new TableHelper(dbHelper, "IV_ABSTRACT"); - ivAbstractTable.setColumns("ID", "PARENT_ID", "TYPE") - .setColumnTypes(Types.INTEGER, Types.INTEGER, Types.CHAR); - - TableHelper ivConcreteTable = new TableHelper(dbHelper, "IV_CONCRETE"); - ivConcreteTable.setColumns("ID", "NAME", "RELATED_ABSTRACT_ID") - .setColumnTypes(Types.INTEGER, Types.VARCHAR, Types.INTEGER); - ivAbstractTable.insert(1, null, "S"); ivConcreteTable.insert(1, "One", null); ivAbstractTable.insert(2, null, "S"); @@ -764,10 +763,6 @@ public class VerticalInheritanceIT extends ServerCase { assertEquals("Two", concreteFetched.getName()); assertNull(concreteFetched.getRelatedAbstract()); } - - // must clean these tables manually - ivConcreteTable.deleteAll(); - ivAbstractTable.deleteAll(); } @Test//(expected = ValidationException.class) // other2 is not mandatory for now