Skip to content
Permalink
Browse files

Support for MongoDB 3.4 big decimals. Fixes grails/grails-data-mappin…

  • Loading branch information
graemerocher committed Dec 14, 2016
1 parent a226006 commit e536ef46bc2a901b4c11e74082378879a2c1b0bb
@@ -9,7 +9,7 @@ addons:
apt:
sources:
- mongodb-upstart
- mongodb-3.0-precise
- mongodb-3.4-precise
packages:
- mongodb-org-server
- mongodb-org-shell
@@ -246,6 +246,22 @@ class SimpleDecoder implements PropertyDecoder<Simple> {

}
}

SIMPLE_TYPE_DECODERS[BigDecimal] = new TypeDecoder() {
@Override
BsonType bsonType() {
BsonType.DECIMAL128
}

@Override
void decode(BsonReader reader, PersistentProperty property, EntityAccess entityAccess) {

entityAccess.setPropertyNoConversion(
property.name,
reader.readDecimal128().bigDecimalValue()
)
}
}
}

@Override
@@ -6,6 +6,7 @@ import org.bson.BsonWriter
import org.bson.codecs.EncoderContext
import org.bson.codecs.configuration.CodecRegistry
import org.bson.types.Binary
import org.bson.types.Decimal128
import org.bson.types.ObjectId
import org.grails.datastore.bson.codecs.PropertyEncoder
import org.grails.datastore.mapping.engine.EntityAccess
@@ -51,8 +52,18 @@ class SimpleEncoder implements PropertyEncoder<Simple> {
SIMPLE_TYPE_ENCODERS[String] = DEFAULT_ENCODER
SIMPLE_TYPE_ENCODERS[StringBuffer] = DEFAULT_ENCODER
SIMPLE_TYPE_ENCODERS[StringBuilder] = DEFAULT_ENCODER
SIMPLE_TYPE_ENCODERS[BigInteger] = DEFAULT_ENCODER
SIMPLE_TYPE_ENCODERS[BigDecimal] = DEFAULT_ENCODER
SIMPLE_TYPE_ENCODERS[BigInteger] = new TypeEncoder() {
@Override
void encode(BsonWriter writer, PersistentProperty property, Object value) {
writer.writeDecimal128( new Decimal128(((BigInteger)value).toBigDecimal()) )
}
}
SIMPLE_TYPE_ENCODERS[BigDecimal] = new TypeEncoder() {
@Override
void encode(BsonWriter writer, PersistentProperty property, Object value) {
writer.writeDecimal128( new Decimal128( (BigDecimal)value ))
}
}
SIMPLE_TYPE_ENCODERS[Byte] = smallNumberEncoder
SIMPLE_TYPE_ENCODERS[byte.class] = smallNumberEncoder
SIMPLE_TYPE_ENCODERS[Integer] = smallNumberEncoder
@@ -21,6 +21,7 @@ import com.mongodb.ReadPreference
import com.mongodb.client.FindIterable
import com.mongodb.client.MongoCollection
import com.mongodb.client.MongoDatabase
import com.mongodb.client.model.FindOneAndDeleteOptions
import grails.mongodb.api.MongoAllOperations
import groovy.transform.CompileStatic
import org.bson.Document
@@ -116,12 +117,37 @@ trait MongoEntity<D> implements GormEntity<D>, DynamicAttributes {
*
* @param filter the query filter
* @return the find iterable interface
* @mongodb.driver.manual tutorial/query-documents/ Find
*/
static FindIterable<D> find(Bson filter) {
currentMongoStaticApi().find(filter)
}

/**
* Atomically find a document and remove it.
*
* @param filter the query filter to find the document with
* @return the document that was removed. If no documents matched the query filter, then null will be returned
*/
static D findOneAndDelete(Bson filter) {
currentMongoStaticApi().findOneAndDelete(filter)
}

/**
* Atomically find a document and remove it.
*
* @param filter the query filter to find the document with
* @return the document that was removed. If no documents matched the query filter, then null will be returned
*/
static D findOneAndDelete(Bson filter, FindOneAndDeleteOptions options) {
currentMongoStaticApi().findOneAndDelete(filter)
}

/**
* Counts the number of the entities in the collection.
*
* @param filter the query filter
* @return the find iterable interface
*/
static Number count(Bson filter) {
currentMongoStaticApi().count(filter)
}
@@ -6,6 +6,7 @@ import com.mongodb.client.AggregateIterable
import com.mongodb.client.FindIterable
import com.mongodb.client.MongoCollection
import com.mongodb.client.MongoDatabase
import com.mongodb.client.model.FindOneAndDeleteOptions
import grails.gorm.api.GormStaticOperations
import org.bson.Document
import org.bson.conversions.Bson
@@ -27,6 +28,22 @@ interface MongoStaticOperations<D> extends GormStaticOperations<D> {
*/
FindIterable<D> find(Bson filter)

/**
* Atomically find a document and remove it.
*
* @param filter the query filter to find the document with
* @return the document that was removed. If no documents matched the query filter, then null will be returned
*/
D findOneAndDelete(Bson filter)

/**
* Atomically find a document and remove it.
*
* @param filter the query filter to find the document with
* @return the document that was removed. If no documents matched the query filter, then null will be returned
*/
D findOneAndDelete(Bson filter, FindOneAndDeleteOptions options)

/**
* @return Custom MongoDB criteria builder
*/
@@ -7,6 +7,7 @@ import com.mongodb.client.FindIterable
import com.mongodb.client.MongoCollection
import com.mongodb.client.MongoDatabase
import com.mongodb.client.model.Filters
import com.mongodb.client.model.FindOneAndDeleteOptions
import com.mongodb.client.model.Projections
import com.mongodb.client.model.TextSearchOptions
import grails.gorm.multitenancy.Tenants
@@ -22,6 +23,7 @@ import org.grails.datastore.mapping.core.Datastore
import org.grails.datastore.mapping.core.Session
import org.grails.datastore.mapping.engine.EntityPersister
import org.grails.datastore.mapping.engine.internal.MappingUtils
import org.grails.datastore.mapping.model.PersistentEntity
import org.grails.datastore.mapping.mongo.AbstractMongoSession
import org.grails.datastore.mapping.mongo.MongoCodecSession
import org.grails.datastore.mapping.mongo.MongoDatastore
@@ -52,6 +54,23 @@ class MongoStaticApi<D> extends GormStaticApi<D> implements MongoAllOperations<D
return findIterable
}
}

@Override
D findOneAndDelete(Bson filter, FindOneAndDeleteOptions options = null) {
withSession { AbstractMongoSession session ->
def entity = session.mappingContext.getPersistentEntity(persistentClass.name)
filter = wrapFilterWithMultiTenancy(filter)
MongoCollection<D> mongoCollection = session.getCollection(entity)
.withDocumentClass(persistentClass)
D result = options ? mongoCollection
.findOneAndDelete(filter, options) :
mongoCollection
.findOneAndDelete(filter)

return result
}
}

Number count(Bson filter) {
withSession { AbstractMongoSession session ->
def entity = session.mappingContext.getPersistentEntity(persistentClass.name)
@@ -69,11 +88,20 @@ class MongoStaticApi<D> extends GormStaticApi<D> implements MongoAllOperations<D
return session.getCollection(entity)
.count(filter)
}
}
}

protected Bson wrapFilterWithMultiTenancy(Bson filter) {
if (multiTenancyMode == MultiTenancySettings.MultiTenancyMode.DISCRIMINATOR && persistentEntity.isMultiTenant()) {
filter = Filters.and(
Filters.eq(MappingUtils.getTargetKey(persistentEntity.tenantId), Tenants.currentId((Class<Datastore>) datastore.getClass())),
filter
)
}
return filter
}
protected <FT> FindIterable<FT> addMultiTenantFilterIfNecessary(FindIterable<FT> findIterable) {
if (multiTenancyMode == MultiTenancySettings.MultiTenancyMode.DISCRIMINATOR) {
if (multiTenancyMode == MultiTenancySettings.MultiTenancyMode.DISCRIMINATOR && persistentEntity.isMultiTenant()) {
return findIterable.filter(
Filters.eq(MappingUtils.getTargetKey(persistentEntity.tenantId), Tenants.currentId((Class<Datastore>) datastore.getClass()))
)
@@ -18,6 +18,8 @@
import groovy.lang.Closure;

import java.beans.PropertyDescriptor;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
import java.util.regex.Pattern;

@@ -62,7 +64,7 @@
/**
* Java types supported as mongo property types.
*/
private static final Set<String> MONGO_NATIVE_TYPES = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
private static final Set<String> MONGO_NATIVE_TYPES = new HashSet<>(Arrays.asList(
Double.class.getName(),
String.class.getName(),
Document.class.getName(),
@@ -85,7 +87,8 @@
UUID.class.getName(),
byte[].class.getName(),
Byte.class.getName()
)));

));

private CodecRegistry codecRegistry;
private Map<Class, Boolean> hasCodecCache = new HashMap<>();
@@ -147,6 +150,10 @@ protected void initialize(ConnectionSourceSettings settings) {
super.initialize(settings);

AbstractMongoConnectionSourceSettings mongoConnectionSourceSettings = (AbstractMongoConnectionSourceSettings) settings;
if(mongoConnectionSourceSettings.isDecimalType()) {
MONGO_NATIVE_TYPES.add(BigDecimal.class.getName());
MONGO_NATIVE_TYPES.add(BigInteger.class.getName());
}
List<Class<? extends Codec>> codecClasses = mongoConnectionSourceSettings.getCodecs();

Iterable<Codec> codecList = ConfigurationUtils.findServices(codecClasses, Codec.class);
@@ -169,6 +176,7 @@ protected void initialize(ConnectionSourceSettings settings) {
private void initialize(Class[] classes) {
registerMongoTypes();
final ConverterRegistry converterRegistry = getConverterRegistry();

converterRegistry.addConverter(new Converter<String, ObjectId>() {
public ObjectId convert(String source) {
if(ObjectId.isValid(source)) {
@@ -60,6 +60,13 @@ abstract class AbstractMongoConnectionSourceSettings extends ConnectionSourceSet
*/
boolean stateless = false

/**
* Whether to use the decimal128 type for BigDecimal values
*
* @see org.bson.types.Decimal128
*/
boolean decimalType = true

/**
* The collection name to use to resolve connections when using {@link MongoConnectionSources}
*/
@@ -581,7 +581,7 @@ protected Document createQueryObject(PersistentEntity persistentEntity) {
public static void populateMongoQuery(final AbstractMongoSession session, Document query, Junction criteria, final PersistentEntity entity) {
EmbeddedQueryEncoder queryEncoder;
if(session instanceof MongoCodecSession) {
final MongoDatastore datastore = (MongoDatastore) session.getDatastore();
final MongoDatastore datastore = session.getDatastore();
final CodecRegistry codecRegistry = datastore.getCodecRegistry();
queryEncoder = new EmbeddedQueryEncoder() {
@Override
@@ -15,6 +15,7 @@ class FindNativeSpec extends GormDatastoreSpec {

void "test native find method"() {
setup:
Product.DB.drop()
new Product(title: "cake").save()
new Product(title: "coffee").save(flush:true)

@@ -37,6 +38,14 @@ class FindNativeSpec extends GormDatastoreSpec {
then:"The results are correct"
findIterable.size() == 1
doc != null

when:"findAndDeleteOne is used"
Product p = Product.findOneAndDelete(eq("title", "coffee"))

then:"The right one was deleted"
Product.count == 1
Product.findByTitle("cake")
p.title == 'coffee'
}


@@ -0,0 +1,36 @@
package org.grails.datastore.gorm.mongo

import grails.gorm.annotation.Entity
import grails.gorm.tests.GormDatastoreSpec
import grails.mongodb.MongoEntity
import groovy.transform.NotYetImplemented
import org.bson.Document
import org.bson.types.Decimal128
import spock.lang.Ignore
import spock.lang.IgnoreIf

/**
* Created by graemerocher on 14/12/16.
*/
class BigDecimalSpec extends GormDatastoreSpec {

void "test save and retrieve big decimal value"() {
when:"A big decimal is saved"
def val = new BigDecimal("1.0")
new BossMan(salary: val).save(flush:true)
session.clear()
BossMan bm = BossMan.first()
then:""
bm.salary == val

}

@Override
List getDomainClasses() {
[BossMan]
}
}
@Entity
class BossMan implements MongoEntity<BossMan> {
BigDecimal salary
}
@@ -44,6 +44,7 @@ class TestSearchSpec extends GormDatastoreSpec{

@Entity
class Product implements MongoEntity<Product>{

ObjectId id
String title

0 comments on commit e536ef4

Please sign in to comment.
You can’t perform that action at this time.