diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8b17b1a04..76adcc951 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,10 +4,14 @@
- Nitrite now supports JPMS. It is now modular and can be used in Java 9 or above.
- Version upgrade for several dependencies
+- Repository type validation can be disabled in `NitriteBuilder` as a fix for #966
### Issue Fixes
- Fix for #935
+- Fix for #948
+- Fix for #961
+- Fix for #966
## Release 4.2.2 - Mar 5, 2024
diff --git a/nitrite/src/main/java/org/dizitart/no2/NitriteBuilder.java b/nitrite/src/main/java/org/dizitart/no2/NitriteBuilder.java
index 84625e245..35373d9de 100644
--- a/nitrite/src/main/java/org/dizitart/no2/NitriteBuilder.java
+++ b/nitrite/src/main/java/org/dizitart/no2/NitriteBuilder.java
@@ -31,8 +31,8 @@
* @see Nitrite
* @since 1.0
*/
+@Getter
public class NitriteBuilder {
- @Getter
/**
* The Nitrite configuration object.
*/
@@ -61,6 +61,27 @@ public NitriteBuilder fieldSeparator(String separator) {
return this;
}
+ /**
+ * Disables the repository type validation for the Nitrite database.
+ *
+ * Repository type validation is a feature in Nitrite that ensures the type of the objects
+ * stored in the repository can be converted to and from {@link org.dizitart.no2.collection.Document}.
+ *
+ * By default, the repository type validation is enabled. If you disable it, and if you try to
+ * store an object that cannot be converted to a {@link org.dizitart.no2.collection.Document},
+ * then Nitrite will throw an exception during the operation.
+ *
+ * @return the NitriteBuilder instance with repository type validation disabled
+ * @see org.dizitart.no2.collection.Document
+ * @see org.dizitart.no2.repository.ObjectRepository
+ * @see org.dizitart.no2.common.mapper.EntityConverter
+ * @since 4.3.0
+ */
+ public NitriteBuilder disableRepositoryTypeValidation() {
+ this.nitriteConfig.disableRepositoryTypeValidation();
+ return this;
+ }
+
/**
* Registers an {@link EntityConverter} with the Nitrite database.
* An {@link EntityConverter} is used to convert between an entity and a
diff --git a/nitrite/src/main/java/org/dizitart/no2/NitriteConfig.java b/nitrite/src/main/java/org/dizitart/no2/NitriteConfig.java
index ae8f3ac40..0c2d043b9 100644
--- a/nitrite/src/main/java/org/dizitart/no2/NitriteConfig.java
+++ b/nitrite/src/main/java/org/dizitart/no2/NitriteConfig.java
@@ -79,6 +79,12 @@ public class NitriteConfig implements AutoCloseable {
*/
private Integer schemaVersion = Constants.INITIAL_SCHEMA_VERSION;
+ @Getter
+ /**
+ * Indicates if repository type validation is disabled.
+ */
+ private boolean repositoryTypeValidationDisabled = false;
+
/**
* Instantiates a new {@link NitriteConfig}.
*/
@@ -103,6 +109,20 @@ public void fieldSeparator(String separator) {
NitriteConfig.fieldSeparator = separator;
}
+ /**
+ * Disables repository type validation.
+ *
+ * @throws InvalidOperationException if the repository type validation is attempted to be
+ * changed after database initialization.
+ */
+ public void disableRepositoryTypeValidation() {
+ if (configured) {
+ throw new InvalidOperationException("Cannot change repository type validation after database" +
+ " initialization");
+ }
+ this.repositoryTypeValidationDisabled = true;
+ }
+
/**
* Registers an {@link EntityConverter} with the Nitrite database.
*
@@ -157,7 +177,7 @@ public NitriteConfig addMigration(Migration migration) {
TreeMap targetMap = migrations.computeIfAbsent(start, k -> new TreeMap<>());
Migration existing = targetMap.get(end);
if (existing != null) {
- log.warn("Overriding migration " + existing + " with " + migration);
+ log.warn("Overriding migration {} with {}", existing, migration);
}
targetMap.put(end, migration);
}
diff --git a/nitrite/src/main/java/org/dizitart/no2/common/util/ValidationUtils.java b/nitrite/src/main/java/org/dizitart/no2/common/util/ValidationUtils.java
index 6adfcb860..2c664eb53 100644
--- a/nitrite/src/main/java/org/dizitart/no2/common/util/ValidationUtils.java
+++ b/nitrite/src/main/java/org/dizitart/no2/common/util/ValidationUtils.java
@@ -16,6 +16,7 @@
package org.dizitart.no2.common.util;
+import org.dizitart.no2.NitriteConfig;
import org.dizitart.no2.collection.Document;
import org.dizitart.no2.common.mapper.NitriteMapper;
import org.dizitart.no2.exceptions.IndexingException;
@@ -149,7 +150,7 @@ public static void validateProjectionType(Class> type, NitriteMapper nitriteMa
}
}
- public static void validateRepositoryType(Class> type, NitriteMapper nitriteMapper) {
+ public static void validateRepositoryType(Class> type, NitriteConfig nitriteConfig) {
Object value;
try {
if (type.isInterface() || (Modifier.isAbstract(type.getModifiers()) && !isBuiltInValueType(type))) {
@@ -157,14 +158,17 @@ public static void validateRepositoryType(Class> type, NitriteMapper nitriteMa
return;
}
- value = newInstance(type, false, nitriteMapper);
- if (value == null) {
- throw new ValidationException("Cannot create new instance of type " + type);
- }
-
- Document document = (Document) nitriteMapper.tryConvert(value, Document.class);
- if (document == null || document.size() == 0) {
- throw new ValidationException("Cannot convert to document from type " + type);
+ if (!nitriteConfig.isRepositoryTypeValidationDisabled()) {
+ NitriteMapper nitriteMapper = nitriteConfig.nitriteMapper();
+ value = newInstance(type, false, nitriteMapper);
+ if (value == null) {
+ throw new ValidationException("Cannot create new instance of type " + type);
+ }
+
+ Document document = (Document) nitriteMapper.tryConvert(value, Document.class);
+ if (document == null || document.size() == 0) {
+ throw new ValidationException("Cannot convert to document from type " + type);
+ }
}
} catch (Exception e) {
throw new ValidationException("Invalid repository type", e);
diff --git a/nitrite/src/main/java/org/dizitart/no2/repository/RepositoryFactory.java b/nitrite/src/main/java/org/dizitart/no2/repository/RepositoryFactory.java
index 06a7c302f..2d2cf72f5 100644
--- a/nitrite/src/main/java/org/dizitart/no2/repository/RepositoryFactory.java
+++ b/nitrite/src/main/java/org/dizitart/no2/repository/RepositoryFactory.java
@@ -19,7 +19,6 @@
import org.dizitart.no2.NitriteConfig;
import org.dizitart.no2.collection.CollectionFactory;
import org.dizitart.no2.collection.NitriteCollection;
-import org.dizitart.no2.common.mapper.NitriteMapper;
import org.dizitart.no2.common.util.StringUtils;
import org.dizitart.no2.exceptions.NitriteIOException;
import org.dizitart.no2.exceptions.ValidationException;
@@ -133,10 +132,9 @@ public void clear() {
private ObjectRepository createRepository(NitriteConfig nitriteConfig, Class type,
String collectionName, String key) {
- NitriteMapper nitriteMapper = nitriteConfig.nitriteMapper();
NitriteStore> store = nitriteConfig.getNitriteStore();
- validateRepositoryType(type, nitriteMapper);
+ validateRepositoryType(type, nitriteConfig);
if (store.getCollectionNames().contains(collectionName)) {
throw new ValidationException("A collection with same entity name already exists");
@@ -154,14 +152,13 @@ private ObjectRepository createRepository(NitriteConfig nitriteConfig, Cl
private ObjectRepository createRepositoryByDecorator(NitriteConfig nitriteConfig,
EntityDecorator entityDecorator,
String collectionName, String key) {
- NitriteMapper nitriteMapper = nitriteConfig.nitriteMapper();
NitriteStore> store = nitriteConfig.getNitriteStore();
if (store.getCollectionNames().contains(collectionName)) {
throw new ValidationException("A collection with same entity name already exists");
}
- validateRepositoryType(entityDecorator.getEntityType(), nitriteMapper);
+ validateRepositoryType(entityDecorator.getEntityType(), nitriteConfig);
NitriteCollection nitriteCollection = collectionFactory.getCollection(collectionName,
nitriteConfig, false);
diff --git a/nitrite/src/test/java/org/dizitart/no2/common/util/ValidationUtilsTest.java b/nitrite/src/test/java/org/dizitart/no2/common/util/ValidationUtilsTest.java
index 5a8e0b17d..5cc36012e 100644
--- a/nitrite/src/test/java/org/dizitart/no2/common/util/ValidationUtilsTest.java
+++ b/nitrite/src/test/java/org/dizitart/no2/common/util/ValidationUtilsTest.java
@@ -16,6 +16,7 @@
package org.dizitart.no2.common.util;
+import org.dizitart.no2.NitriteConfig;
import org.dizitart.no2.common.mapper.SimpleNitriteMapper;
import org.dizitart.no2.exceptions.ValidationException;
import org.dizitart.no2.integration.Retry;
@@ -96,18 +97,19 @@ public void testValidateProjectionType() {
@Test
public void testValidateRepositoryType() {
- SimpleNitriteMapper documentMapper = new SimpleNitriteMapper();
- documentMapper.registerEntityConverter(new ClassA.ClassAConverter());
- documentMapper.registerEntityConverter(new ClassBConverter());
- documentMapper.registerEntityConverter(new EmptyClass.Converter());
+ NitriteConfig nitriteConfig = new NitriteConfig();
+ nitriteConfig.registerEntityConverter(new ClassA.ClassAConverter());
+ nitriteConfig.registerEntityConverter(new ClassBConverter());
+ nitriteConfig.registerEntityConverter(new EmptyClass.Converter());
+ nitriteConfig.autoConfigure();
- validateRepositoryType(ClassA.class, documentMapper);
+ validateRepositoryType(ClassA.class, nitriteConfig);
- assertThrows(ValidationException.class, () -> validateRepositoryType(EmptyClass.class, documentMapper));
- assertThrows(ValidationException.class, () -> validateRepositoryType(ClassC.class, documentMapper));
- assertThrows(ValidationException.class, () -> validateRepositoryType(String.class, documentMapper));
- assertThrows(ValidationException.class, () -> validateRepositoryType(Number.class, documentMapper));
- assertThrows(ValidationException.class, () -> validateRepositoryType(Integer.class, documentMapper));
- assertThrows(ValidationException.class, () -> validateRepositoryType(Object.class, documentMapper));
+ assertThrows(ValidationException.class, () -> validateRepositoryType(EmptyClass.class, nitriteConfig));
+ assertThrows(ValidationException.class, () -> validateRepositoryType(ClassC.class, nitriteConfig));
+ assertThrows(ValidationException.class, () -> validateRepositoryType(String.class, nitriteConfig));
+ assertThrows(ValidationException.class, () -> validateRepositoryType(Number.class, nitriteConfig));
+ assertThrows(ValidationException.class, () -> validateRepositoryType(Integer.class, nitriteConfig));
+ assertThrows(ValidationException.class, () -> validateRepositoryType(Object.class, nitriteConfig));
}
}
diff --git a/pom.xml b/pom.xml
index 3a0fed525..bcf2cc4d6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -62,6 +62,7 @@
2.16.1
1.9.24
1.6.3
+ 0.6.0-RC.2
1.18.32
1.18.20.0
4.13.2
@@ -291,6 +292,12 @@
${kotlin.version}
test
+
+ org.jetbrains.kotlinx
+ kotlinx-datetime-jvm
+ ${kotlinx-datetime.version}
+ test
+
diff --git a/potassium-nitrite/pom.xml b/potassium-nitrite/pom.xml
index f8468cfad..cea167c10 100644
--- a/potassium-nitrite/pom.xml
+++ b/potassium-nitrite/pom.xml
@@ -123,6 +123,11 @@
log4j-core
test
+
+ org.jetbrains.kotlinx
+ kotlinx-datetime-jvm
+ test
+
diff --git a/potassium-nitrite/src/main/kotlin/org/dizitart/kno2/Builder.kt b/potassium-nitrite/src/main/kotlin/org/dizitart/kno2/Builder.kt
index eb14b6a4d..21b8cd418 100644
--- a/potassium-nitrite/src/main/kotlin/org/dizitart/kno2/Builder.kt
+++ b/potassium-nitrite/src/main/kotlin/org/dizitart/kno2/Builder.kt
@@ -49,6 +49,23 @@ class Builder internal constructor() {
*/
var fieldSeparator: String = NitriteConfig.getFieldSeparator()
+ /**
+ * Enables/disables the repository type validation for the Nitrite database.
+ *
+ * Repository type validation is a feature in Nitrite that ensures the type of the objects
+ * stored in the repository can be converted to and from [org.dizitart.no2.collection.Document].
+ *
+ * By default, the repository type validation is enabled. If you disable it, and if you try to
+ * store an object that cannot be converted to a [org.dizitart.no2.collection.Document],
+ * then Nitrite will throw an exception during the operation.
+ *
+ * @see org.dizitart.no2.collection.Document
+ * @see org.dizitart.no2.repository.ObjectRepository
+ * @see org.dizitart.no2.common.mapper.EntityConverter
+ * @since 4.3.0
+ */
+ var enableRepositoryValidation = true
+
/**
* Loads a [NitriteModule] into the Nitrite database. The module can be used to extend the
* functionality of Nitrite.
@@ -88,6 +105,10 @@ class Builder internal constructor() {
builder.schemaVersion(schemaVersion)
}
+ if (!enableRepositoryValidation) {
+ builder.disableRepositoryTypeValidation()
+ }
+
if (entityConverters.isNotEmpty()) {
entityConverters.forEach { builder.registerEntityConverter(it) }
}
diff --git a/potassium-nitrite/src/test/kotlin/org/dizitart/kno2/KotlinXSerializationMapperTest.kt b/potassium-nitrite/src/test/kotlin/org/dizitart/kno2/KotlinXSerializationMapperTest.kt
index 57c8b3678..b098fbe9d 100644
--- a/potassium-nitrite/src/test/kotlin/org/dizitart/kno2/KotlinXSerializationMapperTest.kt
+++ b/potassium-nitrite/src/test/kotlin/org/dizitart/kno2/KotlinXSerializationMapperTest.kt
@@ -23,6 +23,7 @@ import org.dizitart.kno2.filters.eq
import org.dizitart.kno2.serialization.KotlinXSerializationMapper
import org.dizitart.no2.collection.Document
import org.dizitart.no2.common.module.NitriteModule.module
+import org.dizitart.no2.exceptions.ValidationException
import org.dizitart.no2.mvstore.MVStoreModule
import org.junit.Test
import org.slf4j.LoggerFactory
@@ -180,4 +181,51 @@ class KotlinXSerializationMapperTest {
testData.someArray.forEachIndexed { index, s -> assertEquals(decodedObject.someArray[index], s) }
assertEquals(testData, decodedObject.copy(someArray = testData.someArray))
}
-}
\ No newline at end of file
+
+ @Test(expected = ValidationException::class)
+ fun testRepositoryValidationEnabled() {
+ val db = nitrite {
+ loadModule(MVStoreModule(dbPath))
+ loadModule(module(KotlinXSerializationMapper()))
+ }
+
+ val repo = db.getRepository()
+ repo.insert(CacheEntry("sha256", kotlinx.datetime.Clock.System.now()))
+ repo.find(CacheEntry::sha256 eq "sha256").firstOrNull().also {
+ assertEquals(it?.sha256, "sha256")
+ }
+ db.close()
+ try {
+ Files.delete(Paths.get(dbPath))
+ } catch (e: Exception) {
+ log.error("Failed to delete db file", e)
+ }
+ }
+
+ @Test
+ fun testRepositoryValidationDisabled() {
+ val db = nitrite {
+ enableRepositoryValidation = false
+ loadModule(MVStoreModule(dbPath))
+ loadModule(module(KotlinXSerializationMapper()))
+ }
+
+ val repo = db.getRepository()
+ repo.insert(CacheEntry("sha256", kotlinx.datetime.Clock.System.now()))
+ repo.find(CacheEntry::sha256 eq "sha256").firstOrNull().also {
+ assertEquals(it?.sha256, "sha256")
+ }
+ db.close()
+ try {
+ Files.delete(Paths.get(dbPath))
+ } catch (e: Exception) {
+ log.error("Failed to delete db file", e)
+ }
+ }
+}
+
+@Serializable
+data class CacheEntry(
+ val sha256: String,
+ val lastUpdated: kotlinx.datetime.Instant
+)
\ No newline at end of file