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

implement Arithmetic expression #92

Merged
merged 7 commits into from
Oct 14, 2023
Merged
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
89 changes: 84 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,86 @@ dependencies {
}
```

## Why do you need this library?

### Type-safe DSL for aggregation operations

Spring Data MongoDB provides a convenient way to create [aggregation operations](https://www.mongodb.com/docs/manual/aggregation/).
With this library, you can use aggregation operations in a more concise and type-safe way.

For example, if you want to use the `$subtract` operator in the `$project` stage,
you can write the following code using Spring Data MongoDB.

```java
Aggregation.newAggregation(
Aggregation.project()
.andInclude("item")
.and(ArithmeticOperators.Subtract.valueOf("date").subtract(5 * 60 * 1000))
.`as`("dateDifference"),
)
```

you can write the same code as follows.

```kotlin
aggregation {
project {
+Sales::item
"dateDifference" expression {
subtract {
of(Sales::date) - (5 * 60 * 1000)
}
}
}
}
```

### DSL for Atlas Search

Spring Data MongoDB does not provide a convenient way to use [Atlas Search](https://docs.atlas.mongodb.com/atlas-search).
In [official documentation](https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#mongo.aggregation.supported-aggregation-operations),
it suggests using `stage` method and passing the aggregation pipeline as a string.

```java
Aggregation.stage("""
{ $search : {
"near": {
"path": "released",
"origin": { "$date": { "$numberLong": "..." } } ,
"pivot": 7
}
}
}
""");
```

This approach has the following disadvantages.

- It is not type-safe.
- It is not easy to read and maintain.
- It is easy to make a mistake in the query.

However, if you use this library, you can use Atlas Search in a type-safe way.

```kotlin
aggregation {
search {
near {
path(Movies::released)
origin(LocalDateTime.now())
pivot(7)
}
}
}
```

### KDoc for all DSL functions and properties

This library provides KDoc for all DSL functions and properties.
You can easily find out what each function and property does by hovering over it in your IDE.

![kdoc.png](image/kdoc.png)

## Examples

This project provides `example` module that contains examples of how to use the library.
Expand Down Expand Up @@ -107,18 +187,17 @@ There are two types of tests: one that requires a MongoDB Community instance and

- MongoDB Community

The repositories that are not under the `com.github.inflab.example.spring.data.mongodb.repository.atlas` package requires a MongoDB Community instance.
The repositories that are **not under** the `com.github.inflab.example.spring.data.mongodb.repository.atlas` package requires a MongoDB Community instance.
To run the MongoDB Community instance, we use [Testcontainers](https://www.testcontainers.org/).
Therefore, you need to install and run Docker on your machine.

- MongoDB Atlas

The repositories that are under the `com.github.inflab.example.spring.data.mongodb.repository.atlas` package requires a MongoDB Atlas instance.
You need to create a MongoDB Atlas instance and set the connection information in the `application.yml` file.
You need to [create a MongoDB Atlas instance](https://www.mongodb.com/docs/atlas/getting-started) and set the connection information in the `application.yml` file.

> [!NOTE]
> you can copy a sample `application.yml` file
> from [application.sample.yml](example/spring-data-mongodb/src/test/resources/application.sample.yml)
> you can create the `application.yml` file by copying `example/spring-data-mongodb/src/test/resources/application.sample.yml` file.

```yaml
spring:
Expand All @@ -132,7 +211,7 @@ spring:
You should refer to [the following manual](https://www.mongodb.com/docs/atlas/sample-data/) to configure sample data as well.
Because most example codes are provided based on the sample data.
If test codes are using `Atlas Search`, you also need to create a suitable search index.
Please refer to each example documentation for more information.
Please refer to each documentation of the example code for more information.

## Contributors

Expand Down
2 changes: 1 addition & 1 deletion config/detekt/detekt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ complexity:
ignoreDataClasses: true
ignoreAnnotatedParameter: []
MethodOverloading:
active: true
active: false
threshold: 6
NamedArguments:
active: true
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package com.github.inflab.spring.data.mongodb.core.aggregation.expression

import com.github.inflab.spring.data.mongodb.core.aggregation.expression.arithmetic.AddExpressionDsl
import com.github.inflab.spring.data.mongodb.core.aggregation.expression.arithmetic.DivideExpressionDsl
import com.github.inflab.spring.data.mongodb.core.aggregation.expression.arithmetic.SubtractExpressionDsl
import com.github.inflab.spring.data.mongodb.core.annotation.AggregationMarker
import com.github.inflab.spring.data.mongodb.core.extension.toDotPath
import org.springframework.data.mongodb.core.aggregation.AggregationExpression
import org.springframework.data.mongodb.core.aggregation.ArithmeticOperators
import kotlin.reflect.KProperty

/**
* A Kotlin DSL to configure [AggregationExpression] using idiomatic Kotlin code.
Expand All @@ -23,6 +27,15 @@ class AggregationExpressionDsl {
*/
fun abs(field: String): AggregationExpression = ArithmeticOperators.Abs.absoluteValueOf(field)

/**
* Returns the absolute value of a number.
*
* @param property The property of the field.
* @see <a href="https://www.mongodb.com/docs/manual/reference/operator/aggregation/abs/#mongodb-expression-exp.-abs">$abs</a>
*/
fun abs(property: KProperty<Number?>): AggregationExpression =
ArithmeticOperators.Abs.absoluteValueOf(property.toDotPath())

/**
* Returns the absolute value of a number.
*
Expand Down Expand Up @@ -50,4 +63,96 @@ class AggregationExpressionDsl {
*/
fun add(configuration: AddExpressionDsl.() -> AddExpressionDsl.Operands) =
AddExpressionDsl().build(configuration)

/**
* Returns the smallest integer greater than or equal to the specified number.
*
* @param field The name of the field.
* @see <a href="https://www.mongodb.com/docs/manual/reference/operator/aggregation/ceil/#mongodb-expression-exp.-ceil">$ceil</a>
*/
fun ceil(field: String): AggregationExpression = ArithmeticOperators.Ceil.ceilValueOf(field)

/**
* Returns the smallest integer greater than or equal to the specified number.
*
* @param property The property of the field.
* @see <a href="https://www.mongodb.com/docs/manual/reference/operator/aggregation/ceil/#mongodb-expression-exp.-ceil">$ceil</a>
*/
fun ceil(property: KProperty<Number?>): AggregationExpression =
ArithmeticOperators.Ceil.ceilValueOf(property.toDotPath())

/**
* Returns the smallest integer greater than or equal to the specified number.
*
* @param value The number.
* @see <a href="https://www.mongodb.com/docs/manual/reference/operator/aggregation/ceil/#mongodb-expression-exp.-ceil">$ceil</a>
*/
fun ceil(value: Number): AggregationExpression = ArithmeticOperators.Ceil.ceilValueOf(value)

/**
* Returns the smallest integer greater than or equal to the specified number.
*
* @param configuration The configuration block for the [AggregationExpressionDsl].
* @see <a href="https://www.mongodb.com/docs/manual/reference/operator/aggregation/ceil/#mongodb-expression-exp.-ceil">$ceil</a>
*/
fun ceil(configuration: AggregationExpressionDsl.() -> AggregationExpression): AggregationExpression =
ArithmeticOperators.Ceil.ceilValueOf(AggregationExpressionDsl().configuration())

/**
* Returns the result of dividing the first number by the second.
* Accepts two argument expressions.
*
* @param configuration The configuration block for the [DivideExpressionDsl].
* @see <a href="https://www.mongodb.com/docs/manual/reference/operator/aggregation/divide/#mongodb-expression-exp.-divide">$divide</a>
*/
fun divide(configuration: DivideExpressionDsl.() -> AggregationExpression): AggregationExpression =
DivideExpressionDsl().configuration()

/**
* Raises Euler's number (i.e. `e`) to the specified exponent and returns the result.
*
* @param field The name of the field.
* @see <a href="https://www.mongodb.com/docs/manual/reference/operator/aggregation/exp/#-exp--aggregation-">$exp</a>
*/
fun exp(field: String): AggregationExpression = ArithmeticOperators.Exp.expValueOf(field)

/**
* Raises Euler's number (i.e. `e`) to the specified exponent and returns the result.
*
* @param property The property of the field.
* @see <a href="https://www.mongodb.com/docs/manual/reference/operator/aggregation/exp/#-exp--aggregation-">$exp</a>
*/
fun exp(property: KProperty<Number?>): AggregationExpression =
ArithmeticOperators.Exp.expValueOf(property.toDotPath())

/**
* Raises Euler's number (i.e. `e`) to the specified exponent and returns the result.
*
* @param value The number.
* @see <a href="https://www.mongodb.com/docs/manual/reference/operator/aggregation/exp/#-exp--aggregation-">$exp</a>
*/
fun exp(value: Number): AggregationExpression = ArithmeticOperators.Exp.expValueOf(value)

/**
* Raises Euler's number (i.e. `e`) to the specified exponent and returns the result.
*
* @param configuration The configuration block for the [AggregationExpressionDsl].
* @see <a href="https://www.mongodb.com/docs/manual/reference/operator/aggregation/exp/#-exp--aggregation-">$exp</a>
*/
fun exp(configuration: AggregationExpressionDsl.() -> AggregationExpression): AggregationExpression =
ArithmeticOperators.Exp.expValueOf(AggregationExpressionDsl().configuration())

/**
* Returns the result of subtracting the second value from the first.
* If the two values are numbers, return the difference.
* If the two values are dates, return the difference in milliseconds.
* If the two values are a date and a number in milliseconds, return the resulting date.
* Accepts two argument expressions.
* If the two values are a date and a number, specify the date argument first as it is not meaningful to subtract a date from a number.
*
* @param configuration The configuration block for the [SubtractExpressionDsl].
* @see <a href="https://www.mongodb.com/docs/manual/reference/operator/aggregation/subtract/#mongodb-expression-exp.-subtract">$subtract</a>
*/
fun subtract(configuration: SubtractExpressionDsl.() -> AggregationExpression): AggregationExpression =
SubtractExpressionDsl().configuration()
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,19 @@ class AddExpressionDsl {
fun of(field: String) = Operands(mutableListOf("$$field"))

/**
* Creates a [Operands] with the given [field].
* Creates a [Operands] with the given [property].
*
* @param field The name of the field.
* @param property The name of the field.
*/
fun of(property: KProperty<Number?>) = of(property.toDotPath())

/**
* Creates a [Operands] with the given [property].
*
* @param property The name of the field.
*/
fun of(field: KProperty<*>) = of(field.toDotPath())
@JvmName("ofTemporal")
fun of(property: KProperty<Temporal?>) = of(property.toDotPath())

/**
* Creates a [Operands] with the given [AggregationExpression].
Expand Down Expand Up @@ -92,11 +100,19 @@ class AddExpressionDsl {
}

/**
* Adds [field] to the list of operands.
* Adds [property] to the list of operands.
*
* @param field The name of the field.
* @param property The name of the field.
*/
infix fun Operands.and(property: KProperty<Number?>): Operands = and(property.toDotPath())

/**
* Adds [property] to the list of operands.
*
* @param property The name of the field.
*/
infix fun Operands.and(field: KProperty<*>): Operands = and(field.toDotPath())
@JvmName("andTemporal")
infix fun Operands.and(property: KProperty<Temporal?>): Operands = and(property.toDotPath())

/**
* Adds [AggregationExpression] to the list of operands.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package com.github.inflab.spring.data.mongodb.core.aggregation.expression.arithmetic

import com.github.inflab.spring.data.mongodb.core.aggregation.expression.AggregationExpressionDsl
import com.github.inflab.spring.data.mongodb.core.annotation.AggregationMarker
import com.github.inflab.spring.data.mongodb.core.extension.toDotPath
import org.springframework.data.mongodb.core.aggregation.AggregationExpression
import org.springframework.data.mongodb.core.aggregation.ArithmeticOperators.Divide
import kotlin.reflect.KProperty

/**
* A Kotlin DSL to configure `$divide` [AggregationExpression] using idiomatic Kotlin code.
*
* @author Jake Son
* @since 1.0
* @see <a href="https://docs.mongodb.com/manual/reference/operator/aggregation/divide">$divide</a>
*/
@AggregationMarker
class DivideExpressionDsl {
/**
* Represents a dividend inside `$divide`.
*
* @property value The dividend.
*/
@JvmInline
value class Dividend(val value: Divide)

/**
* Creates a new [Dividend].
*
* @param value The number.
*/
fun of(value: Number) = Dividend(Divide.valueOf(value))

/**
* Creates a new [Dividend].
*
* @param field The name of the field.
*/
fun of(field: String) = Dividend(Divide.valueOf(field))

/**
* Creates a new [Dividend].
*
* @param property The property of the field.
*/
fun of(property: KProperty<Number?>) = of(property.toDotPath())

/**
* Creates a new [Dividend].
*
* @param configuration The configuration block for the [AggregationExpressionDsl].
*/
fun of(configuration: AggregationExpressionDsl.() -> AggregationExpression) =
Dividend(Divide.valueOf(AggregationExpressionDsl().configuration()))

/**
* Divide by the given [value].
*
* @param value The number.
*/
infix fun Dividend.by(value: Number): Divide = this.value.divideBy(value)

/**
* Divide by the value of the given [field].
*
* @param field The name of the field.
*/
infix fun Dividend.by(field: String): Divide = this.value.divideBy(field)

/**
* Divide by the value of the given [property].
*
* @param property The property of the field.
*/
infix fun Dividend.by(property: KProperty<Number?>): Divide = this.value.divideBy(property.toDotPath())

/**
* Divide by the result of the given [AggregationExpression].
*
* @param configuration The configuration block for the [AggregationExpressionDsl].
*/
infix fun Dividend.by(configuration: AggregationExpressionDsl.() -> AggregationExpression): Divide =
this.value.divideBy(AggregationExpressionDsl().configuration())
}
Loading