Skip to content
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
4 changes: 4 additions & 0 deletions api/expression-parser.api
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ public final class org/hisp/dhis/lib/expression/ast/NamedFunction : java/lang/En
public static final field d2_countIfValue Lorg/hisp/dhis/lib/expression/ast/NamedFunction;
public static final field d2_countIfZeroPos Lorg/hisp/dhis/lib/expression/ast/NamedFunction;
public static final field d2_daysBetween Lorg/hisp/dhis/lib/expression/ast/NamedFunction;
public static final field d2_exponent Lorg/hisp/dhis/lib/expression/ast/NamedFunction;
public static final field d2_extractDataMatrixValue Lorg/hisp/dhis/lib/expression/ast/NamedFunction;
public static final field d2_floor Lorg/hisp/dhis/lib/expression/ast/NamedFunction;
public static final field d2_hasUserRole Lorg/hisp/dhis/lib/expression/ast/NamedFunction;
Expand All @@ -122,6 +123,7 @@ public final class org/hisp/dhis/lib/expression/ast/NamedFunction : java/lang/En
public static final field d2_lastEventDate Lorg/hisp/dhis/lib/expression/ast/NamedFunction;
public static final field d2_left Lorg/hisp/dhis/lib/expression/ast/NamedFunction;
public static final field d2_length Lorg/hisp/dhis/lib/expression/ast/NamedFunction;
public static final field d2_log Lorg/hisp/dhis/lib/expression/ast/NamedFunction;
public static final field d2_maxValue Lorg/hisp/dhis/lib/expression/ast/NamedFunction;
public static final field d2_minValue Lorg/hisp/dhis/lib/expression/ast/NamedFunction;
public static final field d2_minutesBetween Lorg/hisp/dhis/lib/expression/ast/NamedFunction;
Expand Down Expand Up @@ -1398,6 +1400,7 @@ public abstract interface class org/hisp/dhis/lib/expression/spi/ExpressionFunct
public fun d2_countIfValue (Lorg/hisp/dhis/lib/expression/spi/VariableValue;Ljava/lang/String;)I
public fun d2_countIfZeroPos (Lorg/hisp/dhis/lib/expression/spi/VariableValue;)I
public fun d2_daysBetween (Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/LocalDate;)I
public fun d2_exponent (Ljava/lang/Number;Ljava/lang/Number;)D
public fun d2_extractDataMatrixValue (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
public fun d2_floor (Ljava/lang/Number;)D
public fun d2_hasUserRole (Ljava/lang/String;Ljava/util/List;)Z
Expand Down Expand Up @@ -1460,6 +1463,7 @@ public final class org/hisp/dhis/lib/expression/spi/ExpressionFunctions$DefaultI
public static fun d2_countIfValue (Lorg/hisp/dhis/lib/expression/spi/ExpressionFunctions;Lorg/hisp/dhis/lib/expression/spi/VariableValue;Ljava/lang/String;)I
public static fun d2_countIfZeroPos (Lorg/hisp/dhis/lib/expression/spi/ExpressionFunctions;Lorg/hisp/dhis/lib/expression/spi/VariableValue;)I
public static fun d2_daysBetween (Lorg/hisp/dhis/lib/expression/spi/ExpressionFunctions;Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/LocalDate;)I
public static fun d2_exponent (Lorg/hisp/dhis/lib/expression/spi/ExpressionFunctions;Ljava/lang/Number;Ljava/lang/Number;)D
public static fun d2_extractDataMatrixValue (Lorg/hisp/dhis/lib/expression/spi/ExpressionFunctions;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
public static fun d2_floor (Lorg/hisp/dhis/lib/expression/spi/ExpressionFunctions;Ljava/lang/Number;)D
public static fun d2_hasUserRole (Lorg/hisp/dhis/lib/expression/spi/ExpressionFunctions;Ljava/lang/String;Ljava/util/List;)Z
Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
plugins {
kotlin("multiplatform")
id("maven-publish-conventions")
Expand All @@ -11,7 +11,7 @@
mavenCentral()
}

version = "1.4.2-SNAPSHOT"
version = "1.4.3-SNAPSHOT"
group = "org.hisp.dhis.lib.expression"

if (project.hasProperty("removeSnapshotSuffix")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ enum class NamedFunction(
d2_countIfValue("d2:countIfValue", ValueType.NUMBER, ValueType.MIXED, ValueType.MIXED),
d2_countIfZeroPos("d2:countIfZeroPos", ValueType.NUMBER, ValueType.NUMBER),
d2_daysBetween("d2:daysBetween", ValueType.NUMBER, ValueType.DATE, ValueType.DATE),
d2_exponent("d2:exponent", ValueType.NUMBER, ValueType.NUMBER),
d2_extractDataMatrixValue("d2:extractDataMatrixValue", ValueType.STRING, ValueType.STRING, ValueType.STRING),
d2_floor("d2:floor", ValueType.NUMBER, ValueType.NUMBER),
d2_hasUserRole("d2:hasUserRole", ValueType.BOOLEAN, ValueType.STRING),
Expand All @@ -64,6 +65,7 @@ enum class NamedFunction(
d2_lastEventDate("d2:lastEventDate", ValueType.DATE, ValueType.STRING),
d2_left("d2:left", ValueType.STRING, ValueType.STRING, ValueType.NUMBER),
d2_length("d2:length", ValueType.NUMBER, ValueType.STRING),
d2_log("d2:log", ValueType.NUMBER, ValueType.NUMBER, ValueType.NUMBER),
d2_maxValue("d2:maxValue", ValueType.NUMBER, ValueType.MIXED),
d2_minutesBetween("d2:minutesBetween", ValueType.NUMBER, ValueType.DATE, ValueType.DATE),
d2_minValue("d2:minValue", ValueType.NUMBER, ValueType.MIXED),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ internal class Calculator(
NamedFunction.isNotNull -> functions.isNotNull(evalToMixed(fn.child(0)))
NamedFunction.isNull -> functions.isNull(evalToMixed(fn.child(0)))
NamedFunction.least -> functions.least(evalToNumbers(fn.children()))
NamedFunction.log -> if (fn.size() == 1) functions.log(evalToNumber(fn.child(0)))
NamedFunction.log, NamedFunction.d2_log -> if (fn.size() == 1) functions.log(evalToNumber(fn.child(0)))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to add d2:log when log already exists?

@superskip superskip Jun 8, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason is that all other program rule functions use the d2:-prefix.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this discussed to keep it consistent? Maybe @enricocolasante knows?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say so.
At the moment rule-engine is only allowing CommonD2Functions and RuleEngineD2Functions and all of them have the d2 prefix.
There wasn't a real discussion about it. We already have some generic functions about numeric operations like modulus or floor using the d2 prefix and as always this argument is the one used to continue doing this way.

I think at some point we should separate generic functions and custom DHIS2 functions, the question is: should we do it now?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe not the best time to do another refactor of the language(s) on a more fundamental level so if enrico is happy with this PR I think we try to move it forward

else functions.log(evalToNumber(fn.child(0))) / functions.log(evalToNumber(fn.child(1)))
NamedFunction.log10 -> functions.log10(evalToNumber(fn.child(0)))
NamedFunction.removeZeros -> functions.removeZeros(evalToNumber(fn.child(0)))
Expand Down Expand Up @@ -124,6 +124,9 @@ internal class Calculator(
evalToDate(fn.child(0)),
evalToDate(fn.child(1)))
NamedFunction.d2_oizp -> functions.d2_oizp(evalToNumber(fn.child(0)))
NamedFunction.d2_exponent -> functions.d2_exponent(
evalToNumber(fn.child(0)),
evalToNumber(fn.child(1)))
NamedFunction.d2_right -> functions.d2_right(
evalToString(fn.child(0)),
evalToInteger(fn.child(1)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import org.hisp.dhis.lib.expression.math.ZScore
import kotlin.math.ceil
import kotlin.math.floor
import kotlin.math.ln
import kotlin.math.pow

/**
* Implementation API for all expression languages functions.
Expand Down Expand Up @@ -208,6 +209,10 @@ fun interface ExpressionFunctions {
return start.daysUntil(end)
}

fun d2_exponent(base: Number?, exponent: Number?): Double {
return (base ?: 1.0).toDouble().pow((exponent ?: 0).toDouble())
}

fun d2_extractDataMatrixValue(gs1Key: String?, value: String?): String? {
return fromKey(gs1Key!!).format(value)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,9 @@ object ExpressionGrammar {
fn(NamedFunction.d2_count, dataItem),
fn(NamedFunction.d2_countIfValue, dataItem, expr),
fn(NamedFunction.d2_daysBetween, expr, expr),
fn(NamedFunction.d2_exponent, expr, expr),
fn(NamedFunction.d2_hasValue, dataItem),
fn(NamedFunction.d2_log, expr, expr.maybe()),
fn(NamedFunction.d2_maxValue, dataItem),
fn(NamedFunction.d2_minutesBetween, expr, expr),
fn(NamedFunction.d2_minValue, dataItem),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.hisp.dhis.lib.expression.function

import org.hisp.dhis.lib.expression.Expression
import org.hisp.dhis.lib.expression.ExpressionMode
import kotlin.test.Test
import kotlin.test.assertEquals

/**
* Tests the `d2:exponent` function
*
* @author Tony Valle
*/
internal class ExponentTest {

@Test
fun testExponent() {
assertEquals(1.0, evaluate("d2:exponent(49, 0)"))
assertEquals(7.0, evaluate("d2:exponent(49, 0.5)"))
assertEquals(49.0, evaluate("d2:exponent(49, 1)"))
Comment thread
superskip marked this conversation as resolved.
assertEquals(64.0, evaluate("d2:exponent(32, 1.2)") as Double, errorMargin)
assertEquals(25.0, evaluate("d2:exponent(0.2, -2)") as Double, errorMargin)
}

@Test
fun testExponent_Whitespace() {
assertEquals(2.0, evaluate("d2:exponent(2,1)"))
assertEquals(2.0, evaluate("d2:exponent( 2, 1)"))
assertEquals(2.0, evaluate("d2:exponent(2 , 1 )"))
assertEquals(2.0, evaluate("d2:exponent( 2 , 1 )"))
}

private val errorMargin: Double = 0.00000001

private fun evaluate(expression: String): Any? {
return Expression(expression, ExpressionMode.RULE_ENGINE_ACTION).evaluate()
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.hisp.dhis.lib.expression.function

import org.hisp.dhis.lib.expression.Expression
import org.hisp.dhis.lib.expression.ExpressionMode
import kotlin.test.Test
import kotlin.test.assertEquals

Expand All @@ -25,6 +26,11 @@ internal class LogTest {
assertEquals(Double.POSITIVE_INFINITY, evaluate("log( 2 , 1 )"))
}

@Test
fun testD2Log() {
assertEquals(3.0, Expression("d2:log(8, 2)", ExpressionMode.RULE_ENGINE_ACTION).evaluate())
}

private fun evaluate(expression: String): Any? {
return Expression(expression).evaluate()
}
Expand Down
Loading