diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2e091a53..3ef4f8e2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -18,7 +18,7 @@ assertj = "3.27.3" google-errorprone-core = "2.36.0" nullaway = "0.12.4" jspecify = "1.0.0" -hibernate-orm = "6.6.9.Final" +hibernate-orm = "6.6.21.Final" mongo-java-driver-sync = "5.3.1" slf4j-api = "2.0.16" logback-classic = "1.5.16" diff --git a/src/integrationTest/java/com/mongodb/hibernate/ArrayAndCollectionIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/ArrayAndCollectionIntegrationTests.java index 868bb9c8..84dc6e7d 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/ArrayAndCollectionIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/ArrayAndCollectionIntegrationTests.java @@ -17,6 +17,7 @@ package com.mongodb.hibernate; import static com.mongodb.hibernate.MongoTestAssertions.assertEq; +import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.hibernate.cfg.AvailableSettings.WRAPPER_ARRAY_HANDLING; @@ -41,13 +42,13 @@ import org.bson.types.ObjectId; import org.hibernate.MappingException; import org.hibernate.boot.MetadataSources; +import org.hibernate.exception.GenericJDBCException; import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.ServiceRegistry; import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.hibernate.testing.orm.junit.SessionFactoryScopeAware; import org.hibernate.testing.orm.junit.Setting; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -78,33 +79,32 @@ public void injectSessionFactoryScope(SessionFactoryScope sessionFactoryScope) { void testArrayAndCollectionValues() { var item = new ItemWithArrayAndCollectionValues( 1, - // TODO-HIBERNATE-48 sprinkle on `null` array/collection elements new byte[] {2, 3}, new char[] {'s', 't', 'r'}, new int[] {5}, new long[] {Long.MAX_VALUE, 6}, new double[] {Double.MAX_VALUE}, new boolean[] {true}, - new Character[] {'s', 't', 'r'}, - new Integer[] {7}, - new Long[] {8L}, - new Double[] {9.1d}, - new Boolean[] {true}, - new String[] {"str"}, - new BigDecimal[] {BigDecimal.valueOf(10.1)}, - new ObjectId[] {new ObjectId("000000000000000000000001")}, + new Character[] {'s', null, 't', 'r'}, + new Integer[] {null, 7}, + new Long[] {8L, null}, + new Double[] {9.1d, null}, + new Boolean[] {true, null}, + new String[] {null, "str"}, + new BigDecimal[] {null, BigDecimal.valueOf(10.1)}, + new ObjectId[] {new ObjectId("000000000000000000000001"), null}, new StructAggregateEmbeddableIntegrationTests.Single[] { - new StructAggregateEmbeddableIntegrationTests.Single(1) + new StructAggregateEmbeddableIntegrationTests.Single(1), null }, - List.of('s', 't', 'r'), - List.of(5), - List.of(Long.MAX_VALUE, 6L), - List.of(Double.MAX_VALUE), - List.of(true), - List.of("str"), - List.of(BigDecimal.valueOf(10.1)), - List.of(new ObjectId("000000000000000000000001")), - List.of(new StructAggregateEmbeddableIntegrationTests.Single(1))); + asList('s', 't', null, 'r'), + asList(null, 5), + asList(Long.MAX_VALUE, null, 6L), + asList(null, Double.MAX_VALUE), + asList(null, true), + asList("str", null), + asList(BigDecimal.valueOf(10.1), null), + asList(null, new ObjectId("000000000000000000000001")), + asList(new StructAggregateEmbeddableIntegrationTests.Single(1), null)); sessionFactoryScope.inTransaction(session -> session.persist(item)); assertCollectionContainsExactly( """ @@ -116,24 +116,24 @@ void testArrayAndCollectionValues() { longs: [{$numberLong: "9223372036854775807"}, {$numberLong: "6"}], doubles: [{$numberDouble: "1.7976931348623157E308"}], booleans: [true], - boxedChars: ["s", "t", "r"], - boxedInts: [7], - boxedLongs: [{$numberLong: "8"}], - boxedDoubles: [{$numberDouble: "9.1"}], - boxedBooleans: [true], - strings: ["str"], - bigDecimals: [{$numberDecimal: "10.1"}], - objectIds: [{$oid: "000000000000000000000001"}], - structAggregateEmbeddables: [{a: 1}], - charsCollection: ["s", "t", "r"], - intsCollection: [5], - longsCollection: [{$numberLong: "9223372036854775807"}, {$numberLong: "6"}], - doublesCollection: [{$numberDouble: "1.7976931348623157E308"}], - booleansCollection: [true], - stringsCollection: ["str"], - bigDecimalsCollection: [{$numberDecimal: "10.1"}], - objectIdsCollection: [{$oid: "000000000000000000000001"}], - structAggregateEmbeddablesCollection: [{a: 1}] + boxedChars: ["s", null, "t", "r"], + boxedInts: [null, 7], + boxedLongs: [{$numberLong: "8"}, null], + boxedDoubles: [{$numberDouble: "9.1"}, null], + boxedBooleans: [true, null], + strings: [null, "str"], + bigDecimals: [null, {$numberDecimal: "10.1"}], + objectIds: [{$oid: "000000000000000000000001"}, null], + structAggregateEmbeddables: [{a: 1}, null], + charsCollection: ["s", "t", null, "r"], + intsCollection: [null, 5], + longsCollection: [{$numberLong: "9223372036854775807"}, null, {$numberLong: "6"}], + doublesCollection: [null, {$numberDouble: "1.7976931348623157E308"}], + booleansCollection: [null, true], + stringsCollection: ["str", null], + bigDecimalsCollection: [{$numberDecimal: "10.1"}, null], + objectIdsCollection: [null, {$oid: "000000000000000000000001"}], + structAggregateEmbeddablesCollection: [{a: 1}, null] } """); var loadedItem = sessionFactoryScope.fromTransaction( @@ -158,24 +158,24 @@ void testArrayAndCollectionValues() { longs: [{$numberLong: "9223372036854775807"}, {$numberLong: "-6"}], doubles: [{$numberDouble: "1.7976931348623157E308"}], booleans: [true], - boxedChars: ["s", "t", "r"], - boxedInts: [7], - boxedLongs: [{$numberLong: "8"}], - boxedDoubles: [{$numberDouble: "9.1"}], - boxedBooleans: [true], - strings: ["str"], - bigDecimals: [{$numberDecimal: "10.1"}], - objectIds: [{$oid: "000000000000000000000002"}], - structAggregateEmbeddables: [{a: 1}], - charsCollection: ["s", "t", "r"], - intsCollection: [5], - longsCollection: [{$numberLong: "9223372036854775807"}, {$numberLong: "-6"}], - doublesCollection: [{$numberDouble: "1.7976931348623157E308"}], - booleansCollection: [true], - stringsCollection: ["str"], - bigDecimalsCollection: [{$numberDecimal: "10.1"}], - objectIdsCollection: [{$oid: "000000000000000000000001"}], - structAggregateEmbeddablesCollection: [{a: 1}] + boxedChars: ["s", null, "t", "r"], + boxedInts: [null, 7], + boxedLongs: [{$numberLong: "8"}, null], + boxedDoubles: [{$numberDouble: "9.1"}, null], + boxedBooleans: [true, null], + strings: [null, "str"], + bigDecimals: [null, {$numberDecimal: "10.1"}], + objectIds: [{$oid: "000000000000000000000002"}, null], + structAggregateEmbeddables: [{a: 1}, null], + charsCollection: ["s", "t", null, "r"], + intsCollection: [null, 5], + longsCollection: [{$numberLong: "9223372036854775807"}, null, {$numberLong: "-6"}], + doublesCollection: [null, {$numberDouble: "1.7976931348623157E308"}], + booleansCollection: [null, true], + stringsCollection: ["str", null], + bigDecimalsCollection: [{$numberDecimal: "10.1"}, null], + objectIdsCollection: [null, {$oid: "000000000000000000000001"}], + structAggregateEmbeddablesCollection: [{a: 1}, null] } """); loadedItem = sessionFactoryScope.fromTransaction( @@ -248,7 +248,6 @@ void testArrayAndCollectionEmptyValues() { } @Test - @Disabled("TODO-HIBERNATE-48 https://jira.mongodb.org/browse/HIBERNATE-48 enable this test") void testArrayAndCollectionNullValues() { var item = new ItemWithArrayAndCollectionValues( 1, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, @@ -410,7 +409,6 @@ void testArrayAndCollectionValuesOfStructAggregateEmbeddablesHavingArraysAndColl * @see StructAggregateEmbeddableIntegrationTests#testNestedValueHavingNullArraysAndCollections() */ @Test - @Disabled("TODO-HIBERNATE-48 https://jira.mongodb.org/browse/HIBERNATE-48 enable this test") public void testArrayAndCollectionValuesOfEmptyStructAggregateEmbeddables() { var emptyStructAggregateEmbeddable = new ArraysAndCollections( null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, @@ -597,16 +595,13 @@ static class ItemWithArrayAndCollectionValuesOfStructAggregateEmbeddablesHavingA @Nested class Unsupported { - /** - * The {@link ClassCastException} caught here manifests a Hibernate ORM bug. The issue goes away if the - * {@link ItemWithBoxedBytesArrayValue#bytes} field is removed. Otherwise, the behavior of this test should have - * been equivalent to {@link #testBytesCollectionValue()}. - */ + @Test void testBoxedBytesArrayValue() { var item = new ItemWithBoxedBytesArrayValue(1, new byte[] {1}, new Byte[] {2}); assertThatThrownBy(() -> sessionFactoryScope.inTransaction(session -> session.persist(item))) - .isInstanceOf(ClassCastException.class); + .isInstanceOf(GenericJDBCException.class) + .hasCauseInstanceOf(SQLFeatureNotSupportedException.class); } @Test diff --git a/src/integrationTest/java/com/mongodb/hibernate/BasicCrudIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/BasicCrudIntegrationTests.java index f470d0fa..359f8624 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/BasicCrudIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/BasicCrudIntegrationTests.java @@ -32,7 +32,6 @@ import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.hibernate.testing.orm.junit.SessionFactoryScopeAware; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -97,7 +96,6 @@ void testSimpleEntityInsertion() { } @Test - @Disabled("TODO-HIBERNATE-48 https://jira.mongodb.org/browse/HIBERNATE-48 enable this test") void testEntityWithNullFieldValuesInsertion() { sessionFactoryScope.inTransaction(session -> session.persist(new Item( 1, @@ -218,7 +216,6 @@ void testSimpleUpdate() { } @Test - @Disabled("TODO-HIBERNATE-48 https://jira.mongodb.org/browse/HIBERNATE-48 enable this test") void testSimpleUpdateWithNullFieldValues() { sessionFactoryScope.inTransaction(session -> { var item = new Item( @@ -290,7 +287,6 @@ void testDynamicUpdate() { } @Test - @Disabled("TODO-HIBERNATE-48 https://jira.mongodb.org/browse/HIBERNATE-48 enable this test") void testDynamicUpdateWithNullFieldValues() { sessionFactoryScope.inTransaction(session -> { var item = new ItemDynamicallyUpdated(1, false, true); @@ -337,7 +333,6 @@ void testFindByPrimaryKey() { } @Test - @Disabled("TODO-HIBERNATE-48 https://jira.mongodb.org/browse/HIBERNATE-48 enable this test") void testFindByPrimaryKeyWithNullFieldValues() { var item = new Item( 1, 'c', 1, Long.MAX_VALUE, Double.MAX_VALUE, true, null, null, null, null, null, null, null, null); diff --git a/src/integrationTest/java/com/mongodb/hibernate/embeddable/EmbeddableIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/embeddable/EmbeddableIntegrationTests.java index 58a725ad..ff35df84 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/embeddable/EmbeddableIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/embeddable/EmbeddableIntegrationTests.java @@ -18,6 +18,7 @@ import static com.mongodb.hibernate.MongoTestAssertions.assertEq; import static com.mongodb.hibernate.MongoTestAssertions.assertUsingRecursiveComparison; +import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertNull; @@ -36,6 +37,7 @@ import jakarta.persistence.Table; import java.math.BigDecimal; import java.util.Collection; +import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; @@ -48,7 +50,6 @@ import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.hibernate.testing.orm.junit.SessionFactoryScopeAware; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -151,7 +152,6 @@ void testFlattenedValues() { } @Test - @Disabled("TODO-HIBERNATE-48 https://jira.mongodb.org/browse/HIBERNATE-48 enable this test") void testFlattenedNullValueOrHavingNulls() { var item = new ItemWithFlattenedValues( new Single(1), @@ -234,33 +234,32 @@ void testFlattenedValueHavingArraysAndCollections() { var item = new ItemWithFlattenedValueHavingArraysAndCollections( 1, new ArraysAndCollections( - // TODO-HIBERNATE-48 sprinkle on `null` array/collection elements new byte[] {2, 3}, new char[] {'s', 't', 'r'}, new int[] {5}, new long[] {Long.MAX_VALUE, 6}, new double[] {Double.MAX_VALUE}, new boolean[] {true}, - new Character[] {'s', 't', 'r'}, - new Integer[] {7}, - new Long[] {8L}, - new Double[] {9.1d}, - new Boolean[] {true}, - new String[] {"str"}, - new BigDecimal[] {BigDecimal.valueOf(10.1)}, - new ObjectId[] {new ObjectId("000000000000000000000001")}, + new Character[] {'s', null, 't', 'r'}, + new Integer[] {null, 7}, + new Long[] {8L, null}, + new Double[] {9.1d, null}, + new Boolean[] {true, null}, + new String[] {null, "str"}, + new BigDecimal[] {null, BigDecimal.valueOf(10.1)}, + new ObjectId[] {new ObjectId("000000000000000000000001"), null}, new StructAggregateEmbeddableIntegrationTests.Single[] { - new StructAggregateEmbeddableIntegrationTests.Single(1) + new StructAggregateEmbeddableIntegrationTests.Single(1), null }, - List.of('s', 't', 'r'), - Set.of(5), - List.of(Long.MAX_VALUE, 6L), - List.of(Double.MAX_VALUE), - List.of(true), - List.of("str"), - List.of(BigDecimal.valueOf(10.1)), - List.of(new ObjectId("000000000000000000000001")), - List.of(new StructAggregateEmbeddableIntegrationTests.Single(1)))); + asList('s', 't', null, 'r'), + new HashSet<>(asList(null, 5)), + asList(Long.MAX_VALUE, null, 6L), + asList(null, Double.MAX_VALUE), + asList(null, true), + asList("str", null), + asList(BigDecimal.valueOf(10.1), null), + asList(null, new ObjectId("000000000000000000000001")), + asList(new StructAggregateEmbeddableIntegrationTests.Single(1), null))); sessionFactoryScope.inTransaction(session -> session.persist(item)); assertCollectionContainsExactly( """ @@ -272,24 +271,24 @@ void testFlattenedValueHavingArraysAndCollections() { longs: [{$numberLong: "9223372036854775807"}, {$numberLong: "6"}], doubles: [{$numberDouble: "1.7976931348623157E308"}], booleans: [true], - boxedChars: ["s", "t", "r"], - boxedInts: [7], - boxedLongs: [{$numberLong: "8"}], - boxedDoubles: [{$numberDouble: "9.1"}], - boxedBooleans: [true], - strings: ["str"], - bigDecimals: [{$numberDecimal: "10.1"}], - objectIds: [{$oid: "000000000000000000000001"}], - structAggregateEmbeddables: [{a: 1}], - charsCollection: ["s", "t", "r"], - intsCollection: [5], - longsCollection: [{$numberLong: "9223372036854775807"}, {$numberLong: "6"}], - doublesCollection: [{$numberDouble: "1.7976931348623157E308"}], - booleansCollection: [true], - stringsCollection: ["str"], - bigDecimalsCollection: [{$numberDecimal: "10.1"}], - objectIdsCollection: [{$oid: "000000000000000000000001"}], - structAggregateEmbeddablesCollection: [{a: 1}] + boxedChars: ["s", null, "t", "r"], + boxedInts: [null, 7], + boxedLongs: [{$numberLong: "8"}, null], + boxedDoubles: [{$numberDouble: "9.1"}, null], + boxedBooleans: [true, null], + strings: [null, "str"], + bigDecimals: [null, {$numberDecimal: "10.1"}], + objectIds: [{$oid: "000000000000000000000001"}, null], + structAggregateEmbeddables: [{a: 1}, null], + charsCollection: ["s", "t", null, "r"], + intsCollection: [null, 5], + longsCollection: [{$numberLong: "9223372036854775807"}, null, {$numberLong: "6"}], + doublesCollection: [null, {$numberDouble: "1.7976931348623157E308"}], + booleansCollection: [null, true], + stringsCollection: ["str", null], + bigDecimalsCollection: [{$numberDecimal: "10.1"}, null], + objectIdsCollection: [null, {$oid: "000000000000000000000001"}], + structAggregateEmbeddablesCollection: [{a: 1}, null] } """); var loadedItem = sessionFactoryScope.fromTransaction( @@ -314,24 +313,24 @@ void testFlattenedValueHavingArraysAndCollections() { longs: [{$numberLong: "9223372036854775807"}, {$numberLong: "-6"}], doubles: [{$numberDouble: "1.7976931348623157E308"}], booleans: [true], - boxedChars: ["s", "t", "r"], - boxedInts: [7], - boxedLongs: [{$numberLong: "8"}], - boxedDoubles: [{$numberDouble: "9.1"}], - boxedBooleans: [true], - strings: ["str"], - bigDecimals: [{$numberDecimal: "10.1"}], - objectIds: [{$oid: "000000000000000000000002"}], - structAggregateEmbeddables: [{a: 1}], - charsCollection: ["s", "t", "r"], - intsCollection: [5], - longsCollection: [{$numberLong: "9223372036854775807"}, {$numberLong: "-6"}], - doublesCollection: [{$numberDouble: "1.7976931348623157E308"}], - booleansCollection: [true], - stringsCollection: ["str"], - bigDecimalsCollection: [{$numberDecimal: "10.1"}], - objectIdsCollection: [{$oid: "000000000000000000000001"}], - structAggregateEmbeddablesCollection: [{a: 1}] + boxedChars: ["s", null, "t", "r"], + boxedInts: [null, 7], + boxedLongs: [{$numberLong: "8"}, null], + boxedDoubles: [{$numberDouble: "9.1"}, null], + boxedBooleans: [true, null], + strings: [null, "str"], + bigDecimals: [null, {$numberDecimal: "10.1"}], + objectIds: [{$oid: "000000000000000000000002"}, null], + structAggregateEmbeddables: [{a: 1}, null], + charsCollection: ["s", "t", null, "r"], + intsCollection: [null, 5], + longsCollection: [{$numberLong: "9223372036854775807"}, null, {$numberLong: "-6"}], + doublesCollection: [null, {$numberDouble: "1.7976931348623157E308"}], + booleansCollection: [null, true], + stringsCollection: ["str", null], + bigDecimalsCollection: [{$numberDecimal: "10.1"}, null], + objectIdsCollection: [null, {$oid: "000000000000000000000001"}], + structAggregateEmbeddablesCollection: [{a: 1}, null] } """); loadedItem = sessionFactoryScope.fromTransaction( @@ -412,7 +411,6 @@ void testFlattenedValueHavingEmptyArraysAndCollections() { * @see ArrayAndCollectionIntegrationTests#testArrayAndCollectionValuesOfEmptyStructAggregateEmbeddables() */ @Test - @Disabled("TODO-HIBERNATE-48 https://jira.mongodb.org/browse/HIBERNATE-48 enable this test") public void testFlattenedValueHavingNullArraysAndCollections() { var emptyEmbeddable = new ArraysAndCollections( null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, @@ -460,7 +458,6 @@ public void testFlattenedValueHavingNullArraysAndCollections() { } @Test - @Disabled("TODO-HIBERNATE-48 https://jira.mongodb.org/browse/HIBERNATE-48 enable this test") void testReadNestedValuesMissingFields() { var insertResult = mongoCollection.insertOne( BsonDocument.parse( diff --git a/src/integrationTest/java/com/mongodb/hibernate/embeddable/StructAggregateEmbeddableIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/embeddable/StructAggregateEmbeddableIntegrationTests.java index e42bb6d3..3ab6ac72 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/embeddable/StructAggregateEmbeddableIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/embeddable/StructAggregateEmbeddableIntegrationTests.java @@ -18,6 +18,7 @@ import static com.mongodb.hibernate.MongoTestAssertions.assertEq; import static com.mongodb.hibernate.MongoTestAssertions.assertUsingRecursiveComparison; +import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertNull; @@ -36,6 +37,7 @@ import java.math.BigDecimal; import java.sql.SQLFeatureNotSupportedException; import java.util.Collection; +import java.util.HashSet; import java.util.List; import java.util.Set; import org.bson.BsonDocument; @@ -47,7 +49,6 @@ import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.hibernate.testing.orm.junit.SessionFactoryScopeAware; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -164,7 +165,6 @@ void testNestedValues() { } @Test - @Disabled("TODO-HIBERNATE-48 https://jira.mongodb.org/browse/HIBERNATE-48 enable this test") void testNestedNullValueOrHavingNulls() { var item = new ItemWithNestedValues( new EmbeddableIntegrationTests.Single(1), @@ -240,7 +240,6 @@ void testNestedNullValueOrHavingNulls() { void testNestedValueHavingArraysAndCollections() { var item = new ItemWithNestedValueHavingArraysAndCollections( 1, - // TODO-HIBERNATE-48 sprinkle on `null` array/collection elements new ArraysAndCollections( new byte[] {2, 3}, new char[] {'s', 't', 'r'}, @@ -248,24 +247,24 @@ void testNestedValueHavingArraysAndCollections() { new long[] {Long.MAX_VALUE, 6}, new double[] {Double.MAX_VALUE}, new boolean[] {true}, - new Character[] {'s', 't', 'r'}, - new Integer[] {7}, - new Long[] {8L}, - new Double[] {9.1d}, - new Boolean[] {true}, - new String[] {"str"}, - new BigDecimal[] {BigDecimal.valueOf(10.1)}, - new ObjectId[] {new ObjectId("000000000000000000000001")}, - new Single[] {new Single(1)}, - List.of('s', 't', 'r'), - Set.of(5), - List.of(Long.MAX_VALUE, 6L), - List.of(Double.MAX_VALUE), - List.of(true), - List.of("str"), - List.of(BigDecimal.valueOf(10.1)), - List.of(new ObjectId("000000000000000000000001")), - List.of(new Single(1)))); + new Character[] {'s', null, 't', 'r'}, + new Integer[] {null, 7}, + new Long[] {8L, null}, + new Double[] {9.1d, null}, + new Boolean[] {true, null}, + new String[] {null, "str"}, + new BigDecimal[] {null, BigDecimal.valueOf(10.1)}, + new ObjectId[] {new ObjectId("000000000000000000000001"), null}, + new Single[] {new Single(1), null}, + asList('s', 't', null, 'r'), + new HashSet<>(asList(null, 5)), + asList(Long.MAX_VALUE, null, 6L), + asList(null, Double.MAX_VALUE), + asList(null, true), + asList("str", null), + asList(BigDecimal.valueOf(10.1), null), + asList(null, new ObjectId("000000000000000000000001")), + asList(new Single(1), null))); sessionFactoryScope.inTransaction(session -> session.persist(item)); assertCollectionContainsExactly( """ @@ -278,24 +277,24 @@ void testNestedValueHavingArraysAndCollections() { longs: [{$numberLong: "9223372036854775807"}, {$numberLong: "6"}], doubles: [{$numberDouble: "1.7976931348623157E308"}], booleans: [true], - boxedChars: ["s", "t", "r"], - boxedInts: [7], - boxedLongs: [{$numberLong: "8"}], - boxedDoubles: [{$numberDouble: "9.1"}], - boxedBooleans: [true], - strings: ["str"], - bigDecimals: [{$numberDecimal: "10.1"}], - objectIds: [{$oid: "000000000000000000000001"}], - structAggregateEmbeddables: [{a: 1}], - charsCollection: ["s", "t", "r"], - intsCollection: [5], - longsCollection: [{$numberLong: "9223372036854775807"}, {$numberLong: "6"}], - doublesCollection: [{$numberDouble: "1.7976931348623157E308"}], - booleansCollection: [true], - stringsCollection: ["str"], - bigDecimalsCollection: [{$numberDecimal: "10.1"}], - objectIdsCollection: [{$oid: "000000000000000000000001"}], - structAggregateEmbeddablesCollection: [{a: 1}] + boxedChars: ["s", null, "t", "r"], + boxedInts: [null, 7], + boxedLongs: [{$numberLong: "8"}, null], + boxedDoubles: [{$numberDouble: "9.1"}, null], + boxedBooleans: [true, null], + strings: [null, "str"], + bigDecimals: [null, {$numberDecimal: "10.1"}], + objectIds: [{$oid: "000000000000000000000001"}, null], + structAggregateEmbeddables: [{a: 1}, null], + charsCollection: ["s", "t", null, "r"], + intsCollection: [null, 5], + longsCollection: [{$numberLong: "9223372036854775807"}, null, {$numberLong: "6"}], + doublesCollection: [null, {$numberDouble: "1.7976931348623157E308"}], + booleansCollection: [null, true], + stringsCollection: ["str", null], + bigDecimalsCollection: [{$numberDecimal: "10.1"}, null], + objectIdsCollection: [null, {$oid: "000000000000000000000001"}], + structAggregateEmbeddablesCollection: [{a: 1}, null] } } """); @@ -322,24 +321,24 @@ void testNestedValueHavingArraysAndCollections() { longs: [{$numberLong: "9223372036854775807"}, {$numberLong: "-6"}], doubles: [{$numberDouble: "1.7976931348623157E308"}], booleans: [true], - boxedChars: ["s", "t", "r"], - boxedInts: [7], - boxedLongs: [{$numberLong: "8"}], - boxedDoubles: [{$numberDouble: "9.1"}], - boxedBooleans: [true], - strings: ["str"], - bigDecimals: [{$numberDecimal: "10.1"}], - objectIds: [{$oid: "000000000000000000000002"}], - structAggregateEmbeddables: [{a: 1}], - charsCollection: ["s", "t", "r"], - intsCollection: [5], - longsCollection: [{$numberLong: "9223372036854775807"}, {$numberLong: "-6"}], - doublesCollection: [{$numberDouble: "1.7976931348623157E308"}], - booleansCollection: [true], - stringsCollection: ["str"], - bigDecimalsCollection: [{$numberDecimal: "10.1"}], - objectIdsCollection: [{$oid: "000000000000000000000001"}], - structAggregateEmbeddablesCollection: [{a: 1}] + boxedChars: ["s", null, "t", "r"], + boxedInts: [null, 7], + boxedLongs: [{$numberLong: "8"}, null], + boxedDoubles: [{$numberDouble: "9.1"}, null], + boxedBooleans: [true, null], + strings: [null, "str"], + bigDecimals: [null, {$numberDecimal: "10.1"}], + objectIds: [{$oid: "000000000000000000000002"}, null], + structAggregateEmbeddables: [{a: 1}, null], + charsCollection: ["s", "t", null, "r"], + intsCollection: [null, 5], + longsCollection: [{$numberLong: "9223372036854775807"}, null, {$numberLong: "-6"}], + doublesCollection: [null, {$numberDouble: "1.7976931348623157E308"}], + booleansCollection: [null, true], + stringsCollection: ["str", null], + bigDecimalsCollection: [{$numberDecimal: "10.1"}, null], + objectIdsCollection: [null, {$oid: "000000000000000000000001"}], + structAggregateEmbeddablesCollection: [{a: 1}, null] } } """); @@ -423,7 +422,6 @@ void testNestedValueHavingEmptyArraysAndCollections() { * @see ArrayAndCollectionIntegrationTests#testArrayAndCollectionValuesOfEmptyStructAggregateEmbeddables() */ @Test - @Disabled("TODO-HIBERNATE-48 https://jira.mongodb.org/browse/HIBERNATE-48 enable this test") public void testNestedValueHavingNullArraysAndCollections() { var emptyStructAggregateEmbeddable = new ArraysAndCollections( null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, @@ -473,7 +471,6 @@ public void testNestedValueHavingNullArraysAndCollections() { } @Test - @Disabled("TODO-HIBERNATE-48 https://jira.mongodb.org/browse/HIBERNATE-48 enable this test") void testReadNestedValuesMissingFields() { var insertResult = mongoCollection.insertOne( BsonDocument.parse( diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/select/Book.java b/src/integrationTest/java/com/mongodb/hibernate/query/select/Book.java index 098b145d..1b2fd9b9 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/select/Book.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/select/Book.java @@ -27,13 +27,12 @@ class Book { @Id int id; - // TODO-HIBERNATE-48 dummy values are set for currently null value is not supported - String title = ""; - Boolean outOfStock = false; - Integer publishYear = 0; - Long isbn13 = 0L; - Double discount = 0.0; - BigDecimal price = new BigDecimal("0.0"); + String title; + Boolean outOfStock; + Integer publishYear; + Long isbn13; + Double discount; + BigDecimal price; Book() {} @@ -43,4 +42,9 @@ class Book { this.publishYear = publishYear; this.outOfStock = outOfStock; } + + @Override + public String toString() { + return "Book{" + "id=" + id + '}'; + } } diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/select/SortingSelectQueryIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/select/SortingSelectQueryIntegrationTests.java index 10263698..4d7527b3 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/select/SortingSelectQueryIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/select/SortingSelectQueryIntegrationTests.java @@ -48,8 +48,9 @@ void beforeEach() { new Book(1, "War and Peace", 1869, true), new Book(2, "Crime and Punishment", 1866, false), new Book(3, "Anna Karenina", 1877, false), - new Book(4, "The Brothers Karamazov", 1880, false), - new Book(5, "War and Peace", 2025, false)); + new Book(4, "The Brothers Karamazov", null, false), + new Book(5, "War and Peace", 2025, false), + new Book(6, null, 2000, false)); private static List getBooksByIds(int... ids) { return Arrays.stream(ids) @@ -90,7 +91,7 @@ void testOrderBySingleFieldWithoutTies(String sortDirection) { } """ .formatted(sortDirection.equals("ASC") ? 1 : -1), - sortDirection.equals("ASC") ? getBooksByIds(2, 1, 3, 4, 5) : getBooksByIds(5, 4, 3, 1, 2)); + sortDirection.equals("ASC") ? getBooksByIds(4, 2, 1, 3, 6, 5) : getBooksByIds(5, 6, 3, 1, 2, 4)); } @ParameterizedTest @@ -126,12 +127,12 @@ void testOrderBySingleFieldWithTies(String sortDirection) { sortDirection.equals("ASC") ? resultList -> assertThat(resultList) .satisfiesAnyOf( - list -> assertIterableEq(getBooksByIds(3, 2, 4, 1, 5), list), - list -> assertIterableEq(getBooksByIds(3, 2, 4, 5, 1), list)) + list -> assertIterableEq(getBooksByIds(6, 3, 2, 4, 1, 5), list), + list -> assertIterableEq(getBooksByIds(6, 3, 2, 4, 5, 1), list)) : resultList -> assertThat(resultList) .satisfiesAnyOf( - list -> assertIterableEq(getBooksByIds(1, 5, 4, 2, 3), list), - list -> assertIterableEq(getBooksByIds(5, 1, 4, 2, 3), list))); + list -> assertIterableEq(getBooksByIds(1, 5, 4, 2, 3, 6), list), + list -> assertIterableEq(getBooksByIds(5, 1, 4, 2, 3, 6), list))); } @Test @@ -170,19 +171,19 @@ void testOrderByMultipleFieldsWithoutTies() { } ] }""", - getBooksByIds(3, 2, 4, 5)); + getBooksByIds(6, 3, 2, 4, 5)); } @Test void testOrderByMultipleFieldsWithTies() { assertSelectionQuery( - "from Book ORDER BY title ASC, publishYear DESC, id ASC", + "from Book ORDER BY title ASC", Book.class, - "{ 'aggregate': 'books', 'pipeline': [ { '$sort': { 'title': 1, 'publishYear': -1, '_id': 1 } }, {'$project': {'_id': true, 'discount': true, 'isbn13': true, 'outOfStock': true, 'price': true, 'publishYear': true, 'title': true} } ] }", + "{ 'aggregate': 'books', 'pipeline': [ { '$sort': { 'title': 1 } }, {'$project': {'_id': true, 'discount': true, 'isbn13': true, 'outOfStock': true, 'price': true, 'publishYear': true, 'title': true} } ] }", resultList -> assertThat(resultList) .satisfiesAnyOf( - list -> assertIterableEq(getBooksByIds(3, 2, 4, 1, 5), list), - list -> assertIterableEq(getBooksByIds(3, 2, 4, 5, 1), list))); + list -> assertIterableEq(getBooksByIds(6, 3, 2, 4, 1, 5), list), + list -> assertIterableEq(getBooksByIds(3, 2, 4, 5, 1, 6), list))); } @Test @@ -211,10 +212,11 @@ void testSortFieldByAlias() { """, List.of( new Object[] {"War and Peace", 2025}, - new Object[] {"The Brothers Karamazov", 1880}, + new Object[] {null, 2000}, new Object[] {"Anna Karenina", 1877}, new Object[] {"War and Peace", 1869}, - new Object[] {"Crime and Punishment", 1866})); + new Object[] {"Crime and Punishment", 1866}, + new Object[] {"The Brothers Karamazov", null})); } @Test @@ -241,9 +243,10 @@ void testSortFieldByOrdinalReference() { ] }""", List.of( + new Object[] {null, 2000}, new Object[] {"Anna Karenina", 1877}, new Object[] {"Crime and Punishment", 1866}, - new Object[] {"The Brothers Karamazov", 1880}, + new Object[] {"The Brothers Karamazov", null}, new Object[] {"War and Peace", 2025}, new Object[] {"War and Peace", 1869})); } @@ -331,7 +334,7 @@ void testOrderBySimpleTuple() { ] } """, - getBooksByIds(2, 1, 3, 4, 5)); + getBooksByIds(4, 2, 1, 3, 6, 5)); } @Test @@ -364,7 +367,7 @@ void testOrderByNestedTuple() { ] } """, - getBooksByIds(5, 1, 4, 2, 3)); + getBooksByIds(5, 1, 4, 2, 3, 6)); } } } diff --git a/src/integrationTest/java/com/mongodb/hibernate/type/ObjectIdIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/type/ObjectIdIntegrationTests.java index 2fd47d01..93e7b13b 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/type/ObjectIdIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/type/ObjectIdIntegrationTests.java @@ -32,6 +32,7 @@ import jakarta.persistence.Table; import org.bson.BsonDocument; import org.bson.BsonInt32; +import org.bson.BsonNull; import org.bson.BsonObjectId; import org.bson.types.ObjectId; import org.hibernate.annotations.JavaType; @@ -68,17 +69,14 @@ void insert() { var item = new Item(); item.id = 1; item.v = new ObjectId(1, 0); - // TODO-HIBERNATE-74 https://jira.mongodb.org/browse/HIBERNATE-74, - // TODO-HIBERNATE-48 https://jira.mongodb.org/browse/HIBERNATE-48 Make sure `item.vNull` is set to `null` - // when we implement `MongoPreparedStatement.setNull` properly. - item.vNull = new ObjectId(); + item.vNull = null; item.vExplicitlyAnnotatedNotForThePublic = new ObjectId(2, 3); sessionFactoryScope.inTransaction(session -> session.persist(item)); assertThat(mongoCollection.find()) .containsExactly(new BsonDocument() .append(ID_FIELD_NAME, new BsonInt32(1)) .append("v", new BsonObjectId(item.v)) - .append("vNull", new BsonObjectId(item.vNull)) + .append("vNull", BsonNull.VALUE) .append( "vExplicitlyAnnotatedNotForThePublic", new BsonObjectId(item.vExplicitlyAnnotatedNotForThePublic))); @@ -89,10 +87,7 @@ void findById() { var item = new Item(); item.id = 1; item.v = new ObjectId(2, 0); - // TODO-HIBERNATE-74 https://jira.mongodb.org/browse/HIBERNATE-74, - // TODO-HIBERNATE-48 https://jira.mongodb.org/browse/HIBERNATE-48 Make sure `item.vNull` is set to `null` - // when we implement `MongoPreparedStatement.setNull` properly. - item.vNull = new ObjectId(); + item.vNull = null; item.vExplicitlyAnnotatedNotForThePublic = new ObjectId(3, 4); sessionFactoryScope.inTransaction(session -> session.persist(item)); var loadedItem = sessionFactoryScope.fromTransaction(session -> session.find(Item.class, item.id)); diff --git a/src/main/java/com/mongodb/hibernate/internal/extension/MongoAdditionalMappingContributor.java b/src/main/java/com/mongodb/hibernate/internal/extension/MongoAdditionalMappingContributor.java index 0eb77722..3c217406 100644 --- a/src/main/java/com/mongodb/hibernate/internal/extension/MongoAdditionalMappingContributor.java +++ b/src/main/java/com/mongodb/hibernate/internal/extension/MongoAdditionalMappingContributor.java @@ -26,7 +26,6 @@ import jakarta.persistence.Embeddable; import java.util.Collection; import java.util.Set; -import org.hibernate.annotations.DynamicInsert; import org.hibernate.annotations.Struct; import org.hibernate.boot.ResourceStreamLocator; import org.hibernate.boot.spi.AdditionalMappingContributions; @@ -59,20 +58,12 @@ public void contribute( MetadataBuildingContext buildingContext) { forbidEmbeddablesWithoutPersistentAttributes(metadata); metadata.getEntityBindings().forEach(persistentClass -> { - forbidDynamicInsert(persistentClass); checkColumnNames(persistentClass); forbidStructIdentifier(persistentClass); setIdentifierColumnName(persistentClass); }); } - private static void forbidDynamicInsert(PersistentClass persistentClass) { - if (persistentClass.useDynamicInsert()) { - throw new FeatureNotSupportedException( - format("%s: %s is not supported", persistentClass, DynamicInsert.class.getSimpleName())); - } - } - private static void checkColumnNames(PersistentClass persistentClass) { persistentClass .getTable() diff --git a/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java b/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java index 2a25220a..43b140b4 100644 --- a/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java +++ b/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java @@ -569,7 +569,7 @@ public void visitQueryLiteral(QueryLiteral queryLiteral) { if (literalValue == null) { throw new FeatureNotSupportedException("TODO-HIBERNATE-74 https://jira.mongodb.org/browse/HIBERNATE-74"); } - astVisitorValueHolder.yield(VALUE, new AstLiteralValue(toBsonValue(literalValue))); + astVisitorValueHolder.yield(VALUE, new AstLiteralValue(toLiteralBsonValue(literalValue))); } @Override @@ -589,7 +589,7 @@ public void visitJunction(Junction junction) { @Override public void visitUnparsedNumericLiteral(UnparsedNumericLiteral unparsedNumericLiteral) { var literalValue = assertNotNull(unparsedNumericLiteral.getLiteralValue()); - astVisitorValueHolder.yield(VALUE, new AstLiteralValue(toBsonValue(literalValue))); + astVisitorValueHolder.yield(VALUE, new AstLiteralValue(toLiteralBsonValue(literalValue))); } @Override @@ -945,17 +945,6 @@ static String renderMongoAstNode(AstNode rootAstNode) { } } - static void checkJdbcParameterBindingsSupportability(@Nullable JdbcParameterBindings jdbcParameterBindings) { - if (jdbcParameterBindings != null) { - for (var jdbcParameterBinding : jdbcParameterBindings.getBindings()) { - if (jdbcParameterBinding.getBindValue() == null) { - throw new FeatureNotSupportedException( - "TODO-HIBERNATE-74 https://jira.mongodb.org/browse/HIBERNATE-74"); - } - } - } - } - private static void checkQueryOptionsSupportability(QueryOptions queryOptions) { if (queryOptions.getTimeout() != null) { throw new FeatureNotSupportedException("'timeout' inQueryOptions not supported"); @@ -1031,7 +1020,7 @@ private static boolean isComparingFieldWithValue(ComparisonPredicate comparisonP || (isFieldPathExpression(rhs) && isValueExpression(lhs)); } - private static BsonValue toBsonValue(Object value) { + private static BsonValue toLiteralBsonValue(Object value) { // TODO-HIBERNATE-74 decide if `value` is nullable try { return ValueConversions.toBsonValue(value); diff --git a/src/main/java/com/mongodb/hibernate/internal/translate/SelectMqlTranslator.java b/src/main/java/com/mongodb/hibernate/internal/translate/SelectMqlTranslator.java index 2e6981b6..d35e9c0e 100644 --- a/src/main/java/com/mongodb/hibernate/internal/translate/SelectMqlTranslator.java +++ b/src/main/java/com/mongodb/hibernate/internal/translate/SelectMqlTranslator.java @@ -51,7 +51,6 @@ public JdbcOperationQuerySelect translate( logSqlAst(selectStatement); - checkJdbcParameterBindingsSupportability(jdbcParameterBindings); applyQueryOptions(queryOptions); var result = acceptAndYield((Statement) selectStatement, SELECT_RESULT); diff --git a/src/main/java/com/mongodb/hibernate/internal/type/MongoStructJdbcType.java b/src/main/java/com/mongodb/hibernate/internal/type/MongoStructJdbcType.java index 362403ca..860efc86 100644 --- a/src/main/java/com/mongodb/hibernate/internal/type/MongoStructJdbcType.java +++ b/src/main/java/com/mongodb/hibernate/internal/type/MongoStructJdbcType.java @@ -37,6 +37,7 @@ import java.sql.SQLFeatureNotSupportedException; import java.sql.Struct; import org.bson.BsonDocument; +import org.bson.BsonNull; import org.bson.BsonValue; import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; @@ -105,19 +106,19 @@ public EmbeddableMappingType getEmbeddableMappingType() { * is not called by Hibernate ORM. */ @Override - public BsonValue createJdbcValue(@Nullable Object domainValue, WrapperOptions options) { + public BsonDocument createJdbcValue(@Nullable Object domainValue, WrapperOptions options) { throw fail(); } - private BsonValue createBsonValue(@Nullable Object domainValue, WrapperOptions options) throws SQLException { + private @Nullable BsonDocument createBsonValue(@Nullable Object domainValue, WrapperOptions options) + throws SQLException { if (domainValue == null) { - throw new FeatureNotSupportedException( - "TODO-HIBERNATE-48 https://jira.mongodb.org/browse/HIBERNATE-48 return toBsonValue(domainValue)"); + return null; } var embeddableMappingType = getEmbeddableMappingType(); var result = new BsonDocument(); var jdbcValueCount = embeddableMappingType.getJdbcValueCount(); - for (int columnIndex = 0; columnIndex < jdbcValueCount; columnIndex++) { + for (var columnIndex = 0; columnIndex < jdbcValueCount; columnIndex++) { var jdbcValueSelectable = embeddableMappingType.getJdbcValueSelectable(columnIndex); assertFalse(jdbcValueSelectable.isFormula()); if (!jdbcValueSelectable.isInsertable()) { @@ -130,22 +131,22 @@ private BsonValue createBsonValue(@Nullable Object domainValue, WrapperOptions o } var fieldName = jdbcValueSelectable.getSelectableName(); var value = embeddableMappingType.getValue(domainValue, columnIndex); - if (value == null) { - throw new FeatureNotSupportedException( - "TODO-HIBERNATE-48 https://jira.mongodb.org/browse/HIBERNATE-48"); - } BsonValue bsonValue; - var jdbcMapping = jdbcValueSelectable.getJdbcMapping(); - var jdbcTypeCode = jdbcMapping.getJdbcType().getJdbcTypeCode(); - if (jdbcTypeCode == getJdbcTypeCode()) { - var structValueBinder = assertInstanceOf(jdbcMapping.getJdbcValueBinder(), Binder.class); - bsonValue = structValueBinder.getJdbcType().createBsonValue(value, options); - } else if (jdbcTypeCode == MongoArrayJdbcType.JDBC_TYPE.getVendorTypeNumber()) { - @SuppressWarnings("unchecked") - ValueBinder valueBinder = jdbcMapping.getJdbcValueBinder(); - bsonValue = toBsonValue(valueBinder.getBindValue(value, options)); + if (value == null) { + bsonValue = BsonNull.VALUE; } else { - bsonValue = toBsonValue(value); + var jdbcMapping = jdbcValueSelectable.getJdbcMapping(); + var jdbcTypeCode = jdbcMapping.getJdbcType().getJdbcTypeCode(); + if (jdbcTypeCode == getJdbcTypeCode()) { + var structValueBinder = assertInstanceOf(jdbcMapping.getJdbcValueBinder(), Binder.class); + bsonValue = structValueBinder.getJdbcType().createBsonValue(value, options); + } else if (jdbcTypeCode == MongoArrayJdbcType.JDBC_TYPE.getVendorTypeNumber()) { + @SuppressWarnings("unchecked") + ValueBinder valueBinder = jdbcMapping.getJdbcValueBinder(); + bsonValue = toBsonValue(valueBinder.getBindValue(value, options)); + } else { + bsonValue = toBsonValue(value); + } } result.append(fieldName, bsonValue); } @@ -162,14 +163,13 @@ private BsonValue createBsonValue(@Nullable Object domainValue, WrapperOptions o public Object @Nullable [] extractJdbcValues(@Nullable Object rawJdbcValue, WrapperOptions options) throws SQLException { if (isNull(rawJdbcValue)) { - throw new FeatureNotSupportedException( - "TODO-HIBERNATE-48 https://jira.mongodb.org/browse/HIBERNATE-48 return null"); + return null; } var bsonDocument = assertInstanceOf(rawJdbcValue, BsonDocument.class); var embeddableMappingType = getEmbeddableMappingType(); var jdbcValueCount = embeddableMappingType.getJdbcValueCount(); var result = new Object[jdbcValueCount]; - for (int columnIndex = 0; columnIndex < jdbcValueCount; columnIndex++) { + for (var columnIndex = 0; columnIndex < jdbcValueCount; columnIndex++) { var jdbcValueSelectable = embeddableMappingType.getJdbcValueSelectable(columnIndex); assertFalse(jdbcValueSelectable.isFormula()); var fieldName = jdbcValueSelectable.getSelectableName(); @@ -178,8 +178,7 @@ private BsonValue createBsonValue(@Nullable Object domainValue, WrapperOptions o var jdbcTypeCode = jdbcMapping.getJdbcType().getJdbcTypeCode(); Object domainValue; if (isNull(value)) { - throw new FeatureNotSupportedException( - "TODO-HIBERNATE-48 https://jira.mongodb.org/browse/HIBERNATE-48 domainValue = null"); + domainValue = null; } else if (jdbcTypeCode == getJdbcTypeCode()) { var structValueExtractor = assertInstanceOf(jdbcMapping.getJdbcValueExtractor(), Extractor.class); domainValue = structValueExtractor.getJdbcType().extractJdbcValues(value, options); @@ -223,7 +222,7 @@ public MongoStructJdbcType getJdbcType() { } @Override - public Object getBindValue(@Nullable X value, WrapperOptions options) throws SQLException { + public @Nullable Object getBindValue(@Nullable X value, WrapperOptions options) throws SQLException { return getJdbcType().createBsonValue(value, options); } diff --git a/src/main/java/com/mongodb/hibernate/internal/type/ObjectIdJavaType.java b/src/main/java/com/mongodb/hibernate/internal/type/ObjectIdJavaType.java index 8994c2ca..9b693201 100644 --- a/src/main/java/com/mongodb/hibernate/internal/type/ObjectIdJavaType.java +++ b/src/main/java/com/mongodb/hibernate/internal/type/ObjectIdJavaType.java @@ -58,7 +58,9 @@ public JdbcType getRecommendedJdbcType(JdbcTypeIndicators indicators) { @Override public @Nullable ObjectId wrap(@Nullable X value, WrapperOptions options) { - if (value instanceof ObjectId v) { + if (value == null) { + return null; + } else if (value instanceof ObjectId v) { return v; } else if (value instanceof BsonValue v) { return toObjectIdDomainValue(v); diff --git a/src/main/java/com/mongodb/hibernate/internal/type/ValueConversions.java b/src/main/java/com/mongodb/hibernate/internal/type/ValueConversions.java index 128d09c4..9fed2abd 100644 --- a/src/main/java/com/mongodb/hibernate/internal/type/ValueConversions.java +++ b/src/main/java/com/mongodb/hibernate/internal/type/ValueConversions.java @@ -56,8 +56,7 @@ private ValueConversions() {} public static BsonValue toBsonValue(@Nullable Object value) throws SQLFeatureNotSupportedException { if (value == null) { - throw new SQLFeatureNotSupportedException( - "TODO-HIBERNATE-48 https://jira.mongodb.org/browse/HIBERNATE-48 return BsonNull.VALUE"); + return BsonNull.VALUE; } else if (value instanceof BsonDocument v) { return v; } else if (value instanceof Boolean v) { @@ -85,7 +84,7 @@ public static BsonValue toBsonValue(@Nullable Object value) throws SQLFeatureNot } throw new SQLFeatureNotSupportedException(format( "Value [%s] of type [%s] is not supported", - value, value.getClass().getTypeName())); + value, assertNotNull(value).getClass().getTypeName())); } public static BsonBoolean toBsonValue(boolean value) { @@ -169,10 +168,9 @@ private static BsonArray arrayToBsonValue(Object value) throws SQLFeatureNotSupp return new BsonArray(elements); } - static Object toDomainValue(BsonValue value, Class domainType) throws SQLFeatureNotSupportedException { + static @Nullable Object toDomainValue(BsonValue value, Class domainType) throws SQLFeatureNotSupportedException { if (isNull(value)) { - throw new SQLFeatureNotSupportedException( - "TODO-HIBERNATE-48 https://jira.mongodb.org/browse/HIBERNATE-48 return null"); + return null; } else if (value instanceof BsonDocument v) { return v; } else if (value instanceof BsonBoolean v) { diff --git a/src/main/java/com/mongodb/hibernate/jdbc/MongoPreparedStatement.java b/src/main/java/com/mongodb/hibernate/jdbc/MongoPreparedStatement.java index f693ae17..74fbbabd 100644 --- a/src/main/java/com/mongodb/hibernate/jdbc/MongoPreparedStatement.java +++ b/src/main/java/com/mongodb/hibernate/jdbc/MongoPreparedStatement.java @@ -23,6 +23,7 @@ import com.mongodb.client.ClientSession; import com.mongodb.client.MongoDatabase; +import com.mongodb.hibernate.internal.FeatureNotSupportedException; import com.mongodb.hibernate.internal.type.MongoStructJdbcType; import com.mongodb.hibernate.internal.type.ObjectIdJdbcType; import java.math.BigDecimal; @@ -40,6 +41,7 @@ import java.util.ArrayList; import java.util.Calendar; import java.util.List; +import java.util.Set; import java.util.function.Consumer; import org.bson.BsonArray; import org.bson.BsonDocument; @@ -84,14 +86,46 @@ private void checkAllParametersSet() throws SQLException { throw new SQLException(format("Parameter with index [%d] is not set", i + 1)); } } + checkComparatorNotComparingWithNullValues(); } + /** + * Temporary method to ensure exception is thrown when comparison query operators are comparing with {@code null} + * values. + * + *

Note that only find expression is involved before HIBERNATE-74. TODO-HIBERNATE-74 delete this temporary method + */ + private void checkComparatorNotComparingWithNullValues() { + doCheckComparatorNotComparingWithNullValues(command); + } + + private void doCheckComparatorNotComparingWithNullValues(BsonDocument document) { + for (var entry : document.entrySet()) { + if (COMPARISON_OPERATOR_NAMES_SUPPORTED.contains(entry.getKey()) + && entry.getValue().isNull()) { + throw new FeatureNotSupportedException( + "TODO-HIBERNATE-74 https://jira.mongodb.org/browse/HIBERNATE-74"); + } + if (entry.getValue().isDocument()) { + doCheckComparatorNotComparingWithNullValues(entry.getValue().asDocument()); + } else if (entry.getValue().isArray()) { + for (var bsonValue : entry.getValue().asArray()) { + if (bsonValue.isDocument()) { + doCheckComparatorNotComparingWithNullValues(bsonValue.asDocument()); + } + } + } + } + } + + private static final Set COMPARISON_OPERATOR_NAMES_SUPPORTED = + Set.of("$eq", "$ne", "$gt", "$gte", "$lt", "$lte"); + @Override public void setNull(int parameterIndex, int sqlType) throws SQLException { checkClosed(); checkParameterIndex(parameterIndex); switch (sqlType) { - case Types.ARRAY: case Types.BLOB: case Types.CLOB: case Types.DATALINK: @@ -103,13 +137,10 @@ public void setNull(int parameterIndex, int sqlType) throws SQLException { case Types.REF: case Types.ROWID: case Types.SQLXML: - case Types.STRUCT: throw new SQLFeatureNotSupportedException( "Unsupported SQL type: " + JDBCType.valueOf(sqlType).getName()); } - throw new SQLFeatureNotSupportedException( - "TODO-HIBERNATE-74 https://jira.mongodb.org/browse/HIBERNATE-74, TODO-HIBERNATE-48 https://jira.mongodb.org/browse/HIBERNATE-48" - + " setParameter(parameterIndex, ValueConversions.toBsonValue((Object) null))"); + setParameter(parameterIndex, toBsonValue((Object) null)); } @Override diff --git a/src/main/java/com/mongodb/hibernate/jdbc/MongoResultSet.java b/src/main/java/com/mongodb/hibernate/jdbc/MongoResultSet.java index 12d3f47f..7df76973 100644 --- a/src/main/java/com/mongodb/hibernate/jdbc/MongoResultSet.java +++ b/src/main/java/com/mongodb/hibernate/jdbc/MongoResultSet.java @@ -37,7 +37,6 @@ import static java.lang.String.format; import com.mongodb.client.MongoCursor; -import com.mongodb.hibernate.internal.FeatureNotSupportedException; import com.mongodb.hibernate.internal.type.ValueConversions; import java.math.BigDecimal; import java.sql.Array; @@ -252,14 +251,6 @@ private T getValue(int columnIndex, SqlFunction toJavaConverte try { var key = getKey(columnIndex); var bsonValue = assertNotNull(currentDocument).get(key); - if (bsonValue == null) { - throw new FeatureNotSupportedException( - """ - TODO-HIBERNATE-48 https://jira.mongodb.org/browse/HIBERNATE-48 - simply remove this `null` check, as we support missing fields - and they are equivalent to BSON `Null`, which is handled below - """); - } T value = ValueConversions.isNull(bsonValue) ? null : toJavaConverter.apply(assertNotNull(bsonValue)); lastReadColumnValueWasNull = value == null; return value;