Correct precedence for arithmetic operators (#788)

* Add parentheses for arithmetic operators in sql builders

* Added unit test

* Re-use CustomOperator for arithmetic
This commit is contained in:
Christophe Hesters
2020-02-14 13:11:06 +01:00
committed by GitHub
parent cdbc588494
commit d512d18776
3 changed files with 37 additions and 15 deletions

View File

@@ -218,21 +218,10 @@ class notExists(val query: Query) : Op<Boolean>() {
}
}
class PlusOp<T, S: T>(val expr1: Expression<T>, val expr2: Expression<S>, override val columnType: IColumnType): ExpressionWithColumnType<T>() {
override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder { append(expr1, '+', expr2) }
}
class MinusOp<T, S: T>(val expr1: Expression<T>, val expr2: Expression<S>, override val columnType: IColumnType): ExpressionWithColumnType<T>() {
override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder { append(expr1, '-', expr2) }
}
class TimesOp<T, S: T>(val expr1: Expression<T>, val expr2: Expression<S>, override val columnType: IColumnType): ExpressionWithColumnType<T>() {
override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder { append(expr1, '*', expr2) }
}
class DivideOp<T, S: T>(val expr1: Expression<T>, val expr2: Expression<S>, override val columnType: IColumnType): ExpressionWithColumnType<T>() {
override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder { append('(', expr1, " / ", expr2, ')') }
}
class PlusOp<T, S: T>(expr1: Expression<T>, expr2: Expression<S>, override val columnType: IColumnType): CustomOperator<T>("+", columnType, expr1, expr2)
class MinusOp<T, S: T>(expr1: Expression<T>, expr2: Expression<S>, override val columnType: IColumnType): CustomOperator<T>("-", columnType, expr1, expr2)
class TimesOp<T, S: T>(expr1: Expression<T>, expr2: Expression<S>, override val columnType: IColumnType): CustomOperator<T>("*", columnType, expr1, expr2)
class DivideOp<T, S: T>(expr1: Expression<T>, expr2: Expression<S>, override val columnType: IColumnType): CustomOperator<T>("/", columnType, expr1, expr2)
class ModOp<T:Number?, S: Number?>(val expr1: Expression<T>, val expr2: Expression<S>, override val columnType: IColumnType): ExpressionWithColumnType<T>() {
override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder {

View File

@@ -16,6 +16,10 @@ fun<T:Comparable<T>, S:T?> ExpressionWithColumnType<in S>.min() : ExpressionWit
fun<T:Comparable<T>, S:T?> ExpressionWithColumnType<in S>.max() : ExpressionWithColumnType<T?> = Max<T, S>(this, this.columnType)
/**
* Calculates the average value. Typed to BigDecimal because some DBMS return floating point values for AVG, even if column an integral type
* See examples [here](https://www.w3resource.com/sql/aggregate-functions/avg-function.php)
*/
fun<T:Comparable<T>, S:T?> ExpressionWithColumnType<in S>.avg(scale: Int = 2) : ExpressionWithColumnType<BigDecimal?> = Avg<T, S>(this, scale)
fun<T:Any?> ExpressionWithColumnType<T>.stdDevPop(scale: Int = 2) = StdDevPop(this, scale)

View File

@@ -0,0 +1,29 @@
package org.jetbrains.exposed.sql.tests.shared.dml
import org.jetbrains.exposed.sql.Op
import org.jetbrains.exposed.sql.SqlExpressionBuilder.div
import org.jetbrains.exposed.sql.SqlExpressionBuilder.minus
import org.jetbrains.exposed.sql.SqlExpressionBuilder.times
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.tests.DatabaseTestsBase
import org.jetbrains.exposed.sql.tests.shared.assertEquals
import org.junit.Test
class ArithmeticTests : DatabaseTestsBase() {
@Test
fun `test operator precedence of minus() plus() div() times()`() {
withCitiesAndUsers { _, _, userData ->
val calculatedColumn = ((DMLTestsData.UserData.value - 5) * 2) / 2
userData
.slice(DMLTestsData.UserData.value, calculatedColumn)
.selectAll()
.forEach {
val value = it[DMLTestsData.UserData.value]
val actualResult = it[calculatedColumn]
val expectedResult = ((value - 5) * 2) / 2
assertEquals(expectedResult, actualResult)
}
}
}
}