Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for creating Time Series collection. #3732

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>3.3.0-SNAPSHOT</version>
<version>3.3.0-GH-3731-SNAPSHOT</version>
<packaging>pom</packaging>

<name>Spring Data MongoDB</name>
Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb-benchmarks/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>3.3.0-SNAPSHOT</version>
<version>3.3.0-GH-3731-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb-distribution/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>3.3.0-SNAPSHOT</version>
<version>3.3.0-GH-3731-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>3.3.0-SNAPSHOT</version>
<version>3.3.0-GH-3731-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@

import java.util.Optional;

import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.query.Collation;
import org.springframework.data.mongodb.core.schema.MongoJsonSchema;
import org.springframework.data.mongodb.core.timeseries.Granularities;
import org.springframework.data.mongodb.core.timeseries.Granularity;
import org.springframework.data.mongodb.core.validation.Validator;
import org.springframework.data.util.Optionals;
import org.springframework.lang.Nullable;
Expand All @@ -42,6 +45,7 @@ public class CollectionOptions {
private @Nullable Boolean capped;
private @Nullable Collation collation;
private ValidationOptions validationOptions;
private @Nullable TimeSeriesOptions timeSeriesOptions;

/**
* Constructs a new <code>CollectionOptions</code> instance.
Expand All @@ -54,17 +58,19 @@ public class CollectionOptions {
*/
@Deprecated
public CollectionOptions(@Nullable Long size, @Nullable Long maxDocuments, @Nullable Boolean capped) {
this(size, maxDocuments, capped, null, ValidationOptions.none());
this(size, maxDocuments, capped, null, ValidationOptions.none(), null);
}

private CollectionOptions(@Nullable Long size, @Nullable Long maxDocuments, @Nullable Boolean capped,
@Nullable Collation collation, ValidationOptions validationOptions) {
@Nullable Collation collation, ValidationOptions validationOptions,
@Nullable TimeSeriesOptions timeSeriesOptions) {

this.maxDocuments = maxDocuments;
this.size = size;
this.capped = capped;
this.collation = collation;
this.validationOptions = validationOptions;
this.timeSeriesOptions = timeSeriesOptions;
}

/**
Expand All @@ -78,7 +84,7 @@ public static CollectionOptions just(Collation collation) {

Assert.notNull(collation, "Collation must not be null!");

return new CollectionOptions(null, null, null, collation, ValidationOptions.none());
return new CollectionOptions(null, null, null, collation, ValidationOptions.none(), null);
}

/**
Expand All @@ -88,7 +94,21 @@ public static CollectionOptions just(Collation collation) {
* @since 2.0
*/
public static CollectionOptions empty() {
return new CollectionOptions(null, null, null, null, ValidationOptions.none());
return new CollectionOptions(null, null, null, null, ValidationOptions.none(), null);
}

/**
* Quick way to set up {@link CollectionOptions} for a Time Series collection. For more advanced settings use
* {@link #timeSeries(TimeSeriesOptions)}.
*
* @param timeField The name of the property which contains the date in each time series document. Must not be
* {@literal null}.
* @return new instance of {@link CollectionOptions}.
* @see #timeSeries(TimeSeriesOptions)
* @since 3.3
*/
public static CollectionOptions timeSeries(String timeField) {
return empty().timeSeries(TimeSeriesOptions.timeSeries(timeField));
}

/**
Expand All @@ -99,7 +119,7 @@ public static CollectionOptions empty() {
* @since 2.0
*/
public CollectionOptions capped() {
return new CollectionOptions(size, maxDocuments, true, collation, validationOptions);
return new CollectionOptions(size, maxDocuments, true, collation, validationOptions, null);
}

/**
Expand All @@ -110,7 +130,7 @@ public CollectionOptions capped() {
* @since 2.0
*/
public CollectionOptions maxDocuments(long maxDocuments) {
return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions);
return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions, timeSeriesOptions);
}

/**
Expand All @@ -121,7 +141,7 @@ public CollectionOptions maxDocuments(long maxDocuments) {
* @since 2.0
*/
public CollectionOptions size(long size) {
return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions);
return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions, timeSeriesOptions);
}

/**
Expand All @@ -132,7 +152,7 @@ public CollectionOptions size(long size) {
* @since 2.0
*/
public CollectionOptions collation(@Nullable Collation collation) {
return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions);
return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions, timeSeriesOptions);
}

/**
Expand Down Expand Up @@ -252,7 +272,20 @@ public CollectionOptions schemaValidationAction(ValidationAction validationActio
public CollectionOptions validation(ValidationOptions validationOptions) {

Assert.notNull(validationOptions, "ValidationOptions must not be null!");
return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions);
return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions, timeSeriesOptions);
}

/**
* Create new {@link CollectionOptions} with the given {@link TimeSeriesOptions}.
*
* @param timeSeriesOptions must not be {@literal null}.
* @return new instance of {@link CollectionOptions}.
* @since 3.3
*/
public CollectionOptions timeSeries(TimeSeriesOptions timeSeriesOptions) {

Assert.notNull(timeSeriesOptions, "TimeSeriesOptions must not be null!");
return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions, timeSeriesOptions);
}

/**
Expand Down Expand Up @@ -303,6 +336,16 @@ public Optional<ValidationOptions> getValidationOptions() {
return validationOptions.isEmpty() ? Optional.empty() : Optional.of(validationOptions);
}

/**
* Get the {@link TimeSeriesOptions} if available.
*
* @return {@link Optional#empty()} if not specified.
* @since 3.3
*/
public Optional<TimeSeriesOptions> getTimeSeriesOptions() {
return Optional.ofNullable(timeSeriesOptions);
}

/**
* Encapsulation of ValidationOptions options.
*
Expand Down Expand Up @@ -398,4 +441,87 @@ boolean isEmpty() {
return !Optionals.isAnyPresent(getValidator(), getValidationAction(), getValidationLevel());
}
}

/**
* Options applicable to Time Series collections.
*
* @author Christoph Strobl
* @since 3.3
* @see <a href=
* "https://docs.mongodb.com/manual/core/timeseries-collections">https://docs.mongodb.com/manual/core/timeseries-collections</a>
*/
public static class TimeSeriesOptions {

private final String timeField;

@Nullable //
private String metaField;

private Granularity granularity;

private TimeSeriesOptions(String timeField, @Nullable String metaField, Granularity granularity) {

this.timeField = timeField;
this.metaField = metaField;
this.granularity = granularity;
}

/**
* Create a new instance of {@link TimeSeriesOptions} using the given field as its {@literal timeField}. The one,
* that contains the date in each time series document. <br />
* {@link Field#name() Annotated fieldnames} will be considered during the mapping process.
*
* @param timeField must not be {@literal null}.
* @return new instance of {@link TimeSeriesOptions}.
*/
public static TimeSeriesOptions timeSeries(String timeField) {
return new TimeSeriesOptions(timeField, null, Granularities.DEFAULT);
}

/**
* Set the name of the field which contains metadata in each time series document. Should not be the {@literal id}
* nor {@link TimeSeriesOptions#timeSeries(String)} timeField} nor point to an {@literal array} or
* {@link java.util.Collection}. <br />
* {@link Field#name() Annotated fieldnames} will be considered during the mapping process.
*
* @param metaField must not be {@literal null}.
* @return new instance of {@link TimeSeriesOptions}.
*/
public TimeSeriesOptions metaField(String metaField) {
return new TimeSeriesOptions(timeField, metaField, granularity);
}

/**
* Select the {@link Granularity} parameter to define how data in the time series collection is organized. Select
* one that is closest to the time span between incoming measurements.
*
* @return new instance of {@link TimeSeriesOptions}.
*/
public TimeSeriesOptions granularity(Granularity granularity) {
return new TimeSeriesOptions(timeField, metaField, granularity);
}

/**
* @return never {@literal null}.
*/
public String getTimeField() {
return timeField;
}

/**
* @return can be {@literal null}. Might be an {@literal empty} {@link String} as well, so maybe check via
* {@link org.springframework.util.StringUtils#hasText(String)}.
*/
@Nullable
public String getMetaField() {
return metaField;
}

/**
* @return never {@literal null}.
*/
public Granularity getGranularity() {
return granularity;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,23 @@
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
import org.springframework.data.mongodb.core.CollectionOptions.TimeSeriesOptions;
import org.springframework.data.mongodb.core.convert.MongoWriter;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.core.mapping.MongoSimpleTypes;
import org.springframework.data.mongodb.core.mapping.TimeSeries;
import org.springframework.data.mongodb.core.query.Collation;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.timeseries.Granularities;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

/**
* Common operations performed on an entity in the context of it's mapping metadata.
Expand Down Expand Up @@ -778,6 +782,24 @@ interface TypedOperations<T> {
* @return
*/
Optional<Collation> getCollation(Query query);

/**
* Derive the applicable {@link CollectionOptions} for the given type.
*
* @return never {@literal null}.
* @since 3.3
*/
CollectionOptions getCollectionOptions();

/**
* Map the fields of a given {@link TimeSeriesOptions} against the target domain type to consider potentially
* annotated field names.
*
* @param options must not be {@literal null}.
* @return never {@literal null}.
* @since 3.3
*/
TimeSeriesOptions mapTimeSeriesOptions(TimeSeriesOptions options);
}

/**
Expand Down Expand Up @@ -817,6 +839,16 @@ public Optional<Collation> getCollation(Query query) {

return query.getCollation();
}

@Override
public CollectionOptions getCollectionOptions() {
return CollectionOptions.empty();
}

@Override
public TimeSeriesOptions mapTimeSeriesOptions(TimeSeriesOptions options) {
return options;
}
}

/**
Expand Down Expand Up @@ -854,6 +886,46 @@ public Optional<Collation> getCollation(Query query) {

return Optional.ofNullable(entity.getCollation());
}

@Override
public CollectionOptions getCollectionOptions() {

CollectionOptions collectionOptions = CollectionOptions.empty();
if (entity.hasCollation()) {
collectionOptions = collectionOptions.collation(entity.getCollation());
}

if (entity.isAnnotationPresent(TimeSeries.class)) {

TimeSeries timeSeries = entity.getRequiredAnnotation(TimeSeries.class);
TimeSeriesOptions options = TimeSeriesOptions.timeSeries(timeSeries.timeField());
if (StringUtils.hasText(timeSeries.metaField())) {
options = options.metaField(timeSeries.metaField());
}
if (!Granularities.DEFAULT.equals(timeSeries.granularity())) {
options = options.granularity(timeSeries.granularity());
}
collectionOptions = collectionOptions.timeSeries(options);
}

return collectionOptions;
}

@Override
public TimeSeriesOptions mapTimeSeriesOptions(TimeSeriesOptions source) {

TimeSeriesOptions target = TimeSeriesOptions.timeSeries(mappedNameOrDefault(source.getTimeField()));

if (StringUtils.hasText(source.getMetaField())) {
target = target.metaField(mappedNameOrDefault(source.getMetaField()));
}
return target.granularity(source.getGranularity());
}

private String mappedNameOrDefault(String name) {
MongoPersistentProperty persistentProperty = entity.getPersistentProperty(name);
return persistentProperty != null ? persistentProperty.getFieldName() : name;
}
}

}
Loading