Skip to content

Commit

Permalink
Correct precedence for arithmetic operators (#788)
Browse files Browse the repository at this point in the history
* Add parentheses for arithmetic operators in sql builders

* Added unit test

* Re-use CustomOperator for arithmetic
  • Loading branch information
toefel18 committed Feb 14, 2020
1 parent cdbc588 commit d512d18
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 15 deletions.
19 changes: 4 additions & 15 deletions exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Op.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
}

0 comments on commit d512d18

Please sign in to comment.