From fcf9961603ee727e3e603c91e712a3d3a3d76113 Mon Sep 17 00:00:00 2001 From: Sebastian Heupts Date: Mon, 23 Mar 2026 10:17:35 +0100 Subject: [PATCH 01/16] Shade ANTLR in vtl-parser and vtl-engine --- pom.xml | 1 + .../fr/insee/vtl/engine/VtlScriptEngine.java | 8 ++-- .../vtl/engine/VtlSyntaxPreprocessor.java | 2 +- .../insee/vtl/engine/utils/TypeChecking.java | 2 +- .../vtl/engine/utils/dag/DAGStatement.java | 8 ++-- .../vtl/engine/visitors/AnalyticsVisitor.java | 4 +- .../engine/visitors/AssignmentVisitor.java | 2 +- .../vtl/engine/visitors/ClauseVisitor.java | 4 +- .../engine/visitors/DAGBuildingVisitor.java | 8 ++-- .../expression/ComparisonVisitor.java | 4 +- .../functions/GenericFunctionsVisitor.java | 4 +- .../functions/SetFunctionsVisitor.java | 2 +- .../functions/TimeFunctionsVisitor.java | 2 +- vtl-engine/src/main/java/module-info.java | 2 +- .../utils/dag/DagDefineStatementsTest.java | 12 +++--- vtl-parser/pom.xml | 43 +++++++++++++++++++ vtl-parser/src/main/java/module-info.java | 8 ---- 17 files changed, 76 insertions(+), 40 deletions(-) delete mode 100644 vtl-parser/src/main/java/module-info.java diff --git a/pom.xml b/pom.xml index 62c6505ca..47e7f3ef8 100644 --- a/pom.xml +++ b/pom.xml @@ -12,6 +12,7 @@ vtl-engine vtl-jackson vtl-spark + vtl-spark4 vtl-jdbc vtl-sdmx vtl-csv diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/VtlScriptEngine.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/VtlScriptEngine.java index 051db6f39..f3ea9c1bf 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/VtlScriptEngine.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/VtlScriptEngine.java @@ -2,6 +2,10 @@ import static fr.insee.vtl.engine.VtlNativeMethods.NATIVE_METHODS; +import fr.insee.trevas.antlr.shaded.v4.runtime.*; +import fr.insee.trevas.antlr.shaded.v4.runtime.misc.Interval; +import fr.insee.trevas.antlr.shaded.v4.runtime.tree.ParseTree; +import fr.insee.trevas.antlr.shaded.v4.runtime.tree.TerminalNode; import fr.insee.vtl.engine.exceptions.VtlRuntimeException; import fr.insee.vtl.engine.exceptions.VtlSyntaxException; import fr.insee.vtl.engine.visitors.AssignmentVisitor; @@ -19,10 +23,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import javax.script.*; -import org.antlr.v4.runtime.*; -import org.antlr.v4.runtime.misc.Interval; -import org.antlr.v4.runtime.tree.ParseTree; -import org.antlr.v4.runtime.tree.TerminalNode; /** * The {@link ScriptEngine} implementation for VTL. diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/VtlSyntaxPreprocessor.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/VtlSyntaxPreprocessor.java index b24b6518b..a691c8d08 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/VtlSyntaxPreprocessor.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/VtlSyntaxPreprocessor.java @@ -1,5 +1,6 @@ package fr.insee.vtl.engine; +import fr.insee.trevas.antlr.shaded.v4.runtime.ParserRuleContext; import fr.insee.vtl.engine.utils.dag.DAGBuilder; import fr.insee.vtl.engine.utils.dag.DAGStatement; import fr.insee.vtl.engine.visitors.DAGBuildingVisitor; @@ -9,7 +10,6 @@ import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.antlr.v4.runtime.ParserRuleContext; /** * Class for preprocessing the VTL script for resolving script errors and reordering statements diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/utils/TypeChecking.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/utils/TypeChecking.java index ad202a42b..033cbce84 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/utils/TypeChecking.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/utils/TypeChecking.java @@ -2,6 +2,7 @@ import static fr.insee.vtl.engine.VtlScriptEngine.fromContext; +import fr.insee.trevas.antlr.shaded.v4.runtime.tree.ParseTree; import fr.insee.vtl.engine.exceptions.VtlRuntimeException; import fr.insee.vtl.model.Dataset; import fr.insee.vtl.model.ResolvableExpression; @@ -12,7 +13,6 @@ import java.util.Objects; import java.util.Set; import java.util.stream.Stream; -import org.antlr.v4.runtime.tree.ParseTree; import org.threeten.extra.Interval; import org.threeten.extra.PeriodDuration; diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/utils/dag/DAGStatement.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/utils/dag/DAGStatement.java index 55a2fab3d..6fd256a69 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/utils/dag/DAGStatement.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/utils/dag/DAGStatement.java @@ -1,5 +1,9 @@ package fr.insee.vtl.engine.utils.dag; +import fr.insee.trevas.antlr.shaded.v4.runtime.ParserRuleContext; +import fr.insee.trevas.antlr.shaded.v4.runtime.tree.ParseTree; +import fr.insee.trevas.antlr.shaded.v4.runtime.tree.RuleNode; +import fr.insee.trevas.antlr.shaded.v4.runtime.tree.TerminalNode; import fr.insee.vtl.engine.VtlScriptEngine; import fr.insee.vtl.model.Positioned; import fr.insee.vtl.model.exceptions.VtlMultiStatementScriptException; @@ -9,10 +13,6 @@ import java.util.HashSet; import java.util.Set; import java.util.stream.Collectors; -import org.antlr.v4.runtime.ParserRuleContext; -import org.antlr.v4.runtime.tree.ParseTree; -import org.antlr.v4.runtime.tree.RuleNode; -import org.antlr.v4.runtime.tree.TerminalNode; /** * Representation of a VTL Statement diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/AnalyticsVisitor.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/AnalyticsVisitor.java index 2062da040..0cd33de19 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/AnalyticsVisitor.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/AnalyticsVisitor.java @@ -2,6 +2,8 @@ import static fr.insee.vtl.engine.VtlScriptEngine.fromContext; +import fr.insee.trevas.antlr.shaded.v4.runtime.Token; +import fr.insee.trevas.antlr.shaded.v4.runtime.tree.ParseTree; import fr.insee.vtl.engine.exceptions.InvalidArgumentException; import fr.insee.vtl.engine.exceptions.VtlRuntimeException; import fr.insee.vtl.model.Analytics; @@ -14,8 +16,6 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; -import org.antlr.v4.runtime.Token; -import org.antlr.v4.runtime.tree.ParseTree; public class AnalyticsVisitor extends VtlBaseVisitor { diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/AssignmentVisitor.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/AssignmentVisitor.java index c55ca3bbb..ac2be2c4c 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/AssignmentVisitor.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/AssignmentVisitor.java @@ -2,6 +2,7 @@ import static fr.insee.vtl.engine.VtlScriptEngine.fromContext; +import fr.insee.trevas.antlr.shaded.v4.runtime.tree.TerminalNode; import fr.insee.vtl.engine.VtlScriptEngine; import fr.insee.vtl.engine.exceptions.InvalidArgumentException; import fr.insee.vtl.engine.exceptions.VtlRuntimeException; @@ -18,7 +19,6 @@ import javax.script.ScriptContext; import javax.script.ScriptException; import javax.script.SimpleBindings; -import org.antlr.v4.runtime.tree.TerminalNode; /** AssignmentVisitor is the visitor for VTL assignment expressions. */ public class AssignmentVisitor extends VtlBaseVisitor { diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/ClauseVisitor.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/ClauseVisitor.java index cdda2e14f..5654b216a 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/ClauseVisitor.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/ClauseVisitor.java @@ -5,6 +5,8 @@ import static fr.insee.vtl.engine.utils.TypeChecking.assertBasicScalarType; import static fr.insee.vtl.engine.utils.TypeChecking.assertNumber; +import fr.insee.trevas.antlr.shaded.v4.runtime.ParserRuleContext; +import fr.insee.trevas.antlr.shaded.v4.runtime.misc.Interval; import fr.insee.vtl.engine.VtlScriptEngine; import fr.insee.vtl.engine.exceptions.AlreadyDefinedException; import fr.insee.vtl.engine.exceptions.InvalidArgumentException; @@ -18,8 +20,6 @@ import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; -import org.antlr.v4.runtime.ParserRuleContext; -import org.antlr.v4.runtime.misc.Interval; /** * ClauseVisitor is the visitor for VTL clause expressions (component filter, aggr, diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/DAGBuildingVisitor.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/DAGBuildingVisitor.java index 068153a95..7472a31eb 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/DAGBuildingVisitor.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/DAGBuildingVisitor.java @@ -1,5 +1,9 @@ package fr.insee.vtl.engine.visitors; +import fr.insee.trevas.antlr.shaded.v4.runtime.RuleContext; +import fr.insee.trevas.antlr.shaded.v4.runtime.Token; +import fr.insee.trevas.antlr.shaded.v4.runtime.tree.RuleNode; +import fr.insee.trevas.antlr.shaded.v4.runtime.tree.TerminalNode; import fr.insee.vtl.engine.utils.dag.DAGStatement; import fr.insee.vtl.parser.VtlBaseVisitor; import fr.insee.vtl.parser.VtlParser; @@ -9,10 +13,6 @@ import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.antlr.v4.runtime.RuleContext; -import org.antlr.v4.runtime.Token; -import org.antlr.v4.runtime.tree.RuleNode; -import org.antlr.v4.runtime.tree.TerminalNode; /** DagbuildingVisitor is the visitor for creating a DAG from VTL statements. */ public class DAGBuildingVisitor extends VtlBaseVisitor> { diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/ComparisonVisitor.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/ComparisonVisitor.java index d493fd301..fd217e705 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/ComparisonVisitor.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/ComparisonVisitor.java @@ -2,6 +2,8 @@ import static fr.insee.vtl.engine.VtlScriptEngine.fromContext; +import fr.insee.trevas.antlr.shaded.v4.runtime.Token; +import fr.insee.trevas.antlr.shaded.v4.runtime.tree.TerminalNode; import fr.insee.vtl.engine.exceptions.ConflictingTypesException; import fr.insee.vtl.engine.exceptions.VtlRuntimeException; import fr.insee.vtl.engine.utils.TypeChecking; @@ -15,8 +17,6 @@ import fr.insee.vtl.parser.VtlParser; import java.util.*; import java.util.stream.Collectors; -import org.antlr.v4.runtime.Token; -import org.antlr.v4.runtime.tree.TerminalNode; /** * ComparisonVisitor is the base visitor for comparison, 'element of' and list diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/functions/GenericFunctionsVisitor.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/functions/GenericFunctionsVisitor.java index 5d3d7cac8..ba4876988 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/functions/GenericFunctionsVisitor.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/functions/GenericFunctionsVisitor.java @@ -2,6 +2,8 @@ import static fr.insee.vtl.engine.VtlScriptEngine.fromContext; +import fr.insee.trevas.antlr.shaded.v4.runtime.Token; +import fr.insee.trevas.antlr.shaded.v4.runtime.tree.TerminalNode; import fr.insee.vtl.engine.VtlScriptEngine; import fr.insee.vtl.engine.exceptions.FunctionNotFoundException; import fr.insee.vtl.engine.exceptions.InvalidArgumentException; @@ -29,8 +31,6 @@ import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.antlr.v4.runtime.Token; -import org.antlr.v4.runtime.tree.TerminalNode; import org.threeten.extra.Interval; import org.threeten.extra.PeriodDuration; diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/functions/SetFunctionsVisitor.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/functions/SetFunctionsVisitor.java index 3daa0e2e3..ee85baa22 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/functions/SetFunctionsVisitor.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/functions/SetFunctionsVisitor.java @@ -3,6 +3,7 @@ import static fr.insee.vtl.engine.VtlScriptEngine.fromContext; import static fr.insee.vtl.engine.utils.TypeChecking.assertTypeExpression; +import fr.insee.trevas.antlr.shaded.v4.runtime.RuleContext; import fr.insee.vtl.engine.exceptions.InvalidArgumentException; import fr.insee.vtl.engine.exceptions.VtlRuntimeException; import fr.insee.vtl.engine.visitors.expression.ExpressionVisitor; @@ -17,7 +18,6 @@ import java.util.List; import java.util.Objects; import java.util.stream.Collectors; -import org.antlr.v4.runtime.RuleContext; /** * SetFunctionsVisitor is the visitor for expressions involving set functions (i.e. diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/functions/TimeFunctionsVisitor.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/functions/TimeFunctionsVisitor.java index c56de1f66..e10d006cf 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/functions/TimeFunctionsVisitor.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/functions/TimeFunctionsVisitor.java @@ -2,6 +2,7 @@ import static fr.insee.vtl.engine.VtlScriptEngine.fromContext; +import fr.insee.trevas.antlr.shaded.v4.runtime.tree.ParseTree; import fr.insee.vtl.engine.exceptions.InvalidArgumentException; import fr.insee.vtl.engine.exceptions.VtlRuntimeException; import fr.insee.vtl.engine.expressions.ComponentExpression; @@ -18,7 +19,6 @@ import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.antlr.v4.runtime.tree.ParseTree; import org.threeten.extra.Interval; /** diff --git a/vtl-engine/src/main/java/module-info.java b/vtl-engine/src/main/java/module-info.java index 3032248ea..a99486236 100644 --- a/vtl-engine/src/main/java/module-info.java +++ b/vtl-engine/src/main/java/module-info.java @@ -11,7 +11,7 @@ exports fr.insee.vtl.engine.exceptions; requires transitive java.scripting; - requires transitive fr.insee.vtl.parser; + requires transitive fr.insee.vtl.parser.shaded; requires transitive fr.insee.vtl.model; uses ProcessingEngine; diff --git a/vtl-engine/src/test/java/fr/insee/vtl/engine/utils/dag/DagDefineStatementsTest.java b/vtl-engine/src/test/java/fr/insee/vtl/engine/utils/dag/DagDefineStatementsTest.java index 8881fa042..d8c96d5ab 100644 --- a/vtl-engine/src/test/java/fr/insee/vtl/engine/utils/dag/DagDefineStatementsTest.java +++ b/vtl-engine/src/test/java/fr/insee/vtl/engine/utils/dag/DagDefineStatementsTest.java @@ -2,6 +2,12 @@ import static org.junit.jupiter.api.Assertions.assertEquals; +import fr.insee.trevas.antlr.shaded.v4.runtime.CharStreams; +import fr.insee.trevas.antlr.shaded.v4.runtime.CodePointCharStream; +import fr.insee.trevas.antlr.shaded.v4.runtime.CommonTokenStream; +import fr.insee.trevas.antlr.shaded.v4.runtime.tree.ParseTree; +import fr.insee.trevas.antlr.shaded.v4.runtime.tree.RuleNode; +import fr.insee.trevas.antlr.shaded.v4.runtime.tree.TerminalNode; import fr.insee.vtl.engine.VtlSyntaxPreprocessor; import fr.insee.vtl.model.exceptions.VtlScriptException; import fr.insee.vtl.parser.VtlLexer; @@ -9,12 +15,6 @@ import java.util.Set; import java.util.stream.Stream; import javax.script.ScriptException; -import org.antlr.v4.runtime.CharStreams; -import org.antlr.v4.runtime.CodePointCharStream; -import org.antlr.v4.runtime.CommonTokenStream; -import org.antlr.v4.runtime.tree.ParseTree; -import org.antlr.v4.runtime.tree.RuleNode; -import org.antlr.v4.runtime.tree.TerminalNode; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; diff --git a/vtl-parser/pom.xml b/vtl-parser/pom.xml index 22539f8c2..3dc774072 100644 --- a/vtl-parser/pom.xml +++ b/vtl-parser/pom.xml @@ -51,6 +51,49 @@ + + org.apache.maven.plugins + maven-shade-plugin + 3.5.0 + + + shade-antlr + package + + shade + + + + + false + + + + + org.antlr:antlr4-runtime + + + + + + + org.antlr.v4 + fr.insee.trevas.antlr.shaded.v4 + + + + + + + fr.insee.vtl.parser.shaded + + + + + + + + diff --git a/vtl-parser/src/main/java/module-info.java b/vtl-parser/src/main/java/module-info.java deleted file mode 100644 index e1e2f5fda..000000000 --- a/vtl-parser/src/main/java/module-info.java +++ /dev/null @@ -1,8 +0,0 @@ -/** This module exposes the constructs generated by Antlr from the VTL grammar files. */ -module fr.insee.vtl.parser { - requires org.antlr.antlr4.runtime; - - exports fr.insee.vtl.parser; - - opens fr.insee.vtl.parser; -} From 25cf0e5e5d4f5f4101e371cc87f3ddc1f4ea5c7a Mon Sep 17 00:00:00 2001 From: Sebastian Heupts Date: Mon, 23 Mar 2026 10:46:01 +0100 Subject: [PATCH 02/16] Added spark4 engine --- vtl-spark4/README.md | 3 + vtl-spark4/docs/Analytic_invocation.md | 118 + vtl-spark4/docs/expression_visitor.md | 56 + vtl-spark4/pom.xml | 70 + .../fr/insee/vtl/spark4/SparkDataset.java | 314 +++ .../vtl/spark4/SparkDatasetExpression.java | 36 + .../insee/vtl/spark4/SparkFilterFunction.java | 30 + .../vtl/spark4/SparkProcessingEngine.java | 1177 ++++++++++ .../java/fr/insee/vtl/spark4/SparkRowMap.java | 81 + .../java/fr/insee/vtl/spark4/SparkUtils.java | 24 + .../fr/insee/vtl/spark4/package-info.java | 2 + ...fr.insee.vtl.model.ProcessingEngineFactory | 1 + vtl-spark4/src/main/resources/c_h.csv | 11 + vtl-spark4/src/main/resources/ds1.csv | 11 + vtl-spark4/src/main/resources/ds2.csv | 6 + .../resources/input_sample/sample.parquet | Bin 0 -> 1030452 bytes .../fr/insee/vtl/spark4/SparkDatasetTest.java | 140 ++ .../fr/insee/vtl/spark4/SparkSQLTest.java | 181 ++ .../fr/insee/vtl/spark4/TemporalTest.java | 97 + .../processing.engine/AggregateTest.java | 305 +++ .../spark4/processing.engine/CalcTest.java | 83 + .../spark4/processing.engine/FilterTest.java | 94 + .../spark4/processing.engine/JoinTest.java | 278 +++ .../processing.engine/OperatorsTest.java | 94 + .../spark4/processing.engine/PivotTest.java | 108 + .../spark4/processing.engine/ProjectTest.java | 89 + .../spark4/processing.engine/RenameTest.java | 78 + .../processing.engine/ServiceLoaderTest.java | 22 + .../spark4/processing.engine/UnionTest.java | 477 ++++ .../processing.engine/ValidationTest.java | 2042 +++++++++++++++++ .../analytic/AnalyticAvgTest.java | 445 ++++ .../analytic/AnalyticCountTest.java | 746 ++++++ .../analytic/AnalyticFirstTest.java | 222 ++ .../analytic/AnalyticLagTest.java | 193 ++ .../analytic/AnalyticLastTest.java | 224 ++ .../analytic/AnalyticLeadTest.java | 237 ++ .../analytic/AnalyticMaxTest.java | 369 +++ .../analytic/AnalyticMedianTest.java | 435 ++++ .../analytic/AnalyticMinTest.java | 421 ++++ .../analytic/AnalyticRankTest.java | 184 ++ .../analytic/AnalyticRatioToReportTest.java | 283 +++ .../analytic/AnalyticStdPopTest.java | 388 ++++ .../analytic/AnalyticStdSampTest.java | 403 ++++ .../analytic/AnalyticSumTest.java | 458 ++++ .../analytic/AnalyticTest.java | 207 ++ .../analytic/AnalyticVarPopTest.java | 456 ++++ .../analytic/AnalyticVarSampTest.java | 244 ++ .../vtl/spark4/samples/DatasetSamples.java | 39 + .../spark4/samples/RegisterMethodTest.java | 99 + 49 files changed, 12081 insertions(+) create mode 100644 vtl-spark4/README.md create mode 100644 vtl-spark4/docs/Analytic_invocation.md create mode 100644 vtl-spark4/docs/expression_visitor.md create mode 100644 vtl-spark4/pom.xml create mode 100644 vtl-spark4/src/main/java/fr/insee/vtl/spark4/SparkDataset.java create mode 100644 vtl-spark4/src/main/java/fr/insee/vtl/spark4/SparkDatasetExpression.java create mode 100644 vtl-spark4/src/main/java/fr/insee/vtl/spark4/SparkFilterFunction.java create mode 100644 vtl-spark4/src/main/java/fr/insee/vtl/spark4/SparkProcessingEngine.java create mode 100644 vtl-spark4/src/main/java/fr/insee/vtl/spark4/SparkRowMap.java create mode 100644 vtl-spark4/src/main/java/fr/insee/vtl/spark4/SparkUtils.java create mode 100644 vtl-spark4/src/main/java/fr/insee/vtl/spark4/package-info.java create mode 100644 vtl-spark4/src/main/resources/META-INF/services/fr.insee.vtl.model.ProcessingEngineFactory create mode 100644 vtl-spark4/src/main/resources/c_h.csv create mode 100644 vtl-spark4/src/main/resources/ds1.csv create mode 100644 vtl-spark4/src/main/resources/ds2.csv create mode 100644 vtl-spark4/src/main/resources/input_sample/sample.parquet create mode 100644 vtl-spark4/src/test/java/fr/insee/vtl/spark4/SparkDatasetTest.java create mode 100644 vtl-spark4/src/test/java/fr/insee/vtl/spark4/SparkSQLTest.java create mode 100644 vtl-spark4/src/test/java/fr/insee/vtl/spark4/TemporalTest.java create mode 100644 vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/AggregateTest.java create mode 100644 vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/CalcTest.java create mode 100644 vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/FilterTest.java create mode 100644 vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/JoinTest.java create mode 100644 vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/OperatorsTest.java create mode 100644 vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/PivotTest.java create mode 100644 vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/ProjectTest.java create mode 100644 vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/RenameTest.java create mode 100644 vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/ServiceLoaderTest.java create mode 100644 vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/UnionTest.java create mode 100644 vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/ValidationTest.java create mode 100644 vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticAvgTest.java create mode 100644 vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticCountTest.java create mode 100644 vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticFirstTest.java create mode 100644 vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticLagTest.java create mode 100644 vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticLastTest.java create mode 100644 vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticLeadTest.java create mode 100644 vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticMaxTest.java create mode 100644 vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticMedianTest.java create mode 100644 vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticMinTest.java create mode 100644 vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticRankTest.java create mode 100644 vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticRatioToReportTest.java create mode 100644 vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticStdPopTest.java create mode 100644 vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticStdSampTest.java create mode 100644 vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticSumTest.java create mode 100644 vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticTest.java create mode 100644 vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticVarPopTest.java create mode 100644 vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticVarSampTest.java create mode 100644 vtl-spark4/src/test/java/fr/insee/vtl/spark4/samples/DatasetSamples.java create mode 100644 vtl-spark4/src/test/java/fr/insee/vtl/spark4/samples/RegisterMethodTest.java diff --git a/vtl-spark4/README.md b/vtl-spark4/README.md new file mode 100644 index 000000000..dbc8108ac --- /dev/null +++ b/vtl-spark4/README.md @@ -0,0 +1,3 @@ +# VTL Spark + +[![Maven Central](https://maven-badges.herokuapp.com/maven-central/fr.insee.trevas/vtl-spark/badge.svg)](https://maven-badges.herokuapp.com/maven-central/fr.insee.trevas/vtl-spark) diff --git a/vtl-spark4/docs/Analytic_invocation.md b/vtl-spark4/docs/Analytic_invocation.md new file mode 100644 index 000000000..f82b6e464 --- /dev/null +++ b/vtl-spark4/docs/Analytic_invocation.md @@ -0,0 +1,118 @@ +# Analytic invocation + +## General form: + +`analyticOperator`(firstOperand { , additionalOperand }* **over** ( `analyticClause` ) ) +- `analyticOperator` ::= **avg | count | max | median | min | stddev_pop| stddev_samp | sum | var_pop | var_samp | first_value | lag | last_value | lead | rank | ratio_to_report** +- `analyticClause` ::= { `partitionClause` } { `orderClause` } { `windowClause` } + - `partitionClause` ::= **partition by** identifier { , identifier }* + - `orderClause` ::= **order by** component { **asc | desc** } {, component { **asc | desc** } }* + - `windowClause` ::= { **data points** | **range** } **between** limitClause **and** `limitClause` + - `limitClause` ::= { num **preceding** | num **following** | **current data point** | **unbounded preceding** | **unbounded following** } + +### expression specification +- `analyticOperator`: is the keyword of the analytic operator to invoke (e.g., avg, count, max, etc.) +- `firstOperand`: is the first operand of the invoked analytic operator (a Data Set for an invocation at Data Set level + or a Component of the input Data Set for an invocation at Component level within a calc operator + or a calc clause in a join operation) +- `additionalOperand`: an additional operand (if any) of the invoked operator. The various operators can have + a different number of parameters. The number of parameters, their types and if they + are mandatory or optional depend on the invoked operator +- `analyticClause`: is the clause that specifies the dataset reorganization(partitions, orders and sliding windows). +- `partitionClause`: is the clause that specifies how to partition Data Points in groups to be analysed separately. + The input Data Set is partitioned according to the values of one or more columns (Identifier Components). + If the clause is omitted, **then the Data Set is partitioned by the Identifier Components that are not specified in the orderClause.** +- `orderClause`: is the clause that specifies how to order the Data Points. The input Data Set is ordered + according to the values of one or more Components, in ascending order if asc is + specified, in descending order if desc is specified, by default in ascending order if the + asc and desc keywords are omitted. +- `windowClause`: is the clause that specifies how to apply a sliding window on the ordered Data Points. + - The keyword **data points** means that the sliding window includes a certain number of Data Points + before and after the current Data Point in the order given by the + orderClause. + - The keyword **range** means that the sliding windows includes all the Data Points whose values are in a certain + range in respect to the value, for the current Data Point, of the Measure which the analytic is applied to. + +- `limitClause`: is the clause that can specify either the lower or the upper boundaries of the sliding window. + Each boundary is specified in relationship either to the whole partition or to the + current data point under analysis by using the following keywords: + - **unbounded preceding** means that the sliding window starts at the first Data Point + of the partition (it make sense only as the first limit of the window) + - **unbounded following** indicates that the sliding window ends at the last Data Point + of the partition (it makes sense only as the second limit of the window) + - current data point specifies that the window starts or ends at the current Data Point. + - num **preceding** specifies either the number of **data points** to consider preceding + the current data point in the order given by the orderClause (when **data points** is + specified in the window clause), or the maximum difference to consider, as for the + Measure which the analytic is applied to, between the value of the current Data + Point and the generic other Data Point (when **range** is specified in the windows clause). + - num **following** specifies either the number of data points to consider following the + current data point in the order given by the orderClause (when **data points** is + specified in the window clause), or the maximum difference to consider, as for the + Measure which the analytic is applied to, between the values of the generic other + Data Point and the current Data Point (when **range** is specified in the windows clause). + If the whole windowClause is omitted then **the default is data points between unbounded preceding and current data point.** + +- `identifier`: is an Identifier Component of the input Data Set. (e.g. column_name) +- `component`: a Component of the input Data Set (e.g. column name) +- `num`: a scalar number + +### Examples of valid syntaxes +```text +sum ( DS_1 over ( partition by Id_1 order by Id_2 ) ) +sum ( DS_1 over ( order by Id_2 ) ) +avg ( DS_1 over ( order by Id_1 data points between 1 preceding and 1 following ) ) +DS_1 [ calc M1 := sum ( Me_1 over ( order by Id_1 ) ) ] +``` + + +### parameter datatype: +Input: +- firstOperand :: dataset | component +- additionalOperand :: type of the (possible) additional parameter of the invoked operator +- identifier :: name +- component :: name +- num :: integer + +Output : +- result :: dataset | component + + +### Additional constraints + +- The analytic invocation cannot be nested in other Aggregate or Analytic invocations. +- The analytic operations at component level can be invoked within the calc clause, both as part of a Join operator +and the calc operator (see the parameter calcExpr of those operators). +- The basic scalar types of firstOperand and additionalOperand (if any) must be compliant with the specific basic +scalar types required by the invoked operator (the required basic scalar types are described in the table at the +beginning of this chapter and in the sections of the various operators below). + +### Behaviour + +The analytic Operator is applied as usual to all the Measures of the input Data Set (if invoked at Data Set level) or +to the specified Component of the input Data Set (if invoked at Component level). In both cases, the operator +calculates the desired output values for each Data Point of the input Data Set. +The behaviour of the analytic operations can be procedurally described as follows: +- The Data Points of the input Data Set are first partitioned (according to partitionBy) and then ordered +(according to orderBy). +- The operation is performed for each Data Point (named “current Data Point”) of the input Data Set. For each +input Data Point, one output Data Point is returned, having the same values of the Identifiers. The analytic +operator is applied to a “window” which includes a set of Data Points of the input Data Set and returns the +values of the Measure(s) of the output Data Point. + - If windowClause is not specified, then the set of Data Points which contribute to the analytic operation is + the whole partition which the current Data Point belongs to + - If windowClause is specified, then the set of Data Points is the one specified by windowClause (see + windowsClause and LimitClause explained above). + +For the invocation at Data Set level, the resulting Data Set has the same Measures as the input Data Set +`firstOperand`. For the invocation at Component level, the resulting Data Set has the Measures of the input Data +Set plus the Measures explicitly calculated through the `calc` clause. + +For the invocation at Data Set level, the Attribute propagation rule is applied. For invocation at Component level, +the Attributes calculated within the calc clause are maintained in the result; for all the other Attributes that are +defined as viral, the Attribute propagation rule is applied (for the semantics, see the Attribute Propagation Rule +section in the User Manual). + +As mentioned, the Analytic invocation at component level can be done within the `calc` clause, both as part of a +Join operator and the `calc` operator (see the parameter aggrCalc of those operators), therefore, for a better +comprehension fo the behaviour at Component level, see also those operators diff --git a/vtl-spark4/docs/expression_visitor.md b/vtl-spark4/docs/expression_visitor.md new file mode 100644 index 000000000..289c68eff --- /dev/null +++ b/vtl-spark4/docs/expression_visitor.md @@ -0,0 +1,56 @@ +# How to parse an expression via Antlr + +## Step 1: Check if the expression is already in the grammar (Vtl.g4) +## Step 2: Choose two possible way to parse the expression (visitor vs listeners) + +### When to use visitor or listeners + +If you plan to directly use the parser output for interpretation, the visitor is a good choice. You have full +control of the traversal, so in conditionals only one branch is visited, loops can be visited n times and so on. + +If you translate the input to a lower level, e.g. virtual machine instructions, both patterns may be useful. + +You might take a look at "Language Implementation Patterns", which covers the basic interpreter implementations. + +**Trevas uses the visitor pattern, as it's more flexible**. + +## Step 3 : Identify, Overwrite the visitor generate by Antlr + +When you overwrite the visitor, you need to parse the token and context to some types that you want to reuse in the +`ProcessingEngine`. + +After the parse of the token, then it calls the processingEngine to evaluate the parsed expression + +Check the code of **fr/insee/vtl/engine/visitors/AnalyticsVisitor.java** which parses below grammar. + +In the **fr/insee/vtl/model/Analytics.java**, we define all the types that we want to use to replace the Antlr generated +token type. + +```antlrv4 +anFunction: + op = ( SUM + | AVG + | COUNT + | MEDIAN + | MIN + | MAX + | STDDEV_POP + | STDDEV_SAMP + | VAR_POP + | VAR_SAMP + | FIRST_VALUE + | LAST_VALUE) + LPAREN expr OVER LPAREN (partition=partitionByClause? orderBy=orderByClause? windowing=windowingClause?)RPAREN RPAREN #anSimpleFunction + | op=(LAG |LEAD) LPAREN expr (COMMA offet=signedInteger(defaultValue=constant)?)? OVER LPAREN (partition=partitionByClause? orderBy=orderByClause) RPAREN RPAREN # lagOrLeadAn + | op=RATIO_TO_REPORT LPAREN expr OVER LPAREN (partition=partitionByClause) RPAREN RPAREN +``` + +## Step 4. Implement the evaluation logic in ProcessingEngine + + +### 4.1 Define method signature in the interface + +Note to make low level evaluation logic independent of the parser. The `ProcessingEngine` (fr/insee/vtl/model/ProcessingEngine.java) +is an interface. For now, trevas has two concrete implementations of this interface (e.g. spark, in-memory) + +### 4.2 Implement the method in the concrete ProcessingEngine implementation. \ No newline at end of file diff --git a/vtl-spark4/pom.xml b/vtl-spark4/pom.xml new file mode 100644 index 000000000..0ca251d1e --- /dev/null +++ b/vtl-spark4/pom.xml @@ -0,0 +1,70 @@ + + + 4.0.0 + + + fr.insee.trevas + trevas-parent + 2.3.0-SNAPSHOT + + + vtl-spark4 + VTL Spark4 + Trevas engine for Apache Spark 4 + 2.3.0-SNAPSHOT + + + + ${project.basedir}/../coverage/target/site/jacoco-aggregate/jacoco.xml + + + + + + fr.insee.trevas + vtl-model + 2.3.0-SNAPSHOT + compile + + + fr.insee.trevas + vtl-engine + 2.3.0-SNAPSHOT + compile + + + org.apache.spark + spark-sql_2.13 + 4.0.0 + + + + com.h2database + h2 + 2.4.240 + test + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.5.3 + + --add-exports java.base/sun.nio.ch=ALL-UNNAMED + + + + org.apache.maven.plugins + maven-jar-plugin + 3.4.2 + + + + + diff --git a/vtl-spark4/src/main/java/fr/insee/vtl/spark4/SparkDataset.java b/vtl-spark4/src/main/java/fr/insee/vtl/spark4/SparkDataset.java new file mode 100644 index 000000000..c8930e181 --- /dev/null +++ b/vtl-spark4/src/main/java/fr/insee/vtl/spark4/SparkDataset.java @@ -0,0 +1,314 @@ +package fr.insee.vtl.spark; + +import static org.apache.spark.sql.types.DataTypes.*; + +import fr.insee.vtl.model.Dataset; +import fr.insee.vtl.model.Structured; +import java.time.Instant; +import java.time.LocalDate; +import java.util.*; +import java.util.stream.Collectors; +import org.apache.spark.sql.Column; +import org.apache.spark.sql.Row; +import org.apache.spark.sql.RowFactory; +import org.apache.spark.sql.SparkSession; +import org.apache.spark.sql.types.*; + +/** The SparkDataset class is a wrapper around a Spark dataframe. */ +public class SparkDataset implements Dataset { + + private final org.apache.spark.sql.Dataset sparkDataset; + private DataStructure dataStructure = null; + private Map roles = Collections.emptyMap(); + private Map valuedomains = Collections.emptyMap(); + + /** + * Constructor taking a Spark dataset. + * + * @param sparkDataset a Spark dataset. + */ + public SparkDataset(org.apache.spark.sql.Dataset sparkDataset) { + org.apache.spark.sql.Dataset casted = castIfNeeded(sparkDataset); + this.sparkDataset = casted; + this.dataStructure = fromSparkSchema(casted.schema(), Map.of(), Map.of()); + } + + /** + * Constructor taking a Spark dataset and a structure. + * + * @param sparkDataset a Spark dataset. + * @param structure a Data Structure. + */ + public SparkDataset(org.apache.spark.sql.Dataset sparkDataset, DataStructure structure) { + org.apache.spark.sql.Dataset castedSparkDataset = + castIfNeeded(Objects.requireNonNull(sparkDataset)); + this.sparkDataset = addMetadata(castedSparkDataset, structure); + this.dataStructure = structure; + this.roles = + Objects.requireNonNull( + structure.entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().getRole()))); + this.valuedomains = + Objects.requireNonNull( + structure.entrySet().stream() + .filter(e -> null != e.getValue().getValuedomain()) + .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().getValuedomain()))); + } + + /** + * Constructor taking a {@link Dataset}, a mapping of component names and roles, and a Spark + * session. + * + * @param vtlDataset a VTL dataset. + * @param roles a map between component names and their roles in the dataset. + * @param spark a Spark session to use for the creation of the Spark dataset. + */ + public SparkDataset(Dataset vtlDataset, Map roles, SparkSession spark) { + List rows = + vtlDataset.getDataPoints().stream() + .map( + dataPoint -> { + // Convert Instant to Date for Spark compatibility + Object[] values = + dataPoint.stream() + .map( + obj -> { + if (obj instanceof java.time.Instant instant) { + return java.sql.Date.valueOf( + instant.atZone(java.time.ZoneOffset.UTC).toLocalDate()); + } + return obj; + }) + .toArray(); + return RowFactory.create(values); + }) + .collect(Collectors.toList()); + // TODO: Handle nullable with component + StructType schema = toSparkSchema(vtlDataset.getDataStructure()); + this.sparkDataset = spark.createDataFrame(rows, schema); + this.roles = Objects.requireNonNull(roles); + this.dataStructure = vtlDataset.getDataStructure(); + } + + /** + * Constructor taking a Spark dataset and a mapping of component names and roles. + * + * @param sparkDataset a Spark dataset. + * @param roles a map between component names and their roles in the dataset. + */ + public SparkDataset(org.apache.spark.sql.Dataset sparkDataset, Map roles) { + org.apache.spark.sql.Dataset castedSparkDataset = + castIfNeeded(Objects.requireNonNull(sparkDataset)); + DataStructure dataStructure = fromSparkSchema(sparkDataset.schema(), roles, Map.of()); + this.sparkDataset = addMetadata(castedSparkDataset, dataStructure); + this.dataStructure = dataStructure; + this.roles = Objects.requireNonNull(roles); + } + + /** Cast integer and float types to long and double efficiently. */ + private static org.apache.spark.sql.Dataset castIfNeeded( + org.apache.spark.sql.Dataset sparkDataset) { + StructType schema = sparkDataset.schema(); + + List castedColumns = + Arrays.stream(schema.fields()) + .map( + field -> { + DataType type = field.dataType(); + Column col = SparkUtils.safeCol(field.name()); + if (type instanceof IntegerType + || type instanceof FloatType + || type instanceof DecimalType) { + return col.cast( + type instanceof IntegerType ? DataTypes.LongType : DataTypes.DoubleType) + .alias(field.name()); + } + return col; + }) + .toList(); + + return sparkDataset.select(castedColumns.toArray(new Column[0])); + } + + /** Convert Spark schema to VTL DataStructure efficiently. */ + public static DataStructure fromSparkSchema( + StructType schema, Map roles, Map valuedomains) { + return new DataStructure( + Arrays.stream(schema.fields()) + .map( + field -> + new Component( + field.name(), + extractVtlType(field), + handleRole(field, roles), + null, + handleValuedomain(field, valuedomains))) + .collect(Collectors.toList())); + } + + /** Add metadata to dataset in a single transformation step. */ + private static org.apache.spark.sql.Dataset addMetadata( + org.apache.spark.sql.Dataset sparkDataset, DataStructure structure) { + StructType updatedSchema = toSparkSchema(structure); + + return sparkDataset.select( + Arrays.stream(updatedSchema.fields()) + .map(field -> SparkUtils.safeCol(field.name()).as(field.name(), field.metadata())) + .toArray(Column[]::new)); + } + + /** + * Transforms a {@link DataStructure} into a Spark schema. + * + * @param structure the dataset structure to transform + * @return The resulting Spark schema (StructType object). + */ + public static StructType toSparkSchema(DataStructure structure) { + List schema = new ArrayList<>(); + for (Component component : structure.values()) { + Object vd = null == component.getValuedomain() ? null : component.getValuedomain(); + Map map = new HashMap<>(); + map.put("vtlRole", component.getRole().name()); + map.put("vtlValuedomain", vd); + map.put("vtlType", component.getType().getName()); + scala.collection.immutable.Map md = + scala.collection.immutable.Map.from(scala.jdk.javaapi.CollectionConverters.asScala(map)); + schema.add( + DataTypes.createStructField( + component.getName(), fromVtlType(component.getType()), true, new Metadata(md))); + } + return DataTypes.createStructType(schema); + } + + private static Role handleRole(StructField field, Map roles) { + Role fieldRole; + if (roles.containsKey(field.name())) { + fieldRole = roles.get(field.name()); + } else if (field.metadata().contains("vtlRole")) { + String roleName = field.metadata().getString("vtlRole"); + fieldRole = Role.valueOf(roleName); + } else { + fieldRole = Role.MEASURE; + } + return fieldRole; + } + + private static String handleValuedomain(StructField field, Map valuedomains) { + String valuedomain; + if (valuedomains.containsKey(field.name())) { + valuedomain = valuedomains.get(field.name()); + } else if (field.metadata().contains("vtlValuedomain")) { + valuedomain = field.metadata().getString("vtlValuedomain"); + } else { + valuedomain = null; + } + return valuedomain; + } + + /** + * Translates a Spark data type into a VTL data type. + * + * @param dataType the Spark {@link DataType} to translate. + * @return The corresponding VTL data type as a class. + */ + public static Class toVtlType(DataType dataType) { + if (StringType.sameType(dataType)) { + return String.class; + } else if (IntegerType.sameType(dataType)) { + return Long.class; + } else if (LongType.sameType(dataType)) { + return Long.class; + } else if (FloatType.sameType(dataType)) { + return Double.class; + } else if (DoubleType.sameType(dataType)) { + return Double.class; + } else if (BooleanType.sameType(dataType)) { + return Boolean.class; + } else if (DecimalType.class.equals(dataType.getClass())) { + return Double.class; + } else if (DateType.sameType(dataType) || TimestampType.sameType(dataType)) { + return Instant.class; + } else { + throw new UnsupportedOperationException("unsupported type " + dataType); + } + } + + /** + * Translates a VTL data type into a Spark data type. + * + * @param type the VTL data type to translate (as a class). + * @return The corresponding Spark {@link DataType}. + */ + public static DataType fromVtlType(Class type) { + if (String.class.equals(type)) { + return StringType; + } else if (Long.class.equals(type)) { + return LongType; + } else if (Double.class.equals(type)) { + return DoubleType; + } else if (Boolean.class.equals(type)) { + return BooleanType; + } else if (Instant.class.equals(type) || LocalDate.class.equals(type)) { + return DateType; + } else { + throw new UnsupportedOperationException("unsupported type " + type); + } + } + + private static Class extractVtlType(StructField field) { + if (field.metadata().contains("vtlType")) { + try { + return Class.forName(field.metadata().getString("vtlType")); + } catch (ClassNotFoundException e) { + throw new IllegalStateException("Unknown VTL type in metadata", e); + } + } + return toVtlType(field.dataType()); + } + + /** + * Returns the Spark dataset. + * + * @return The Spark dataset. + */ + public org.apache.spark.sql.Dataset getSparkDataset() { + return sparkDataset; + } + + @Override + public List getDataPoints() { + List rows = sparkDataset.collectAsList(); + return rows.stream() + .map( + row -> { + List values = new ArrayList<>(); + int i = 0; + for (Component component : getDataStructure().values()) { + Object v = row.get(i++); + if (component.getType().equals(Instant.class)) { + if (v instanceof java.time.LocalDate ld) { + values.add(ld.atStartOfDay().toInstant(java.time.ZoneOffset.UTC)); + } else if (v instanceof java.sql.Date d) { + values.add(d.toLocalDate().atStartOfDay().toInstant(java.time.ZoneOffset.UTC)); + } else if (v instanceof java.sql.Timestamp ts) { + values.add(ts.toInstant()); + } else { + values.add(v); + } + } else { + values.add(v); + } + } + return new DataPoint(getDataStructure(), values); + }) + .collect(Collectors.toList()); + } + + @Override + public Structured.DataStructure getDataStructure() { + if (dataStructure == null) { + dataStructure = fromSparkSchema(sparkDataset.schema(), roles, valuedomains); + } + return dataStructure; + } +} diff --git a/vtl-spark4/src/main/java/fr/insee/vtl/spark4/SparkDatasetExpression.java b/vtl-spark4/src/main/java/fr/insee/vtl/spark4/SparkDatasetExpression.java new file mode 100644 index 000000000..3f0290a80 --- /dev/null +++ b/vtl-spark4/src/main/java/fr/insee/vtl/spark4/SparkDatasetExpression.java @@ -0,0 +1,36 @@ +package fr.insee.vtl.spark; + +import fr.insee.vtl.model.DatasetExpression; +import fr.insee.vtl.model.Positioned; +import fr.insee.vtl.model.Structured; +import java.util.Map; +import java.util.Objects; + +/** + * The SparkDatasetExpression class represents a VTL dataset expression involving a + * Spark dataset. + */ +public class SparkDatasetExpression extends DatasetExpression implements Positioned { + + private final SparkDataset dataset; + + /** + * Constructor taking a {@link SparkDataset}. + * + * @param dataset The Spark dataset used in the expression. + */ + public SparkDatasetExpression(SparkDataset dataset, Positioned position) { + super(position); + this.dataset = Objects.requireNonNull(dataset); + } + + @Override + public SparkDataset resolve(Map context) { + return dataset; + } + + @Override + public Structured.DataStructure getDataStructure() { + return dataset.getDataStructure(); + } +} diff --git a/vtl-spark4/src/main/java/fr/insee/vtl/spark4/SparkFilterFunction.java b/vtl-spark4/src/main/java/fr/insee/vtl/spark4/SparkFilterFunction.java new file mode 100644 index 000000000..a44d32e6a --- /dev/null +++ b/vtl-spark4/src/main/java/fr/insee/vtl/spark4/SparkFilterFunction.java @@ -0,0 +1,30 @@ +package fr.insee.vtl.spark; + +import fr.insee.vtl.model.ResolvableExpression; +import org.apache.spark.api.java.function.FilterFunction; +import org.apache.spark.sql.Row; + +/** + * The SparkFilterFunction class is a wrapper around a filter expression operating on + * rows of a Spark dataset. + */ +public class SparkFilterFunction implements FilterFunction { + + private final ResolvableExpression expression; + + /** + * Constructor taking a VTL expression. + * + * @param expression the VTL expression. + */ + public SparkFilterFunction(ResolvableExpression expression) { + this.expression = expression; + } + + @Override + public boolean call(Row row) { + var res = expression.resolve(new SparkRowMap(row)); + if (res == null) return false; + return (boolean) res; + } +} diff --git a/vtl-spark4/src/main/java/fr/insee/vtl/spark4/SparkProcessingEngine.java b/vtl-spark4/src/main/java/fr/insee/vtl/spark4/SparkProcessingEngine.java new file mode 100644 index 000000000..dbbd70728 --- /dev/null +++ b/vtl-spark4/src/main/java/fr/insee/vtl/spark4/SparkProcessingEngine.java @@ -0,0 +1,1177 @@ +package fr.insee.vtl.spark; + +import static fr.insee.vtl.model.AggregationExpression.*; +import static fr.insee.vtl.model.Dataset.Component; +import static fr.insee.vtl.model.Dataset.Role; +import static fr.insee.vtl.model.Dataset.Role.IDENTIFIER; +import static fr.insee.vtl.model.Dataset.Role.MEASURE; +import static fr.insee.vtl.spark.SparkDataset.fromVtlType; +import static org.apache.spark.sql.functions.*; +import static org.apache.spark.sql.functions.avg; +import static org.apache.spark.sql.functions.count; +import static org.apache.spark.sql.functions.max; +import static org.apache.spark.sql.functions.min; +import static org.apache.spark.sql.functions.sum; +import static scala.collection.JavaConverters.iterableAsScalaIterable; + +import fr.insee.vtl.engine.exceptions.VtlRuntimeException; +import fr.insee.vtl.model.*; +import java.util.*; +import java.util.stream.Collectors; +import javax.script.ScriptEngine; +import org.apache.spark.sql.*; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.expressions.UserDefinedFunction; +import org.apache.spark.sql.expressions.Window; +import org.apache.spark.sql.expressions.WindowSpec; +import scala.collection.JavaConverters; +import scala.collection.Seq; + +/** + * The SparkProcessingEngine class is an implementation of a VTL engine using Apache + * Spark. + */ +public class SparkProcessingEngine implements ProcessingEngine { + + public static final Integer DEFAULT_MEDIAN_ACCURACY = 1000000; + public static final UnsupportedOperationException UNKNOWN_ANALYTIC_FUNCTION = + new UnsupportedOperationException("Unknown analytic function"); + private static final String BOOLVAR = "bool_var"; + private static final String ERRORCODE = "errorcode"; + private static final String ERRORLEVEL = "errorlevel"; + private static final String RULEID = "ruleid"; + private static final String IMBALANCE = "imbalance"; + private static final String NON_NULL = "non_null"; + private static final String NON_ZERO = "non_zero"; + private static final String PARTIAL_NULL = "partial_null"; + private static final String PARTIAL_ZERO = "partial_zero"; + private static final String ALWAYS_NULL = "always_null"; + private static final String ALWAYS_ZERO = "always_zero"; + private final SparkSession spark; + + /** + * Constructor taking an existing Spark session. + * + * @param spark The Spark session to use for the engine. + */ + public SparkProcessingEngine(SparkSession spark) { + spark.conf().set("spark.sql.datetime.java8API.enabled", true); + this.spark = Objects.requireNonNull(spark); + } + + private static Map getRoleMap(Collection components) { + return components.stream().collect(Collectors.toMap(Component::getName, Component::getRole)); + } + + private static Map getRoleMap(fr.insee.vtl.model.Dataset dataset) { + return getRoleMap(dataset.getDataStructure().values()); + } + + // TODO (expression instanceof MinAggregationExpression) + // TODO column = stddev_pop(columnName); + private static Column convertAggregation(String columnName, AggregationExpression expression) + throws UnsupportedOperationException { + Column column; + if (expression instanceof MinAggregationExpression) { + column = min(SparkUtils.safeCol(columnName)); + } else if (expression instanceof MaxAggregationExpression) { + column = max(SparkUtils.safeCol(columnName)); + } else if (expression instanceof AverageAggregationExpression) { + column = avg(SparkUtils.safeCol(columnName)); + } else if (expression instanceof SumAggregationExpression) { + column = sum(SparkUtils.safeCol(columnName)); + } else if (expression instanceof CountAggregationExpression) { + column = count("*"); + } else if (expression instanceof MedianAggregationExpression) { + column = + percentile_approx(SparkUtils.safeCol(columnName), lit(0.5), lit(DEFAULT_MEDIAN_ACCURACY)); + } else if (expression instanceof StdDevSampAggregationExpression) { + column = stddev_samp(SparkUtils.safeCol(columnName)); + } else if (expression instanceof VarPopAggregationExpression) { + column = var_pop(SparkUtils.safeCol(columnName)); + } else if (expression instanceof VarSampAggregationExpression) { + column = var_samp(SparkUtils.safeCol(columnName)); + } else { + throw new UnsupportedOperationException("unknown aggregation " + expression.getClass()); + } + return column.alias(columnName); + } + + // todo need to add unit test + private static WindowSpec buildWindowSpec(List partitionBy) { + return buildWindowSpec(partitionBy, null, null); + } + + // todo need to add unit test + private static WindowSpec buildWindowSpec( + List partitionBy, Map orderBy) { + return buildWindowSpec(partitionBy, orderBy, null); + } + + // todo need to add unit test + private static WindowSpec buildWindowSpec( + List partitionBy, Map orderBy, Analytics.WindowSpec window) { + if (partitionBy == null) { + partitionBy = List.of(); + } + + Column[] partitionCols = + scala.collection.JavaConverters.seqAsJavaList(colNameToCol(partitionBy)) + .toArray(new Column[0]); + + WindowSpec windowSpec = Window.partitionBy(partitionCols); + + if (orderBy == null) { + orderBy = Map.of(); + } + + Column[] orderCols = + scala.collection.JavaConverters.seqAsJavaList(buildOrderCol(orderBy)) + .toArray(new Column[0]); + windowSpec = windowSpec.orderBy(orderCols); + + if (window instanceof Analytics.DataPointWindow) { + windowSpec = windowSpec.rowsBetween(-window.getLower(), window.getUpper()); + } else if (window instanceof Analytics.RangeWindow) { + windowSpec = windowSpec.rangeBetween(-window.getLower(), window.getUpper()); + } + + return windowSpec; + } + + public static Seq colNameToCol(List inputColNames) { + List cols = new ArrayList<>(); + for (String colName : inputColNames) { + cols.add(SparkUtils.safeCol(colName)); + } + return JavaConverters.asScalaIteratorConverter(cols.iterator()).asScala().toSeq(); + } + + // helper function that builds order col expression with asc and desc spec + public static Seq buildOrderCol(Map orderCols) { + List orders = new ArrayList<>(); + for (Map.Entry entry : orderCols.entrySet()) { + if (entry.getValue().equals(Analytics.Order.DESC)) { + orders.add(SparkUtils.safeCol(entry.getKey()).desc()); + } else { + orders.add(SparkUtils.safeCol(entry.getKey())); + } + } + return JavaConverters.asScalaIteratorConverter(orders.iterator()).asScala().toSeq(); + } + + private static List identifierNames(List components) { + return components.stream() + .filter(component -> IDENTIFIER.equals(component.getRole())) + .map(Component::getName) + .collect(Collectors.toList()); + } + + private SparkDataset asSparkDataset(DatasetExpression expression) { + if (expression instanceof SparkDatasetExpression datasetExpression) { + return datasetExpression.resolve(Map.of()); + } else { + var dataset = expression.resolve(Map.of()); + if (dataset instanceof PersistentDataset persistentDataset) { + dataset = persistentDataset.getDelegate(); + } + if (dataset instanceof SparkDataset sparkDataset) { + return sparkDataset; + } else { + return new SparkDataset(dataset, getRoleMap(dataset), spark); + } + } + } + + @Override + public DatasetExpression executeCalc( + DatasetExpression expression, + Map expressions, + Map roles, + Map expressionStrings) { + SparkDataset dataset = asSparkDataset(expression); + Dataset ds = dataset.getSparkDataset(); + + // Rename all the columns to avoid conflicts (static single assignment). + Map aliasesToName = new HashMap<>(); + Map renamedExpressions = new LinkedHashMap<>(); + Map renamedExpressionString = new LinkedHashMap<>(); + for (var name : expressions.keySet()) { + String alias = name + "_" + aliasesToName.size(); + renamedExpressions.put(alias, expressions.get(name)); + renamedExpressionString.put(alias, expressionStrings.get(name)); + aliasesToName.put(alias, name); + } + + // First pass with interpreted spark expressions + Dataset interpreted = executeCalcInterpreted(ds, renamedExpressionString); + + // Execute the rest using the resolvable expressions + Dataset evaluated = executeCalcEvaluated(interpreted, renamedExpressions); + + // Rename the columns back to their original names + Dataset renamed = rename(evaluated, aliasesToName); + + // Create the new role map. + var roleMap = getRoleMap(dataset); + roleMap.putAll(roles); + + return new SparkDatasetExpression(new SparkDataset(renamed, roleMap), expression); + } + + private Dataset executeCalcEvaluated( + Dataset interpreted, Map expressions) { + var columnNames = Set.of(interpreted.columns()); + Column structColumns = + struct(columnNames.stream().map(colName -> col(colName)).toArray(Column[]::new)); + for (var name : expressions.keySet()) { + // Ignore the columns that already exist. + if (columnNames.contains(name)) { + continue; + } + // Execute the ResolvableExpression by wrapping it in a UserDefinedFunction. + ResolvableExpression expression = expressions.get(name); + UserDefinedFunction exprFunction = + udf( + (Row row) -> { + try { + SparkRowMap context = new SparkRowMap(row); + Object result = expression.resolve(context); + // Convert java.time.Instant to java.sql.Date for Spark compatibility + if (result instanceof java.time.Instant instant) { + return java.sql.Date.valueOf( + instant.atZone(java.time.ZoneOffset.UTC).toLocalDate()); + } + return result; + } catch (VtlRuntimeException e) { + // VtlRuntimeException already wraps the real VTL error, re-throw it + throw e; + } catch (Exception e) { + // Wrap any other exception to provide context + throw new RuntimeException( + "Error in UDF for column '" + name + "': " + e.getMessage(), e); + } + }, + fromVtlType(expression.getType())); + interpreted = interpreted.withColumn(name, exprFunction.apply(structColumns)); + } + return interpreted; + } + + private Dataset executeCalcInterpreted( + Dataset result, Map expressionStrings) { + for (String name : expressionStrings.keySet()) { + try { + String expression = expressionStrings.get(name); + if (expression == null) continue; + result = result.withColumn(name, expr(expression)); + } catch (Exception e) { + // Silently ignore expressions that Spark SQL cannot interpret directly. + // These will be evaluated as ResolvableExpressions in executeCalcEvaluated instead. + } + } + return result; + } + + @Override + public DatasetExpression executeFilter( + DatasetExpression expression, ResolvableExpression filter, String filterText) { + SparkDataset dataset = asSparkDataset(expression); + + Dataset ds = dataset.getSparkDataset(); + try { + Dataset result = ds.filter(filterText); + return new SparkDatasetExpression(new SparkDataset(result, getRoleMap(dataset)), expression); + } catch (Exception e) { + SparkFilterFunction filterFunction = new SparkFilterFunction(filter); + Dataset result = ds.filter(filterFunction); + return new SparkDatasetExpression(new SparkDataset(result, getRoleMap(dataset)), expression); + } + } + + @Override + public DatasetExpression executeRename(DatasetExpression expression, Map fromTo) { + SparkDataset dataset = asSparkDataset(expression); + + var result = rename(dataset.getSparkDataset(), fromTo); + + var originalRoles = getRoleMap(dataset); + var renamedRoles = new LinkedHashMap<>(originalRoles); + for (Map.Entry fromToEntry : fromTo.entrySet()) { + renamedRoles.put(fromToEntry.getValue(), originalRoles.get(fromToEntry.getKey())); + } + + return new SparkDatasetExpression(new SparkDataset(result, renamedRoles), expression); + } + + public Dataset rename(Dataset dataset, Map fromTo) { + List columns = new ArrayList<>(); + for (String name : dataset.columns()) { + if (fromTo.containsKey(name)) { + columns.add(SparkUtils.safeCol(name).as(fromTo.get(name))); + } else if (!fromTo.containsValue(name)) { + columns.add(SparkUtils.safeCol(name)); + } + } + return dataset.select(iterableAsScalaIterable(columns).toSeq()); + } + + @Override + public DatasetExpression executeProject(DatasetExpression expression, List columnNames) { + SparkDataset dataset = asSparkDataset(expression); + + List columns = columnNames.stream().map(Column::new).collect(Collectors.toList()); + Column[] columnArray = columns.toArray(new Column[0]); + + // Project in spark. + Dataset result = dataset.getSparkDataset().select(columnArray); + + return new SparkDatasetExpression(new SparkDataset(result, getRoleMap(dataset)), expression); + } + + private boolean checkColNameCompatibility(List datasets) { + boolean result = true; + IndexedHashMap baseStructure = datasets.get(0).getDataStructure(); + for (int i = 1; i <= datasets.size() - 1; i++) { + // check if current structure equals base structure + IndexedHashMap curretStructure = datasets.get(i).getDataStructure(); + if (!baseStructure.equals(curretStructure)) { + result = false; + break; + } + } + return result; + } + + @Override + public DatasetExpression executeUnion(List datasets) { + DatasetExpression dataset = datasets.get(0); + + if (!checkColNameCompatibility(datasets)) + throw new UnsupportedOperationException("The schema of the dataset is not compatible"); + // use the base data structure to build the result data roles + Structured.DataStructure baseDataStructure = datasets.get(0).getDataStructure(); + Set keys = baseDataStructure.keySet(); + HashMap dataRoles = new HashMap<>(); + for (String key : keys) { + Component item = baseDataStructure.get(key); + dataRoles.put(item.getName(), item.getRole()); + } + + // get Id column list + List colNames = datasets.get(0).getColumnNames(); + ArrayList idColList = new ArrayList<>(); + IndexedHashMap structure = dataset.getDataStructure(); + // get column list with ID role, it will be used to drop duplicated rows + for (String colName : colNames) { + if (structure.get(colName).getRole().equals(IDENTIFIER)) idColList.add(colName); + } + int size = datasets.size(); + + if (size == 1) { + return datasets.get(0); + } else { + Dataset result = asSparkDataset(datasets.get(0)).getSparkDataset(); + for (int i = 1; i <= size - 1; i++) { + Dataset current = asSparkDataset(datasets.get(i)).getSparkDataset(); + result = result.union(current); + } + result = result.dropDuplicates(iterableAsScalaIterable(idColList).toSeq()); + return new SparkDatasetExpression(new SparkDataset(result, dataRoles), datasets.get(0)); + } + } + + @Override + public DatasetExpression executeAggr( + DatasetExpression dataset, + List groupBy, + Map collectorMap) { + SparkDataset sparkDataset = asSparkDataset(dataset); + List columns = + collectorMap.entrySet().stream() + .map(e -> convertAggregation(e.getKey(), e.getValue())) + .collect(Collectors.toList()); + List groupByColumns = + groupBy.stream().map(SparkUtils::safeCol).collect(Collectors.toList()); + Dataset result = + sparkDataset + .getSparkDataset() + .groupBy(iterableAsScalaIterable(groupByColumns).toSeq()) + .agg( + columns.get(0), + iterableAsScalaIterable(columns.subList(1, columns.size())).toSeq()); + SparkDataset sparkDs = new SparkDataset(result, dataset.getRoles()); + return new SparkDatasetExpression(sparkDs, dataset); + } + + @Override + public DatasetExpression executeSimpleAnalytic( + DatasetExpression dataset, + String targetColName, + Analytics.Function function, + String sourceColName, + List partitionBy, + Map orderBy, + Analytics.WindowSpec window) { + SparkDataset sparkDataset = asSparkDataset(dataset); + + // step1: build window spec + WindowSpec windowSpec = buildWindowSpec(partitionBy, orderBy, window); + + // step 2: call analytic func on window spec + // 2.1 get all measurement column + + Column safeCol = SparkUtils.safeCol(sourceColName); + + Column column = + switch (function) { + case COUNT -> count(safeCol).over(windowSpec); + case SUM -> sum(safeCol).over(windowSpec); + case MIN -> min(safeCol).over(windowSpec); + case MAX -> max(safeCol).over(windowSpec); + case AVG -> avg(safeCol).over(windowSpec); + case MEDIAN -> + percentile_approx(safeCol, lit(0.5), lit(DEFAULT_MEDIAN_ACCURACY)).over(windowSpec); + case STDDEV_POP -> stddev_pop(safeCol).over(windowSpec); + case STDDEV_SAMP -> stddev_samp(safeCol).over(windowSpec); + case VAR_POP -> var_pop(safeCol).over(windowSpec); + case VAR_SAMP -> var_samp(safeCol).over(windowSpec); + case FIRST_VALUE -> first(safeCol).over(windowSpec); + case LAST_VALUE -> last(safeCol).over(windowSpec); + default -> throw UNKNOWN_ANALYTIC_FUNCTION; + }; + var result = sparkDataset.getSparkDataset().withColumn(targetColName, column); + return new SparkDatasetExpression(new SparkDataset(result), dataset); + } + + @Override + public DatasetExpression executeLeadOrLagAn( + DatasetExpression dataset, + String targetColName, + Analytics.Function function, + String sourceColName, + int offset, + List partitionBy, + Map orderBy) { + SparkDataset sparkDataset = asSparkDataset(dataset); + + // step1: build window spec + WindowSpec windowSpec = buildWindowSpec(partitionBy, orderBy); + + // step 2: call analytic func on window spec + Column column = + switch (function) { + case LEAD -> lead(sourceColName, offset).over(windowSpec); + case LAG -> lag(sourceColName, offset).over(windowSpec); + default -> throw UNKNOWN_ANALYTIC_FUNCTION; + }; + var result = sparkDataset.getSparkDataset().withColumn(targetColName, column); + return new SparkDatasetExpression(new SparkDataset(result), dataset); + } + + @Override + public DatasetExpression executeRatioToReportAn( + DatasetExpression dataset, + String targetColName, + Analytics.Function function, + String sourceColName, + List partitionBy) { + if (!function.equals(Analytics.Function.RATIO_TO_REPORT)) throw UNKNOWN_ANALYTIC_FUNCTION; + + SparkDataset sparkDataset = asSparkDataset(dataset); + // step1: build window spec + WindowSpec windowSpec = buildWindowSpec(partitionBy); + + // step 2: call analytic func on window spec + String totalColName = "total_" + sourceColName; + // 2.2 add the result column for the calc clause + Dataset result = + sparkDataset + .getSparkDataset() + .withColumn(totalColName, sum(SparkUtils.safeCol(sourceColName)).over(windowSpec)) + .withColumn(targetColName, SparkUtils.safeCol(sourceColName).divide(col(totalColName))) + .drop(totalColName); + // 2.3 without the calc clause, we need to overwrite the measure columns with the result column + return new SparkDatasetExpression(new SparkDataset(result), dataset); + } + + @Override + public DatasetExpression executeRankAn( + DatasetExpression dataset, + String targetColName, + Analytics.Function function, + List partitionBy, + Map orderBy) { + if (!function.equals(Analytics.Function.RANK)) throw UNKNOWN_ANALYTIC_FUNCTION; + + SparkDataset sparkDataset = asSparkDataset(dataset); + // step1: build window spec + WindowSpec windowSpec = buildWindowSpec(partitionBy, orderBy); + + // step 2: call analytic func on window spec + Dataset result = + sparkDataset.getSparkDataset().withColumn(targetColName, rank().over(windowSpec)); + // 2.3 without the calc clause, we need to overwrite the measure columns with the result column + return new SparkDatasetExpression(new SparkDataset(result), dataset); + } + + @Override + public DatasetExpression executeInnerJoin( + Map datasets, List components) { + List> sparkDatasets = toAliasedDatasets(datasets); + List identifiers = identifierNames(components); + var innerJoin = executeJoin(sparkDatasets, identifiers, "inner"); + DatasetExpression datasetExpression = datasets.entrySet().iterator().next().getValue(); + return new SparkDatasetExpression( + new SparkDataset(innerJoin, getRoleMap(components)), datasetExpression); + } + + @Override + public DatasetExpression executeLeftJoin( + Map datasets, List components) { + List> sparkDatasets = toAliasedDatasets(datasets); + List identifiers = identifierNames(components); + var innerJoin = executeJoin(sparkDatasets, identifiers, "left"); + DatasetExpression datasetExpression = datasets.entrySet().iterator().next().getValue(); + return new SparkDatasetExpression( + new SparkDataset(innerJoin, getRoleMap(components)), datasetExpression); + } + + @Override + public DatasetExpression executeCrossJoin( + Map datasets, List identifiers) { + List> sparkDatasets = toAliasedDatasets(datasets); + var crossJoin = executeJoin(sparkDatasets, List.of(), "cross"); + DatasetExpression datasetExpression = datasets.entrySet().iterator().next().getValue(); + return new SparkDatasetExpression( + new SparkDataset(crossJoin, getRoleMap(identifiers)), datasetExpression); + } + + @Override + public DatasetExpression executeFullJoin( + Map datasets, List identifiers) { + List> sparkDatasets = toAliasedDatasets(datasets); + List identifierNames = identifierNames(identifiers); + var crossJoin = executeJoin(sparkDatasets, identifierNames, "outer"); + DatasetExpression datasetExpression = datasets.entrySet().iterator().next().getValue(); + return new SparkDatasetExpression( + new SparkDataset(crossJoin, getRoleMap(identifiers)), datasetExpression); + } + + @Override + public DatasetExpression executeValidateDPruleset( + DataPointRuleset dpr, + DatasetExpression dataset, + String output, + Positioned pos, + List toDrop) { + SparkDataset sparkDataset = asSparkDataset(dataset); + Dataset ds = sparkDataset.getSparkDataset(); + Dataset renamedDs = rename(ds, dpr.getAlias()); + + SparkDataset sparkDs = new SparkDataset(renamedDs); + DatasetExpression sparkDsExpr = new SparkDatasetExpression(sparkDs, pos); + Structured.DataStructure dataStructure = sparkDs.getDataStructure(); + + var roleMap = getRoleMap(sparkDataset); + roleMap.put(RULEID, IDENTIFIER); + roleMap.put(BOOLVAR, MEASURE); + roleMap.put(ERRORLEVEL, MEASURE); + roleMap.put(ERRORCODE, MEASURE); + + Class errorCodeType = dpr.getErrorCodeType(); + Class errorLevelType = dpr.getErrorLevelType(); + + List datasetsExpression = + dpr.getRules().stream() + .map( + rule -> { + String ruleName = rule.getName(); + ResolvableExpression ruleIdExpression = + ResolvableExpression.withType(String.class) + .withPosition(pos) + .using(context -> ruleName); + + ResolvableExpression antecedentExpression = + rule.getBuildAntecedentExpression(dataStructure); + ResolvableExpression consequentExpression = + rule.getBuildConsequentExpression(dataStructure); + + ResolvableExpression errorCodeExpr = rule.getErrorCodeExpression(); + ResolvableExpression errorCodeExpression = + ResolvableExpression.withType(errorCodeType) + .withPosition(pos) + .using( + context -> { + if (errorCodeExpr == null) return null; + Map mapContext = (Map) context; + Object erCode = errorCodeExpr.resolve(mapContext); + if (erCode == null) return null; + Boolean antecedentValue = + (Boolean) antecedentExpression.resolve(mapContext); + Boolean consequentValue = + (Boolean) consequentExpression.resolve(mapContext); + return Boolean.TRUE.equals(antecedentValue) + && Boolean.FALSE.equals(consequentValue) + ? errorCodeType.cast(erCode) + : null; + }); + + ResolvableExpression errorLevelExpr = rule.getErrorLevelExpression(); + ResolvableExpression errorLevelExpression = + ResolvableExpression.withType(errorLevelType) + .withPosition(pos) + .using( + context -> { + if (errorLevelExpr == null) return null; + Map mapContext = (Map) context; + Object erLevel = errorLevelExpr.resolve(mapContext); + if (erLevel == null) return null; + Boolean antecedentValue = + (Boolean) antecedentExpression.resolve(mapContext); + Boolean consequentValue = + (Boolean) consequentExpression.resolve(mapContext); + return Boolean.TRUE.equals(antecedentValue) + && Boolean.FALSE.equals(consequentValue) + ? errorLevelType.cast(erLevel) + : null; + }); + + ResolvableExpression BOOLVARExpression = + ResolvableExpression.withType(Boolean.class) + .withPosition(pos) + .using( + context -> { + Boolean antecedentValue = + (Boolean) antecedentExpression.resolve(context); + Boolean consequentValue = + (Boolean) consequentExpression.resolve(context); + if (antecedentValue == null) return consequentValue; + if (consequentValue == null) return antecedentValue; + return !antecedentValue || consequentValue; + }); + + Map resolvableExpressions = new HashMap<>(); + resolvableExpressions.put(RULEID, ruleIdExpression); + resolvableExpressions.put(BOOLVAR, BOOLVARExpression); + resolvableExpressions.put(ERRORLEVEL, errorLevelExpression); + resolvableExpressions.put(ERRORCODE, errorCodeExpression); + // do we need to use execute executeCalcInterpreted too? + return executeCalc(sparkDsExpr, resolvableExpressions, roleMap, Map.of()); + }) + .collect(Collectors.toList()); + + Dataset invertRenamedSparkDs = + rename( + asSparkDataset(executeUnion(datasetsExpression)).getSparkDataset(), + invertMap(dpr.getAlias())); + SparkDatasetExpression sparkDatasetExpression = + new SparkDatasetExpression(new SparkDataset(invertRenamedSparkDs), pos); + List toKeep = + sparkDatasetExpression.getColumnNames().stream() + .filter(v -> !toDrop.contains(v)) + .collect(Collectors.toList()); + DatasetExpression cleanedExpression = executeProject(sparkDatasetExpression, toKeep); + if (output == null || output.equals(ValidationOutput.INVALID.value)) { + ResolvableExpression defaultExpression = + ResolvableExpression.withType(Boolean.class).withPosition(pos).using(c -> null); + DatasetExpression filteredDataset = + executeFilter(cleanedExpression, defaultExpression, BOOLVAR + " = false"); + Dataset result = asSparkDataset(filteredDataset).getSparkDataset().drop(BOOLVAR); + return new SparkDatasetExpression(new SparkDataset(result), pos); + } + return cleanedExpression; + } + + @Override + public DatasetExpression executeValidationSimple( + DatasetExpression dsExpr, + ResolvableExpression errorCodeExpr, + ResolvableExpression errorLevelExpr, + DatasetExpression imbalanceExpr, + String output, + Positioned pos) { + // Rename imbalance single measure to imbalance + SparkDataset sparkImbalanceDataset = asSparkDataset(imbalanceExpr); + Dataset sparkImbalanceDatasetRow = sparkImbalanceDataset.getSparkDataset(); + String imbalanceMonomeasureName = + imbalanceExpr.getDataStructure().values().stream() + .filter(Component::isMeasure) + .map(Component::getName) + .collect(Collectors.toList()) + .get(0); + Map varsToRename = + Map.ofEntries(Map.entry(imbalanceMonomeasureName, IMBALANCE)); + Dataset renamed = rename(sparkImbalanceDatasetRow, varsToRename); + var imbalanceRoleMap = getRoleMap(sparkImbalanceDataset); + SparkDatasetExpression imbalanceRenamedExpr = + new SparkDatasetExpression(new SparkDataset(renamed, imbalanceRoleMap), pos); + // Join expr ds & imbalance ds + Map datasetExpressions = + Map.ofEntries( + Map.entry("dsExpr", dsExpr), Map.entry("imbalanceExpr", imbalanceRenamedExpr)); + List components = + dsExpr.getDataStructure().values().stream() + .filter(Component::isIdentifier) + .collect(Collectors.toList()); + DatasetExpression datasetExpression = executeLeftJoin(datasetExpressions, components); + SparkDataset sparkDataset = asSparkDataset(datasetExpression); + Dataset ds = sparkDataset.getSparkDataset(); + + // TODO: Extract to a ValidationExpression(ResolvableExpression). + Class errorCodeType = errorCodeExpr == null ? String.class : errorCodeExpr.getType(); + ResolvableExpression errorCodeExpression = + ResolvableExpression.withType(errorCodeType) + .withPosition(pos) + .using( + context -> { + Map contextMap = (Map) context; + if (errorCodeExpr == null) return null; + Object erCode = errorCodeExpr.resolve(contextMap); + Boolean boolVar = (Boolean) contextMap.get(BOOLVAR); + return boolVar ? null : errorCodeType.cast(erCode); + }); + // TODO: Extract to a ValidationExpression(ResolvableExpression). + Class errorLevelType = errorLevelExpr == null ? String.class : errorLevelExpr.getType(); + ResolvableExpression errorLevelExpression = + ResolvableExpression.withType(errorLevelType) + .withPosition(pos) + .using( + context -> { + Map contextMap = (Map) context; + if (errorLevelExpr == null) return null; + Object erLevel = errorLevelExpr.resolve(contextMap); + Boolean boolVar = (Boolean) contextMap.get(BOOLVAR); + return boolVar ? null : errorLevelType.cast(erLevel); + }); + + var roleMap = getRoleMap(sparkDataset); + roleMap.put(ERRORLEVEL, MEASURE); + roleMap.put(ERRORCODE, MEASURE); + + Map resolvableExpressions = + Map.ofEntries( + Map.entry(ERRORLEVEL, errorLevelExpression), Map.entry(ERRORCODE, errorCodeExpression)); + + Dataset calculatedDataset = executeCalcEvaluated(ds, resolvableExpressions); + DatasetExpression sparkDatasetExpression = + new SparkDatasetExpression(new SparkDataset(calculatedDataset, roleMap), pos); + + // handle output: if none or all, return, if invalid filter on bool_var and return + if (output == null || output.equals(ValidationOutput.ALL.value)) { + return sparkDatasetExpression; + } + DatasetExpression filteredDataset = + executeFilter( + sparkDatasetExpression, + ResolvableExpression.withType(Boolean.class).withPosition(pos).using(c -> null), + BOOLVAR + " = false"); + // VTL issue: drop BOOLVAR in check_datapoint only specified but we apply also here for + // harmonization + Dataset result = asSparkDataset(filteredDataset).getSparkDataset().drop(BOOLVAR); + return new SparkDatasetExpression(new SparkDataset(result), pos); + } + + @Override + public DatasetExpression executeHierarchicalValidation( + DatasetExpression dsE, + HierarchicalRuleset hr, + String componentID, + String validationMode, + String inputMode, + String validationOutput, + Positioned pos) { + // inputMode: dataset (default) | dataset_priority (not handled) + if (inputMode != null && inputMode.equals("dataset_priority")) { + throw new UnsupportedOperationException( + "dataset_priority input mode is not supported in check_hierarchy"); + } + + // Create "bindings" (componentID column values) + fr.insee.vtl.model.Dataset ds = dsE.resolve(Map.of()); + + Map bindings = + ds.getDataAsMap().stream() + .collect( + HashMap::new, + (acc, dp) -> acc.put(dp.get(componentID).toString(), dp.get(hr.getVariable())), + HashMap::putAll); + // Save monomeasure type + Component measure = dsE.getDataStructure().getMeasures().get(0); + Class measureType = measure.getType(); + + var roleMap = getRoleMap(ds); + roleMap.put(RULEID, IDENTIFIER); + roleMap.put(BOOLVAR, MEASURE); + roleMap.put(IMBALANCE, MEASURE); + roleMap.put(ERRORLEVEL, MEASURE); + roleMap.put(ERRORCODE, MEASURE); + + Class errorCodeType = hr.getErrorCodeType(); + Class errorLevelType = hr.getErrorLevelType(); + + List datasetsExpression = new ArrayList<>(); + hr.getRules() + .forEach( + rule -> { + DatasetExpression filteredDataset; + try { + filteredDataset = + executeFilterForHR( + dsE, componentID + " = \"" + rule.getValueDomainValue() + "\""); + } catch (Exception e) { + throw new RuntimeException(e); + } + + String ruleName = rule.getName(); + List codeItems = rule.getCodeItems(); + + // handle validationMode + Map ruleBindings = extractHRRuleBindings(bindings, codeItems); + Boolean hasToProduceOutputLine = checkRule(codeItems, ruleBindings, validationMode); + if (Boolean.FALSE.equals(hasToProduceOutputLine)) { + // trick to break + return; + } + ruleBindings = + buildBindingsWithDefault(ruleBindings, codeItems, validationMode, measureType); + // Iterate on rules to resolve expressions + Map resolvedRuleExpressions = new HashMap<>(); + Map resolvedLeftRuleExpressions = new HashMap<>(); + Map resolvedRightRuleExpressions = new HashMap<>(); + // use try / catch because of scalar expr function resolution issue with null (only + // handled in ds thanks to column type) + try { + resolvedRuleExpressions.put( + ruleName, (Boolean) rule.getExpression().resolve(ruleBindings)); + } catch (Exception e) { + resolvedRuleExpressions.put(ruleName, null); + } + try { + resolvedLeftRuleExpressions.put( + ruleName, (Double) rule.getLeftExpression().resolve(ruleBindings)); + } catch (Exception e) { + resolvedLeftRuleExpressions.put(ruleName, null); + } + try { + resolvedRightRuleExpressions.put( + ruleName, (Double) rule.getRightExpression().resolve(ruleBindings)); + } catch (Exception e) { + resolvedRightRuleExpressions.put(ruleName, null); + } + ResolvableExpression ruleIdExpression = + ResolvableExpression.withType(String.class) + .withPosition(pos) + .using(context -> ruleName); + + String vd = rule.getValueDomainValue(); + ResolvableExpression valueDomainExpression = + ResolvableExpression.withType(String.class) + .withPosition(pos) + .using(context -> vd); + + Boolean expression = resolvedRuleExpressions.get(ruleName); + + ResolvableExpression errorCodeExpr = rule.getErrorCodeExpression(); + ResolvableExpression errorCodeExpression = + ResolvableExpression.withType(errorCodeType) + .withPosition(pos) + .using( + context -> { + if (errorCodeExpr == null || expression == null) return null; + Map mapContext = (Map) context; + Object erCode = errorCodeExpr.resolve(mapContext); + if (erCode == null) return null; + return expression.equals(Boolean.FALSE) + ? errorCodeType.cast(erCode) + : null; + }); + + ResolvableExpression errorLevelExpr = rule.getErrorLevelExpression(); + ResolvableExpression errorLevelExpression = + ResolvableExpression.withType(errorLevelType) + .withPosition(pos) + .using( + context -> { + if (errorLevelExpr == null || expression == null) return null; + Map mapContext = (Map) context; + Object erLevel = errorLevelExpr.resolve(mapContext); + if (erLevel == null) return null; + return expression.equals(Boolean.FALSE) + ? errorLevelType.cast(erLevel) + : null; + }); + + ResolvableExpression BoolvarExpression = + ResolvableExpression.withType(Boolean.class) + .withPosition(pos) + .using(context -> expression); + + ResolvableExpression imbalanceExpression = + ResolvableExpression.withType(measureType) + .withPosition(pos) + .using( + context -> { + Double leftExpression = resolvedLeftRuleExpressions.get(ruleName); + Double rightExpression = resolvedRightRuleExpressions.get(ruleName); + if (leftExpression == null || rightExpression == null) { + return null; + } + if (measureType.isAssignableFrom(Long.class)) { + return leftExpression.longValue() - rightExpression.longValue(); + } + return leftExpression - rightExpression; + }); + + Map resolvableExpressions = new HashMap<>(); + resolvableExpressions.put(RULEID, ruleIdExpression); + resolvableExpressions.put(componentID, valueDomainExpression); + resolvableExpressions.put(BOOLVAR, BoolvarExpression); + resolvableExpressions.put(IMBALANCE, imbalanceExpression); + resolvableExpressions.put(ERRORLEVEL, errorLevelExpression); + resolvableExpressions.put(ERRORCODE, errorCodeExpression); + + datasetsExpression.add( + executeCalc(filteredDataset, resolvableExpressions, roleMap, Map.of())); + }); + DatasetExpression datasetExpression; + if (datasetsExpression.size() == 0) { + InMemoryDataset emptyCHDataset = + new InMemoryDataset( + List.of(), + Map.of( + measure.getName(), + measureType, + RULEID, + String.class, + componentID, + String.class, + BOOLVAR, + Boolean.class, + IMBALANCE, + Double.class, + ERRORLEVEL, + errorLevelType, + ERRORCODE, + errorCodeType), + roleMap); + datasetExpression = DatasetExpression.of(emptyCHDataset, pos); + } else { + datasetExpression = executeUnion(datasetsExpression); + } + // validationOutput invalid (default) | all | all_measures + if (null == validationOutput || validationOutput.equals("invalid")) { + DatasetExpression filteredDataset = + executeFilter( + datasetExpression, + ResolvableExpression.withType(Boolean.class).withPosition(pos).using(c -> null), + BOOLVAR + " = false"); + Dataset result = asSparkDataset(filteredDataset).getSparkDataset().drop(BOOLVAR); + return new SparkDatasetExpression(new SparkDataset(result), pos); + } + if (validationOutput.equals("all")) { + String measureName = measure.getName(); + Dataset result = asSparkDataset(datasetExpression).getSparkDataset().drop(measureName); + return new SparkDatasetExpression(new SparkDataset(result), pos); + } + // all_measures + return datasetExpression; + } + + private DatasetExpression executeFilterForHR(DatasetExpression expression, String filterText) + throws Exception { + SparkDataset dataset = asSparkDataset(expression); + Dataset ds = dataset.getSparkDataset(); + try { + Dataset result = ds.filter(filterText); + if (result.isEmpty()) { + result = ds.limit(1); + } + return new SparkDatasetExpression(new SparkDataset(result, getRoleMap(dataset)), expression); + } catch (Exception e) { + throw new Exception(e); + } + } + + private Map extractHRRuleBindings( + Map bindings, List items) { + Map ruleBindings = new HashMap<>(); + items.forEach( + k -> { + if (bindings.containsKey(k)) { + Object value = bindings.get(k); + ruleBindings.put(k, value); + } + }); + return ruleBindings; + } + + private Boolean checkRule( + List codeItems, Map ruleBindings, String validationMode) { + if (validationMode == null || validationMode.equals(NON_NULL)) { + if (codeItems.size() != ruleBindings.size()) { + return Boolean.FALSE; + } + if (ruleBindings.values().stream().noneMatch(Objects::isNull)) { + return Boolean.TRUE; + } + return Boolean.FALSE; + } + if (validationMode.equals(NON_ZERO)) { + if (ruleBindings.values().stream() + .noneMatch( + r -> { + if (null == r) { + return Boolean.TRUE; + } + Double d = null; + if (r.getClass().isAssignableFrom(Long.class)) { + d = ((Long) r).doubleValue(); + } + if (r.getClass().isAssignableFrom(Double.class)) { + d = (Double) r; + } + if (d.equals(0D)) { + return Boolean.FALSE; + } + return Boolean.TRUE; + })) { + return Boolean.FALSE; + } + return Boolean.TRUE; + } + if (validationMode.equals(PARTIAL_NULL) || validationMode.equals(PARTIAL_ZERO)) { + if (ruleBindings.values().stream().filter(Objects::nonNull).count() > 0) { + return Boolean.TRUE; + } + return Boolean.FALSE; + } + if (validationMode.equals(ALWAYS_NULL) || validationMode.equals(ALWAYS_ZERO)) { + return Boolean.TRUE; + } + return Boolean.FALSE; + } + + private Map buildBindingsWithDefault( + Map bindings, + List ruleItems, + String validationMode, + Class measureType) { + Map bindingsWithDefault = new HashMap<>(); + ruleItems.forEach( + i -> { + if (bindings.containsKey(i)) { + bindingsWithDefault.put(i, bindings.get(i)); + } else { + // don't need to handle non_null, items are always in bindings + if (List.of(NON_ZERO, PARTIAL_ZERO, ALWAYS_ZERO).contains(validationMode)) { + if (measureType.isAssignableFrom(Long.class)) { + bindingsWithDefault.put(i, 0L); + } else bindingsWithDefault.put(i, 0D); + } + if (List.of(PARTIAL_NULL, ALWAYS_NULL).contains(validationMode)) { + bindingsWithDefault.put(i, null); + } + } + }); + return bindingsWithDefault; + } + + private Map invertMap(Map map) { + return map.entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey)); + } + + private List> toAliasedDatasets(Map datasets) { + List> sparkDatasets = new ArrayList<>(); + for (Map.Entry dataset : datasets.entrySet()) { + var sparkDataset = asSparkDataset(dataset.getValue()).getSparkDataset().as(dataset.getKey()); + sparkDatasets.add(sparkDataset); + } + return sparkDatasets; + } + + /** + * Utility method used for the implementation of the different types of join operations. + * + * @param sparkDatasets a list datasets. + * @param identifiers the list of identifiers to join on. + * @param type the type of join operation. + * @return The dataset resulting from the join operation. + */ + public Dataset executeJoin( + List> sparkDatasets, List identifiers, String type) { + var iterator = sparkDatasets.iterator(); + var result = iterator.next(); + while (iterator.hasNext()) { + if (type.equals("cross")) result = result.crossJoin(iterator.next()); + else + result = result.join(iterator.next(), iterableAsScalaIterable(identifiers).toSeq(), type); + } + return result; + } + + /** + * Execute pivot on dataset expression. + * + * @param dsExpr dataset expression + * @param idName identifier name + * @param meName measure name + * @param pos script error position + * @return the result of the pivot + */ + public DatasetExpression executePivot( + DatasetExpression dsExpr, String idName, String meName, Positioned pos) { + + Dataset sparkDataset = asSparkDataset(dsExpr).getSparkDataset(); + + List groupByIdentifiers = new ArrayList<>(dsExpr.getIdentifierNames()); + groupByIdentifiers.remove(idName); + + Column[] groupByCols = + groupByIdentifiers.stream().map(SparkUtils::safeCol).toArray(Column[]::new); + + // TODO: fail if any values needs to be aggregated + Dataset result = + sparkDataset + .groupBy(groupByCols) + .pivot(SparkUtils.safeCol(idName)) + .agg(functions.first(meName)); + + return new SparkDatasetExpression(new SparkDataset(result), pos); + } + + /** + * The Factory class is an implementation of a VTL engine factory that returns Spark + * engines. + */ + public static class Factory implements ProcessingEngineFactory { + + private static final String SPARK_SESSION = "$vtl.spark.session"; + + @Override + public String getName() { + return "spark4"; + } + + @Override + public ProcessingEngine getProcessingEngine(ScriptEngine engine) { + // Try to find the session in the script engine. + var session = engine.get(SPARK_SESSION); + if (session != null) { + if (session instanceof SparkSession sparkSession) { + return new SparkProcessingEngine(sparkSession); + } else { + throw new IllegalArgumentException(SPARK_SESSION + " was not a spark session"); + } + } else { + var activeSession = SparkSession.active(); + if (activeSession != null) { + return new SparkProcessingEngine(activeSession); + } else { + throw new IllegalArgumentException("no active spark session"); + } + } + } + } +} diff --git a/vtl-spark4/src/main/java/fr/insee/vtl/spark4/SparkRowMap.java b/vtl-spark4/src/main/java/fr/insee/vtl/spark4/SparkRowMap.java new file mode 100644 index 000000000..64d01c316 --- /dev/null +++ b/vtl-spark4/src/main/java/fr/insee/vtl/spark4/SparkRowMap.java @@ -0,0 +1,81 @@ +package fr.insee.vtl.spark; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import org.apache.spark.sql.Row; + +/** The SparkRowMap class represents a row in a Spark dataset as a map. */ +class SparkRowMap implements Map { + + private final Row row; + + /** + * Constructor taking a Spark {@link Row}. + * + * @param row the row of the Spark dataset. + */ + public SparkRowMap(Row row) { + this.row = row; + } + + @Override + public int size() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isEmpty() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean containsKey(Object key) { + return row.fieldIndex((String) key) > -1; + } + + @Override + public boolean containsValue(Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public Object get(Object key) { + return row.get(row.fieldIndex((String) key)); + } + + @Override + public Object put(String key, Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public Object remove(Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public void putAll(Map m) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public Set keySet() { + throw new UnsupportedOperationException(); + } + + @Override + public Collection values() { + throw new UnsupportedOperationException(); + } + + @Override + public Set> entrySet() { + throw new UnsupportedOperationException(); + } +} diff --git a/vtl-spark4/src/main/java/fr/insee/vtl/spark4/SparkUtils.java b/vtl-spark4/src/main/java/fr/insee/vtl/spark4/SparkUtils.java new file mode 100644 index 000000000..df7643ab2 --- /dev/null +++ b/vtl-spark4/src/main/java/fr/insee/vtl/spark4/SparkUtils.java @@ -0,0 +1,24 @@ +package fr.insee.vtl.spark; + +import static scala.collection.JavaConverters.iterableAsScalaIterable; + +import java.util.Collection; +import org.apache.spark.sql.Column; +import org.apache.spark.sql.functions; +import scala.collection.Seq; + +public final class SparkUtils { + + private SparkUtils() {} + + public static Column safeCol(String name) { + if (name == null) { + throw new IllegalArgumentException("Column name cannot be null"); + } + return functions.col("`" + name + "`"); + } + + public static Seq safeCols(Collection names) { + return iterableAsScalaIterable(names.stream().map(SparkUtils::safeCol).toList()).toSeq(); + } +} diff --git a/vtl-spark4/src/main/java/fr/insee/vtl/spark4/package-info.java b/vtl-spark4/src/main/java/fr/insee/vtl/spark4/package-info.java new file mode 100644 index 000000000..ef61fb20b --- /dev/null +++ b/vtl-spark4/src/main/java/fr/insee/vtl/spark4/package-info.java @@ -0,0 +1,2 @@ +/** This package contains the Spark VTL engine. */ +package fr.insee.vtl.spark4; diff --git a/vtl-spark4/src/main/resources/META-INF/services/fr.insee.vtl.model.ProcessingEngineFactory b/vtl-spark4/src/main/resources/META-INF/services/fr.insee.vtl.model.ProcessingEngineFactory new file mode 100644 index 000000000..5466b1492 --- /dev/null +++ b/vtl-spark4/src/main/resources/META-INF/services/fr.insee.vtl.model.ProcessingEngineFactory @@ -0,0 +1 @@ +fr.insee.vtl.spark.SparkProcessingEngine$Factory \ No newline at end of file diff --git a/vtl-spark4/src/main/resources/c_h.csv b/vtl-spark4/src/main/resources/c_h.csv new file mode 100644 index 000000000..c1b2a0091 --- /dev/null +++ b/vtl-spark4/src/main/resources/c_h.csv @@ -0,0 +1,11 @@ +"id";"Me" +"ABC";"12" +"A";"1" +"B";"10" +"C";"1" +"DEF";"100" +"E";"99" +"F";"1" +"HIJ";"100" +"H";"99" +"I";"0" diff --git a/vtl-spark4/src/main/resources/ds1.csv b/vtl-spark4/src/main/resources/ds1.csv new file mode 100644 index 000000000..efaa125f5 --- /dev/null +++ b/vtl-spark4/src/main/resources/ds1.csv @@ -0,0 +1,11 @@ +id;long1;string1;bool1;double1 +1;100;aa;true;0.1 +2;250;bb;true;10.59786 +3;1;cc;true;0 +4;;dd;false;1.2 +5;50;ee;false; +6;40;ff;true;56 +7;79;gg;true;5.3216004 +8;100;hh;false;0 +9;-2;ii;true;12 +10;0;jjjjjjjj;true;-5 diff --git a/vtl-spark4/src/main/resources/ds2.csv b/vtl-spark4/src/main/resources/ds2.csv new file mode 100644 index 000000000..a7530f6bc --- /dev/null +++ b/vtl-spark4/src/main/resources/ds2.csv @@ -0,0 +1,6 @@ +id;long1;string1;bool1;double1 +1;200;aaaaaa;false;0.11 +2;-250;bbbbbb;true;11.11999 +3;1;c;true;0 +4;10;ddd;false;-1.2 +5;56;eeeeeeee;false; diff --git a/vtl-spark4/src/main/resources/input_sample/sample.parquet b/vtl-spark4/src/main/resources/input_sample/sample.parquet new file mode 100644 index 0000000000000000000000000000000000000000..d74f91c19b97d2b5883a9b032b3913f8ccce13ba GIT binary patch literal 1030452 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

NHupB!E_yVb>lIRW3EdTbCk|Kl;H3tE{|W` z{2U>8p@>hL$fHpGaC(2KT$YjvgBB=GSSLi1vD2SaD9g`Z)Y*#D=jN5OCqS?>7m^5CS?W*{H@mmU`j4Kb8iQ=xu(s}3 z`uI-4o)5mJ6@m^luV*VR~Gdf9j>PIISA1IhqE2@C*>5icem7? zpRuQOd=o^brS-$AO7(2>FGk{_F8uL+a8?@8o-gL=nb65}jF< zlQq}N;DHcbQT?Dv2$=G7#zpp}wJKTPv+pWRdyucjZ*}4zMcBui6u$_~F{0pG`~U*^ zD=0R!h~P$Yrx+73FUMf@*R|zK;;6yPPAyM2bSvqHWC1CM@?tBw=mp=}GS_?pB5+j|L7)Vp5zLt8E_z1m2^(*tj7UP`5BZq_hr2v1X|pleuKPbr;mc zKe05Aex0*oK^YmuLuIxte5@?ojf3LPSmYpu6ju)0hrJRoLIn?B@Y;g1{+Aqc>3Sk3 z;WQI<PyI+PSm4@Xa8sEJ`JJEp1z75iSot6OnURq7`;*UGUw)agSQB*6mPx> zb97pnG0!c5V;0Veq>X$}3Vn+md8GJ~BOJ`@B5;2E>tGcd9pz1NrerFzcpNiewaJ^) zmR--(|2Dlx>}~Ukx&dt8RPFzcbD_)qv+M7FVQVOkvftFb%=WVQJr2F~&j(BkA_8zq ze*Um4a%>9I-uE-~`X$DKr;NP?pS~47rPE@V#g*x{IdBUbIW@k}5QXYSAvL zB2{>osgkQuT~v#9Q593QpXVsGe+WeP`SJdKpZ9sb&-Xhr8bH_pTnj>E`5>jj&LsMT zClPAgL}d^Ss*B5;K)Eyk|K!j!%rhVDNDT-uU6sR#lrjWqnWwD%{!@UGa9`%{N;3L1 zd2l_@6b^&MniGH*_S(4#zSR6OFz-Q81aSfykGSL^8*2oxl#Hhrm0 zzKgzOPAdyha&xG2s~{@y{cz@hevJvXO2y)>f;$|)2NH-YbY6h4g|w&8>&CFCN8w|F zKaH%`7)gb-^-x@-wc!r~6ZxU!)H|Q-P?imVU!ds4xb)8-=zF({(Ms?b#DNh*5%K_s+!=&qKLSlJjtl<(Y8tPAt1$5V>QyI z&}|V;)~g2@2BH6Y0qO{WyN7jYTRhxWN&ZnQsnyV*CbaGURFZJlm9nt7Bd7*~$h3cP zoEsARX;lD9F)exqJIx~w;JU)62Hu)?f!Tn>PU=T~Eb&b_7jWtmmiQ)i?G-^T17VUd z-WVrN4sixBd3+We?xEht3;8G-80t_OZqST@`~hYqYQUph+6K0Y2}Zbf^%ypwMYbgm zpT)s;V-4C%HZkO7I15(xC~M7XzM`ZJu2)MN@p)h0OFU&*Kt!8+*m;4kCIY%5tx6h} z9~{ScJh|65mbIl{bC8Dbh|T`8&>aeuQViSe_dmQgA$BR$Qi=0rIHOdkM0>QB?_v6v zm2>#msAO}3lu0H^wNT$JRS40E1=E=n?@9!UVf=EYcfLj0X)rO|U9G#8ToG1?D$Z&t z)Pvc8w_nCH4QT^mYPcXmxj3G3^|KZ1oA=M$X*yl6^z_Ou{#sd`vK<*O$74mjz?tW}o z=M}cjhd&kv@MW1@j;nUKKxY>I3l;4g4^HWY!}u?}Yq&gdRDv$06WhymJildMx0e%rk^8kBvz)=sDLN6H>WS!RYE9xy+ zZou)4YeBf2=8GNxhbwFs(#MtaxCnhf6?E0g5BS7&0iO^@KgKh2kEju$jxvzn0JZre z08Yd3gO;|^*QJ7MBhBN`>2c~h_Wzhf22Ii`N^)25@6g6Llr{fdf=1j91#*m-9AyD7 z*#l@XSa~|jc+JD3JY^R^s+_ki2Ro4cC{8wUw;m}K2TxOy@xG{YipU(3r|3Mz5P%U0 z5C*h4m_hktviAzcvI$ZH=4 zRgK3yUOPpjPY#sCE^jC^HIT%R#uJ(> zZk{MZ`+7U9jFdzCmAYNW=L_B9PrF=D1}@*rDN+4qmjL@n)SwxRV|``XqVVy_>qK+O zA_45UGPnS*d(NJQSI!=fkI+`3QJ`owMMaUhO{PUa+A|607iUR(bQ zVn{wK5~Xh8Sa$v)kF%2XW%QHZ0jziIost2Va+J+~zBi*RHW77J{)dGlkpLS+WZ(*m zxt9G4t|=~8F8qi{vVR`xKPB>-S{zrp!g5Ff#ww^l^VFP{PheM%!-kh^Eo1hxjmTtY z&icohIt3C1Zj}jFwcDccgj@MX@j+T=WNn`(YlzLr5G>y$SxC7FlRF>!Z zM-s($t~`c}m{TsVbDb~mi@~2}rw+}BUH45M;aN@-&m|{ARC*nRF2izRx_Mth{!Aar zp{tXZyKDn}NCYZfAeg9PqCaS-QKTi&!Z;Ek6`Ec9R6TJI6H!se_mEOGEVO4)sIfeG zpO?B&J}hc^<&$NYlx~X@3@wB1GA{7X@(Rv3CPF1CJ9u6nCdh%*!UKH}S=X-w8QTX=-HNeje@ zsD?>>NLW}!M8`7VnF67M6eo_Iw}FG&_PHs(puRbbdXnt@$^uUZ=>q@{l)$w7VVm9Z zD-g3oHu2oD{FSOij1EE7S$>+8tNgU(YTNP4S;HOw!ZI?_fwZ%06FCffX`7OGX_0|U z@zW4R-O`?@31O0F9VPE8WbT_O<4<%S*k3|c={yu90HgSq*@un8?tr?f=YFZSlf~NF zwN67pFpNt|ew zqd$BdPRckl#bZ(?EksV`QNL)IYmau8>|(-!zwj=u!d@CHAtqL71b@og5D%4Ru*@@# z6zS{&rR879e20-4=6~3^(*DLX-j7 zCnRQ(s@vsYf0i=`CVE8v1)31v-MloC>yz{ALP0Kr><|OO)UNFwv;wyIh8Q71nN4$k2L4`l>G3`LtA0rv_6kTXL=^AiPO>dihYEb~&Nff^cuvY@rSBFRw;AH-0D%RND?N9I zX0^^&Noch>obn9@07|=Z9Uz`e1D4%M`iSq4=sRD>Cl`A8FDiaQYk+;e6mn5$7FlI% z(KIcN<;R{vC{kM2OiwVreVX01E;y9r9m3WSLZzS%7xfT?tR0%`P)``uZ7}V=?cEwd z%h~<{;R@D!oQAAi6wE~gfC~ZRkmV(7lFT^Ewazb4B-9U)f(ApBGB^{1@SiLt$v%B$ zK(@B@BBmmcFOfLKW;Oy%3|Y<${-EZNF94gM3~l5+U+7LT|$&kTFunbY3<`{{R zEI;cR0ce+|O!^x}!XGED5P~*-A7TmQU?WjNqGK<6fSMDHF0H{B=j%@Ta?f7;hpN{Ft{42$r9!VercG@ z5xU;ZvRfw?Xc-$J5*{e<22R$5cwzs;O=vmSr3x%^PQx0E+_&^O}r^^*YE$q2w@ z!Z}AgOqQr~@cFiUo@|v5(EegaO-sT9I6dYUV~dHIy46CLKy&E(PqVBVXw*{y>;oIk zDI`akvTUk`ci|y0IL->Lkp!V=y6OA4{awgp*owo_0`+^M7Bhn7zo!gYUJU6G@^je! z%)1*P#u^mj$gK0&gZ2sHuk6hqkYUGun1Xwl@-;4e5Rwzo2|298l}#EMW^Fgl4`RgB zSO%!=!?KOIl8okA6dBnNIe_iMf`sX}93$TrZUAt57fB-HZlrOBB?+EqP%FQ2?PxP^ zw(O%jaxa$AE&v1MhnO>Gg@oOMAskl^`k_!akiiNAGR)L4lq1DNuN>WxG6A9>$@Q+V zNx3ko)lP>j7ymiw(yMw zBO=g+3&o=A%c__{oP)MyKjaOM=5Ww@pJcI5U8u`gGGrNZKk2TmznI}qL`8s=hEk7) zc;*z>8lUw(OM*Pa_GDbli_c5?XG?OxOjMw_n9UU)lBLohLA(yf42+?^5Eimi{}=|K zcyte686xl~eqZ8tyf>cRM-RhuYMp1Qq#ofj63REBntz1DO8&74wQ#8?f~TL&vR~tT z@Ggw=L)34?!`_Vav;S!qMKR*b9KD0>)j^OIj}g0u2&ZHTrLxp5gRK395htJmgq;RPHrrE4Ttt#Zx&U7uczFPK8#hSuQXDiO~J=1`8hPK9e*p~r^?OG1&}-2u@|37{$=2a0|=5%4Rg ze%AlSWk!_RD|nK08jIJvObi9#WX!I#`^AtPfCSn=D6kxG3=1;NbO)wvxK5r9af+u}a92U(K1w?a(8;R5oh0?&Qm@5{db*>jE*@AvYZEO7CKi5NuNI`H^ z%3{m%%fXz?TcKaH(rhe_*cvViI2_9lSvXNX1>8j0wkl-el8;(XPYVmS1wjzLtaw$lw*EeY&JZ146d||R z{qcM3VfqXnk$DdvH#Wd7!n!<_kzqfOoEX_#EJDOsF;_BlFKYq0fQY&l=|>WW1gSjK#Ak zn`z%(!1a}`=dhl-jFkZDX@BkLZ>B=-1ICjH3PBcnwmViODGr&gsL0SGEiwtA;w0hj z&~BKGL~=FB+YAH`Jz*twQV(|7=tVGOH)Nk&DP~tsdl#x?=G>nbV%EZ8XvktU)GURw z772`RRE(4hR3MJ4B>{YN5&Oh~6oYY@+`uc~NNFAHcz7a!Aco9eZekm$57{k!1Q^PH`Z|WCkrC{Ail>&=Bc1Bx?y{_)jNjq2!2yk#4{?01v<5gx2T`uV_^b zNJjc(odV$`TW#=Tfvm5)!<_84S)ABx_Dynuw{Mbrlw5^cz*iTt&v5J$tQ#0|=oUO> zqDqwZ*i}%=;3vGFF$r5nSmeuWq6;W5AbRE@<@ihq*!XOmFBzoykac$e-r{Lq0)H@@`^9f#mi@s0}n<2e4!d9lktM6vt%cWq@tc2@iz=2(DJ~ zjMgc1=CEu)uN8c>Kb6V6nKgpigGZxijQADupt%Y1Gwcskw(Ry7Q4m93nXN)k5H#i zk<_|$SZ=c2&z9vk$ER+-c0JrUDbu^wi8dt7yR~1w#O7r_;=>D8vr9elT^gOX$fZot z466sLjQ6M>n?2>JI1hQ(-KC)YUCPPFh*yrSSJ!(?j;~atn*)NvJTqkx_Ba~Tg)HFFwrvU)654k^F&-b zL72W~?*3AHz~;UBEd0lSLE-u+9c(;iufH@_(=57$W5Pr^!tH$mS!K$y6{Wvekzmoi z438&(B>cPH)kPWwZDJx#aF~*a#X3=7-_uru&F|`TO?7;wtbO3xP*I+h&am*KyV4?w z^eF$?d=8Gvv1X$=Wd zru0Vdy*4hjEQE>~uvaovv`;)uF#bf@-U0=o9x;|!=20S*A=X*O+C*JC%Wjf%fE*f& z9v(Hu+vP1Ks{(R2Tnu=+bq;P#GETTN!dwKZTq5#o(jy&$EvkP`$_h5RHVjc6Pn^(2 zLX#XeX{y+4l`9ySc znxB&}TP!dist=UR!^GKqTrEUC3KP3sN~j%CzJftr)G3|wgLE4)Ms!FS7Wo>$+l*kb zr6(gK`N8=xqh)1L15mZ6ouPGeO$s+9ZgQ+4Iel4rlNeKYi)chRoQJW9ZY@iYq#9ey zyTTCE*2_qhuum34#w?{o|HDDM*G#$TYt&_m}8^gcYB_y(RG zkZE_85P5`cX^QjM{k*cEJPs@Khi{SZiOLIXYE-&oZ&43!MZ*?61GNUfgEb9qtQVqf zISRHU+jANr#E58W!%={7IZ4Y$YXRoD4|*_f+##_ZpLP=rapEPDmAmwog%`%QE)PqH zxZxn-PpGh}Ai^Q@m_sbCCb&4omnoWZlbi#{DJCR%x&bYryrH5Rmj}C22MW}1u#Qvq zp#+oxffhW;$q(gg=Rym%y5!ofAHTN3x8%LbhA~tX%tL6Hwipiu>SWj!az!-HS~b3f zR7$|Sj(dFv1W$AyRmRu3sT5w&Cde>MrJiQRN!LeQaH2{M3iu!>gtW3vXhMQo11Dr0 zKERFc`}aAl5Kr#!);G)UWKi#w^^+(Y3~O3of;GjN6-`IUIV~%{F%{2MfDY#vOZBc- z9At#r(|`}H zf0H3Mc(67rTp!3xZ~&o2^jHLu*K6JQ$FGHztz)+yGH$9*f;uK^J;oZnMs9q_xUx$p z;7?=Zb(^f>iwA0_*~DN2n|Io*)q=x7act zFub+8=cVYJA%J{WA0QhMFE(r;;m-5K;S=}{XnGPs#Ck~r?qwH`RP^`7w?t9EE#iIC?;axGB}+r_kg}vZxq(VCD^y z?|LeTiP^@rA#i7$vwBz_hdX>T`S^d$d;J%Is^niwplvY zM5STQ;hF?QQ#y7nP|vEbf#FT?^5)oJ=q!ZJ4i#CE;y%u^W{9tI6HN{Qq%ZQ4DDl z8F%r97ipk|awYMqDB$AokPJeF4#^)j zwU*zh>%N?D2dRfV8;rpo`54$yMPt*Ufspm2yC`un6LFAd)9OfOdz8f(6CNpI5IA(( zH`FFjgA&w?Ll*6rO1|{dDtfIu{vq8{Y5 zHFPLB-=1CO#S!n>v(Is66`4?Z(2UUAl8Y0;(a&nr6=%4hD}bvz-djx|(7rgve{+=k z<~B!KJPOL+9~5Fe4xiXQrl_=Y=$d4yf%3y(XWA^2c7jut=1FH+i^WeJOJ~_kI5N;V zDLEc<2hBk_@2wVLp17Yju@vmc$~qop&q~n-8S=H*55lZwAq#!&*e4UT&nCrYdxcJt zUtNL&6rs7As8%Kl0yKx533udSrzj*yGDCxP0ERV5rGWuAg6p4Yx~Db_VK-d43^bt< zj9}1lAZ~hIdao;~!uBnt9OGL<@RmVECFskCyRs9IZOGXIsyVp?sS5n>g0&@2Pm+x= zJQGB)@UFtLF;7Mlp9wyjN{c)P4r2&tU*);5jtPPiFF=9%;E9IUTHa@gz@J<`wqKHX ziP7TDi`B4VhlFw|n(A z4{BZ3!yJld>Y^-KEz(zs7)(oJ@)q3r#t~FeDDi`s_;tstX(`Um!eqP}V;pSb%XpB$ zJ9m8)FOqoid$jq0o2G$Xdrf@fn=tP^nAgjEzJgf|y9X-+GTF_SC>#@qSfDGR$w{g| z#~>q2#*rOdD~{U-!}9-uN@{5y!g(fq_bdubm;fgVII*Dlq6lN1Ec|P3q=@VyXgR!o zqbuZI_jOcz8SNLa3tp!`Tsa{kmLpXieO<7H0uzQBFv7ZHdn{grO(_b#oZHKP_&+o0 zLKSb`0NIenXYGkp&uS?3U|8>KVA^dT42*OhoI{+=NhV6A$at!e34WO$Y^c!cWq56! z5|bl4s{p=q-zHX)@`%Mdrg;s6Uig^dRpERg?%6!c z%7}8hDLxpq!i740@%56-^+g@sqL?Pyo$U6$@X`4;#~H_du%stm!)~-5#Pq(`$#7Wy zGL35sx!ODvk5y|%!gw45rE%8R=x6+8b%M15d)h+yT;ao{q&!%9IAfL}G`)j@xq7#gGMYjUIt=CQ}(| zspu=Cli=o+$+t=RY@lNLv{Nqx=?llmY-DWCBD}&gou`3M1^>u2w{%25Z>I6VJEkRB zF~LX|@zDozkROPFlHZ7s2SSI)^IPj!Db_)utsfxVV2kU9p)bc=oP6fyfW~@KJqJy6y;sAYu!XHrF zT9odO0G)Bm$1s$-`KS%YWCU1-sV%0s5>s_vW6M^)U0I$`s+-g{lomtgRs4oZ_RMFv zH5!jT8s!n5b90>ANGPkpP#P*LX}h$`HKB^~HjYQ+Mah=YgqvIw9v~9d}_uOzh*iWGw8!#jk|kWP#@QLnKM|@Hq%#+#_z|0sP#Q#)*rE96L+U zAgmlg^=E9_)xJ4iv>P8gChmrGJzEx3uv^$yw2O6VmnWeG`;d8fX_coZa_5S%!iC|0 zSIA5Qdl=CDHA+ju1fHmg5)V)p*i7z)xWhl71{&%;6|%KHp2Kuu<)@oeCa0i5SN3sL z)e&|3Me)>Xllyry2cw2CZ-AmdSv#s_A|x^QQCl!9!t=-}UuE0D;gF+Fut!ksmn-Lq z6Wn9jQWl?6i3(=4LMf#u?SW82hLM7z8g{2}PNFXST+NVZ*e^X(O!vh95#v@;F3ffsi|GTVQXQAQy3(vF$uIT+MYvIpD5zE;C35cRHr2!BZUVw zOGW3-gubqZN*kN5Pf#ob4kqD+6uTuj6JkQjs6im1lrBs|-A>xm(yJvfND{E*ltXcF zmShf7=7G|hKJm-ABp*;$>6w>G3<#i#M5ohWYLa&3DjS*0L`!_pzQJd*$dpui-n`i> zeFj&x_{ciSizX{`96R-@D1|4xS>>Xp@=FONR;tb#;SmFZKYmbd4%q4TwiC?4>zuII}KaJl%md9SaHMMxN9kkYam z`&WdRs|yD{qnBWz7T(j|Mi|pdKZz1KbM2c=;=2;?KYNJuglEv#T@$l4uk>YSdx5^I0{En3|Nzy*a zsFaj~HIBf2;NlkGdQ<0=EuZ?0UCfooLrN%rVV0G>d(WK=n?=ncr~P)YU;d>fS|dfO zG(=QsvZ%IY0H))9Rmm2D!jg-oZOcAJHWdt%LSulj0~rj#Mv}Gh{2NcQ)H`jIFDYv; zg0raE#4K?2YPDvratVo0Qe}p7ct=MrATL%Y{!os|vG#$JGo={1IQHr;u*_Ql9spn5 z^Drq7X-h~NP{+ww&5(Fr)8KS@Un}%>A+*t$4QWoV(sb6fO&k&o1save?Iu}rEZUmH z7FeX|N~ao6tazctEvqJ3BG)jHJwVe-HRoECs#YGw_u)5W zh1MY8Pn;iNQhD)68gr+bzN_3-{?kj3v-0d75{5&5Kz#np$BWGjdIvj)*9E0md5#zW z_d_)k3>%Y}k88!DHd@rOXM&@I?}4M0gVXX6%*?rO(&nw-l;T2`uvbK$)|#&}Ptp$2 z1`;wY*#iYh;RC_>!w>vpOv27O7`9ON+5x(&>^p2ui^pj6js9soaHC9{rwk9R?+lZ; zC)17-kkl$$Th8)5-h4Tgr2t$qCgM2_o=d{)Cvm(k(!7=s#Ydp5yR(EmO?&M38&FhG z&DFQDQ~*$VK1$~_`4x^qdz3lz-WJo^*E{zqP0hEu7<|)pn3g+lU>dBvTN9-WWSpeW z%__r2fK7*bJrQyVEqnSxSShQ=UB`st9L_VxzlBdxDl&+xvOj!fp6z7zTkI9xXIPRt z-(|f|-|FCc1OThayM2iF374xFq9RbiqK^%bJ3)xX(zo_2L+_fcO2^sq8D-4ANdk7- zOlE{Ue&TC4w(&;|3EoT(A5a?bmL2?jG;l0O&A{2zD&Gn7@|d=DjJOpHNtMCo5j}Id zmkb<={F`k_I^wC8-VzXHg$z!464M7-xxgJHRDoiUL>CADaEz@3N=aq2?Bv?NB7kJ% zLvKUBEoe{?0 z%M*kFOnsZ;*K4j)!h0?h^Kv6YJ#5Kj?tERFfi5l%-<}MdDW82VhedvD7vrmuoyTLv z!DZH3rgXh`iPExS?Wjug0o}o0X*&{QAJK74@>VjChP2KFL`)6>$idue#| zLpVsH4~0wD5Z50&9h7?8dn+f!?GN~xa>h3@?J<1L^t0uW_`p*=#=S&5Xd~7_L-T9f zXAo6qc7wTAc5Ufak30`r$$zpg3-q-)rQz&-eB|UpNXiJO!#dj9f4ay7UHvu{oCn9O zExZWC%||Ya=nPVj?P%n%GmvOcr=@~4cw1Zj3X9XyY3u}3pG1=VVD12TgKdR)%=stdoMEQZT4i7QNZNtJTmQS`ha0mWk;3aC^-coG+ ziaCMnnrEu`y12YF^1+h4pfvL7?6ZG7neWr8^CA!P47fq$VPEI!;0ucbsR!37PzP5f zKMqH)@c}MVtwqk5)toUzZA|FPtaIhmSi#aY!@FIDh|=$R#?>e()?YFPaPGB8}YdCk&#kE$DUo{03BfMRTo54 z185rhmW-AU1Pf`1?x3{e!cl$^E`mFmr+vz*$NWInRh~+(neha-%5%J_N(awxVPM1c0H_wwt zRen?-;0Js(;x!AOmsc6TYvY18ao5ma)<5@AP0(t5=m%#>a=v` zAStf6A;0L9&%7)6eMJ04IJ{JFG4lG*XN7^}&;>zsl8K`qUi060oyWJFSk$ z05H@IsUv=!mf+s47fN_~`nRh*V&@HBhe?~V3^EQs;)PUk_Px@`$20^kn}+upN+ZFi zQ&m&raUZKw76a1fB2(R}V0@0z3aN@Qh9M|Z z$jPNp)8FAjIe~{fQa0p^$w+DBOfM4O&Ci+y(nY$}`B=KcbX54zD88i*oSEF$xoKTJ#QIm{n2W~ucK zlQ9pKx*vQS?cj!v zS9m^bEPU^%100*V8v$#|Wq&cjuaxtLZw(bUK4ZdpHkM%PkqKdtXf6|qhc~cPq+2Ow z_B+ApeBI&k&0^=1D_R{a49G#?l$B$-C({?WjB$Qks*;G6#y(i(^OQIxshuNw>r&^D z#-N`-D$^Xcx+4?SvfwMDejnp;WSSb!N*$5%YSaur-gjgI`nY8I%(8yh!>^R3LPsy}{?(MAQVw-0fMeVPg;vUa*{aD0Xm%39&iPv3*jyq%dS0<2N3aH>B07 zC|9^~ln5xAR{c7irSyIEeX1h~_b8L%Cylt|uMv&s4*`pT1S-WR4n0fA`?N`M8?cGT z{gT)heay%unyoHO#}tQgf)T?%hhwkntUSU%1(!##Fw#(JR`{;c>XX8|svxu%@U?Ry+C3o;$Do+wZ0>Nb95?|Q$^BxzB+q6ATmVS;ndQJ8&<5Bk zWB_TftklOlrLrRM@bz|ll{g9`T2~f3c;b9X4E+~wgUnUP05?za0>We*x2dxHfK8wz z@kJj>Za%0A5eR~X0V$Be3#3bW&coKhLjUyo$ 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

Zk{+f0G)W9#%jJbkN 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 z%0A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

hi*uG>lVW1V{!%sO8k=vZH;#Lv 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%0A&33aF`+; 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%0A#skK$Nk-xJyA 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 literal 0 HcmV?d00001 diff --git a/vtl-spark4/src/test/java/fr/insee/vtl/spark4/SparkDatasetTest.java b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/SparkDatasetTest.java new file mode 100644 index 000000000..12f086485 --- /dev/null +++ b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/SparkDatasetTest.java @@ -0,0 +1,140 @@ +package fr.insee.vtl.spark; + +import static fr.insee.vtl.model.Structured.Component; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import fr.insee.vtl.engine.VtlScriptEngine; +import fr.insee.vtl.model.InMemoryDataset; +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import javax.script.ScriptContext; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import org.apache.spark.api.java.function.FilterFunction; +import org.apache.spark.sql.*; +import org.apache.spark.sql.types.DataTypes; +import org.apache.spark.sql.types.StructType; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class SparkDatasetTest { + + private static SparkSession spark; + private ScriptEngine engine; + + @BeforeEach + public void setUp() { + spark = SparkSession.builder().appName("test").master("local").getOrCreate(); + + ScriptEngineManager mgr = new ScriptEngineManager(); + engine = mgr.getEngineByExtension("vtl"); + engine.put(VtlScriptEngine.PROCESSING_ENGINE_NAMES, "spark4"); + } + + @AfterAll + public static void tearDown() throws IOException { + if (spark != null) spark.close(); + } + + @Test + public void testFilters() { + StructType schema = + DataTypes.createStructType( + List.of( + DataTypes.createStructField("string", DataTypes.StringType, false), + DataTypes.createStructField("integer", DataTypes.LongType, false), + DataTypes.createStructField("boolean", DataTypes.BooleanType, false), + DataTypes.createStructField("float", DataTypes.DoubleType, false))); + + Dataset dataFrame = + spark.createDataFrame(List.of(RowFactory.create("string", 1L, true, 1.5D)), schema); + + dataFrame.filter("integer > 0").collectAsList(); + dataFrame.filter((FilterFunction) row -> false).collectAsList(); + dataFrame.selectExpr("integer + 2 as newInteger").collectAsList(); + } + + @Test + void testVtlCanReadSpark() { + + StructType schema = + DataTypes.createStructType( + List.of( + DataTypes.createStructField("string", DataTypes.StringType, false), + DataTypes.createStructField("integer", DataTypes.LongType, false), + DataTypes.createStructField("boolean", DataTypes.BooleanType, false), + DataTypes.createStructField("float", DataTypes.DoubleType, false))); + + Dataset dataFrame = + spark.createDataFrame(List.of(RowFactory.create("string", 1L, true, 1.5D)), schema); + + fr.insee.vtl.model.Dataset sparkDataset = new SparkDataset(dataFrame); + + fr.insee.vtl.model.Dataset expectedDataset = + new InMemoryDataset( + List.of(List.of("string", 1L, true, 1.5D)), + List.of( + new Component("string", String.class, fr.insee.vtl.model.Dataset.Role.MEASURE), + new Component("integer", Long.class, fr.insee.vtl.model.Dataset.Role.MEASURE), + new Component("boolean", Boolean.class, fr.insee.vtl.model.Dataset.Role.MEASURE), + new Component("float", Double.class, fr.insee.vtl.model.Dataset.Role.MEASURE))); + + assertThat(sparkDataset.getDataStructure()).isEqualTo(expectedDataset.getDataStructure()); + assertThat(sparkDataset.getDataPoints()).isEqualTo(expectedDataset.getDataPoints()); + } + + @Test + public void testParquetMetadataReading(@TempDir Path tmpDirectory) { + SparkDataset sparkDataset = + new SparkDataset( + spark.read().parquet("src/main/resources/input_sample"), + Map.of( + "year", fr.insee.vtl.model.Dataset.Role.IDENTIFIER, + "student_number", fr.insee.vtl.model.Dataset.Role.ATTRIBUTE)); + assertTrue(sparkDataset.getDataStructure().get("year").isIdentifier()); + assertTrue(sparkDataset.getDataStructure().get("student_number").isAttribute()); + + // Write the file as parquet and read again. + sparkDataset + .getSparkDataset() + .write() + .mode(SaveMode.Overwrite) + .parquet(tmpDirectory.toString()); + SparkDataset readSparkDataset = new SparkDataset(spark.read().parquet(tmpDirectory.toString())); + + assertTrue(readSparkDataset.getDataStructure().get("year").isIdentifier()); + assertTrue(readSparkDataset.getDataStructure().get("student_number").isAttribute()); + } + + @Test + public void testParquetMetadataWriting(@TempDir Path tmpDirectory) throws ScriptException { + SparkDataset datasetWithoutMetadata = + new SparkDataset(spark.read().parquet("src/main/resources/input_sample")); + ScriptContext context = engine.getContext(); + context.setAttribute("ds", datasetWithoutMetadata, ScriptContext.ENGINE_SCOPE); + + engine.eval("ds1 := ds[calc identifier school_id := school_id, attribute year := year];"); + + SparkDataset dsWithRoles = (SparkDataset) engine.getContext().getAttribute("ds1"); + dsWithRoles.getSparkDataset().write().mode(SaveMode.Overwrite).parquet(tmpDirectory.toString()); + + SparkDataset dsWithMetadata = new SparkDataset(spark.read().parquet(tmpDirectory.toString())); + + assertTrue(dsWithMetadata.getDataStructure().get("school_id").isIdentifier()); + assertTrue(dsWithMetadata.getDataStructure().get("year").isAttribute()); + + context.setAttribute("ds2", dsWithMetadata, ScriptContext.ENGINE_SCOPE); + + engine.eval("ds3 := ds2[calc identifier year := year];"); + + SparkDataset dsWithMetadataAndRoles = (SparkDataset) engine.getContext().getAttribute("ds3"); + + assertTrue(dsWithMetadataAndRoles.getDataStructure().get("year").isIdentifier()); + } +} diff --git a/vtl-spark4/src/test/java/fr/insee/vtl/spark4/SparkSQLTest.java b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/SparkSQLTest.java new file mode 100644 index 000000000..1da61da52 --- /dev/null +++ b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/SparkSQLTest.java @@ -0,0 +1,181 @@ +package fr.insee.vtl.spark; + +import static org.assertj.core.api.Assertions.assertThat; + +import fr.insee.vtl.engine.VtlScriptEngine; +import fr.insee.vtl.model.Dataset; +import java.io.File; +import java.io.IOException; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import javax.script.ScriptContext; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import org.apache.spark.sql.SparkSession; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class SparkSQLTest { + + private static SparkSession spark; + private ScriptEngine engine; + private File databaseFile; + + @BeforeEach + public void setUp() throws SQLException, IOException { + databaseFile = File.createTempFile("vtl-test", "h2"); + databaseFile.deleteOnExit(); + var connection = DriverManager.getConnection("jdbc:h2:" + databaseFile); + var statement = connection.createStatement(); + statement.executeUpdate( + "create table if not exists ds1 (" + + " id integer," + + " colChar char(7), " + + " colVarchar varchar, " + + " colInteger int," + + " colInteger4 int4," + + " colFloat float," + + " colFloat8 float8," + + " colBoolean boolean," + + " colNumeric numeric(20,2)," + + " colBigint int8," + + " colTimestamp timestamp," + + " colDate date," + + " primary key (id)" + + ")"); + + statement.executeUpdate("delete from ds1"); + statement.executeUpdate( + "insert into ds1 values (1, 'string1', 'string1', 1, 1, 1.2, 1.2, 'true', 1.2, 100, '2001-01-01T00:00:00Z', '2001-01-01')"); + statement.executeUpdate( + "insert into ds1 values (2, 'string2', 'string2', 2, 2, 5.2, 5.2, 'false', 5.2, 200, '2002-01-01T00:00:00Z', '2002-01-01')"); + statement.executeUpdate( + "insert into ds1 values (3, 'string3', 'string3', 3, 3, 3.2, 3.2, 'true', 3.2, 300, '2003-01-01T00:00:00Z', '2003-01-01')"); + statement.executeUpdate( + "insert into ds1 values (4, 'string4', 'string4', 4, 4, 4.2, 4.2, 'false', 4.2, 400, '2004-01-01T00:00:00Z', '2004-01-01')"); + + spark = SparkSession.builder().appName("test").master("local").getOrCreate(); + + ScriptEngineManager mgr = new ScriptEngineManager(); + engine = mgr.getEngineByExtension("vtl"); + engine.put(VtlScriptEngine.PROCESSING_ENGINE_NAMES, "spark4"); + } + + @AfterAll + public static void tearDown() throws IOException { + if (spark != null) spark.close(); + } + + @Test + public void testReadSql() throws ScriptException { + + // See https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html + var ds1 = + spark + .read() + .format("jdbc") + .option("url", "jdbc:h2:" + databaseFile) + // .option("dbtable", "DS1") + .option("query", "select * from DS1") + .load(); + + var bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); + bindings.put("ds1", new SparkDataset(ds1, Map.of("id", Dataset.Role.IDENTIFIER))); + + engine.eval( + "ds2 := ds1[calc " + + "col1 := COLCHAR || \" ok\", " + + "col2 := COLVARCHAR || \" ok\", " + + "col3 := COLINTEGER + 1, " + + "col4 := COLINTEGER4 * 100, " + + "col5 := round(COLFLOAT - 0.1, 1), " + + "col6 := round(COLFLOAT8 - 0.0009, 4), " + + "col7 := not(COLBOOLEAN), " + + "col8 := round(COLNUMERIC + 0.0001, 4), " + + "col9 := COLBIGINT + 9999, " + + "col10 := cast(COLTIMESTAMP, string, \"YYYY\"), " + + "col11 := cast(COLDATE, string, \"YYYY\") " + + "]" + + "[keep ID, col1, col2, col3, col4, col5, col6, col7, col8, col9, col10, col11];"); + + var ds2 = (Dataset) bindings.get("ds2"); + List> roundedDs2 = + ds2.getDataAsList().stream() + .map( + line -> + line.stream() + .map( + element -> { + if (element instanceof Double double1) { + BigDecimal bd = new BigDecimal(Double.toString(double1)); + bd = bd.setScale(10, RoundingMode.HALF_UP); + return bd.doubleValue(); + } + return element; + }) + .collect(Collectors.toList())) + .collect(Collectors.toList()); + + assertThat(roundedDs2) + .containsExactlyInAnyOrder( + List.of( + 1L, + "string1 ok", + "string1 ok", + 2L, + 100L, + 1.1D, + 1.1991D, + false, + 1.2001D, + 10099L, + "2001", + "2001"), + List.of( + 2L, + "string2 ok", + "string2 ok", + 3L, + 200L, + 5.1D, + 5.1991D, + true, + 5.2001D, + 10199L, + "2002", + "2002"), + List.of( + 3L, + "string3 ok", + "string3 ok", + 4L, + 300L, + 3.1D, + 3.1991D, + false, + 3.2001D, + 10299L, + "2003", + "2003"), + List.of( + 4L, + "string4 ok", + "string4 ok", + 5L, + 400L, + 4.1D, + 4.1991D, + true, + 4.2001D, + 10399L, + "2004", + "2004")); + } +} diff --git a/vtl-spark4/src/test/java/fr/insee/vtl/spark4/TemporalTest.java b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/TemporalTest.java new file mode 100644 index 000000000..9badbca8d --- /dev/null +++ b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/TemporalTest.java @@ -0,0 +1,97 @@ +package fr.insee.vtl.spark; + +import static fr.insee.vtl.model.Structured.Component; +import static fr.insee.vtl.spark.processing.engine.analytic.AnalyticTest.spark; +import static org.assertj.core.api.Assertions.assertThat; + +import fr.insee.vtl.engine.VtlScriptEngine; +import fr.insee.vtl.model.Dataset; +import fr.insee.vtl.model.InMemoryDataset; +import java.io.IOException; +import java.time.Instant; +import java.util.List; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import org.apache.spark.sql.SparkSession; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class TemporalTest { + + private ScriptEngine engine; + + @BeforeEach + public void setUp() { + SparkSession spark = SparkSession.builder().appName("test").master("local").getOrCreate(); + ScriptEngineManager mgr = new ScriptEngineManager(); + engine = mgr.getEngineByExtension("vtl"); + engine.put(VtlScriptEngine.PROCESSING_ENGINE_NAMES, "spark4"); + } + + @AfterAll + public static void tearDown() throws IOException { + if (spark != null) spark.close(); + } + + @Test + public void testFlowToStock() throws ScriptException { + var ds = + new InMemoryDataset( + List.of( + new Component("id1", String.class, Dataset.Role.IDENTIFIER), + new Component("id2", Instant.class, Dataset.Role.IDENTIFIER), + new Component("me1", Long.class, Dataset.Role.MEASURE)), + List.of("A", Instant.parse("2009-01-01T00:00:00Z"), 2L), + List.of("A", Instant.parse("2011-01-01T00:00:00Z"), 5L), + List.of("A", Instant.parse("2012-01-01T00:00:00Z"), -3L), + List.of("B", Instant.parse("2010-01-01T00:00:00Z"), 9L), + List.of("B", Instant.parse("2011-01-01T00:00:00Z"), 4L), + List.of("B", Instant.parse("2013-01-01T00:00:00Z"), -6L)); + engine.put("ds", ds); + engine.eval("r := flow_to_stock(ds);"); + assertThat(engine.get("r")).isInstanceOf(Dataset.class); + assertThat(((Dataset) engine.get("r")).getDataAsList()) + .containsExactlyInAnyOrder( + List.of("A", Instant.parse("2009-01-01T00:00:00Z"), 2L), + List.of("A", Instant.parse("2011-01-01T00:00:00Z"), 7L), + List.of("A", Instant.parse("2012-01-01T00:00:00Z"), 4L), + List.of("B", Instant.parse("2010-01-01T00:00:00Z"), 9L), + List.of("B", Instant.parse("2011-01-01T00:00:00Z"), 13L), + List.of("B", Instant.parse("2013-01-01T00:00:00Z"), 7L)); + } + + @Test + public void testStockToFlow() throws ScriptException { + var ds = + new InMemoryDataset( + List.of( + new Component("id1", String.class, Dataset.Role.IDENTIFIER), + new Component("id2", Instant.class, Dataset.Role.IDENTIFIER), + new Component("me1", Long.class, Dataset.Role.MEASURE), + new Component("me2", String.class, Dataset.Role.MEASURE)), + List.of("A", Instant.parse("2010-01-01T00:00:00Z"), 2L, "foo"), + List.of("A", Instant.parse("2011-01-01T00:00:00Z"), 7L, "foo"), + List.of("A", Instant.parse("2012-01-01T00:00:00Z"), 4L, "foo"), + List.of("A", Instant.parse("2013-01-01T00:00:00Z"), 13L, "foo"), + List.of("B", Instant.parse("2010-01-01T00:00:00Z"), 4L, "foo"), + List.of("B", Instant.parse("2011-01-01T00:00:00Z"), -4L, "foo"), + List.of("B", Instant.parse("2012-01-01T00:00:00Z"), -4L, "foo"), + List.of("B", Instant.parse("2013-01-01T00:00:00Z"), 2L, "foo")); + engine.put("ds", ds); + engine.eval("res := stock_to_flow(ds);"); + assertThat(engine.get("res")).isInstanceOf(Dataset.class); + ((Dataset) engine.get("res")).getDataAsMap().forEach(System.out::println); + assertThat(((Dataset) engine.get("res")).getDataAsList()) + .containsExactly( + List.of("A", Instant.parse("2010-01-01T00:00:00Z"), "foo", 2L), + List.of("A", Instant.parse("2011-01-01T00:00:00Z"), "foo", 5L), + List.of("A", Instant.parse("2012-01-01T00:00:00Z"), "foo", -3L), + List.of("A", Instant.parse("2013-01-01T00:00:00Z"), "foo", 9L), + List.of("B", Instant.parse("2010-01-01T00:00:00Z"), "foo", 4L), + List.of("B", Instant.parse("2011-01-01T00:00:00Z"), "foo", -8L), + List.of("B", Instant.parse("2012-01-01T00:00:00Z"), "foo", 0L), + List.of("B", Instant.parse("2013-01-01T00:00:00Z"), "foo", 6L)); + } +} diff --git a/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/AggregateTest.java b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/AggregateTest.java new file mode 100644 index 000000000..ac2101dd7 --- /dev/null +++ b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/AggregateTest.java @@ -0,0 +1,305 @@ +package fr.insee.vtl.spark.processing.engine; + +import static org.assertj.core.api.Assertions.assertThat; + +import fr.insee.vtl.engine.VtlScriptEngine; +import fr.insee.vtl.model.Dataset; +import fr.insee.vtl.model.InMemoryDataset; +import fr.insee.vtl.model.Structured; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import org.apache.spark.sql.SparkSession; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class AggregateTest { + + InMemoryDataset dataset = + new InMemoryDataset( + List.of( + Map.of("name", "Hadrien", "country", "norway", "age", 10L, "weight", 11D), + Map.of("name", "Nico", "country", "france", "age", 11L, "weight", 10D), + Map.of("name", "Franck", "country", "france", "age", 12L, "weight", 9D), + Map.of("name", "pengfei", "country", "france", "age", 13L, "weight", 11D)), + Map.of( + "name", + String.class, + "country", + String.class, + "age", + Long.class, + "weight", + Double.class), + Map.of( + "name", + Dataset.Role.IDENTIFIER, + "country", + Dataset.Role.IDENTIFIER, + "age", + Dataset.Role.MEASURE, + "weight", + Dataset.Role.MEASURE)); + private SparkSession spark; + private ScriptEngine engine; + + @BeforeEach + public void setUp() { + + ScriptEngineManager mgr = new ScriptEngineManager(); + engine = mgr.getEngineByExtension("vtl"); + + spark = SparkSession.builder().appName("test").master("local").getOrCreate(); + SparkSession.setActiveSession(spark); + + engine.put(VtlScriptEngine.PROCESSING_ENGINE_NAMES, "spark4"); + } + + @AfterEach + public void tearDown() throws IOException { + if (spark != null) spark.close(); + } + + @Test + void testAggregateGroupAll() throws ScriptException { + engine.put("ds1", dataset); + engine.eval("res := ds1[aggr test := sum(age) group all length(name)];"); + + var actual = ((Dataset) engine.get("res")); + + assertThat(actual.getDataAsMap()) + .containsExactly( + Map.of("test", 23L, "time", 7L), + Map.of("test", 12L, "time", 6L), + Map.of("test", 11L, "time", 4L)); + } + + @Test + public void testAggregateClause() throws ScriptException { + + engine.put("ds1", dataset); + engine.eval( + "res := ds1[aggr " + + "sumAge := sum(age*2)," + + "avgWeight := avg(weight)," + + "countVal := count()," + + "maxAge := max(age)," + + "maxWeight := max(weight)," + + "minAge := min(age)," + + "minWeight := min(weight)," + + "medianAge := median(age)," + + "medianWeight := median(weight)" + + " group by country];"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + assertThat(((Dataset) engine.getContext().getAttribute("res")).getDataAsMap()) + .containsExactly( + Map.of( + "country", + "france", + "sumAge", + 72L, + "avgWeight", + 10.0D, + "countVal", + 3L, + "maxAge", + 13L, + "maxWeight", + 11.0D, + "minAge", + 11L, + "minWeight", + 9D, + "medianAge", + 12L, + "medianWeight", + 10.0D), + Map.of( + "country", + "norway", + "sumAge", + 20L, + "avgWeight", + 11.0, + "countVal", + 1L, + "maxAge", + 10L, + "maxWeight", + 11.0D, + "minAge", + 10L, + "minWeight", + 11D, + "medianAge", + 10L, + "medianWeight", + 11D)); + + // InMemoryDataset dataset2 = new InMemoryDataset( + // List.of( + // Map.of("name", "Hadrien", "country", "norway", "age", 10L, "weight", + // 11D), + // Map.of("name", "Nico", "country", "france", "age", 9L, "weight", 5D), + // Map.of("name", "Franck", "country", "france", "age", 10L, "weight", + // 15D), + // Map.of("name", "Nico1", "country", "france", "age", 11L, "weight", + // 10D), + // Map.of("name", "Franck1", "country", "france", "age", 12L, "weight", + // 8D) + // ), + // Map.of("name", String.class, "country", String.class, "age", Long.class, + // "weight", Double.class), + // Map.of("name", Role.IDENTIFIER, "country", Role.IDENTIFIER, "age", + // Role.MEASURE, "weight", Role.MEASURE) + // ); + // + // context.setAttribute("ds2", dataset2, ScriptContext.ENGINE_SCOPE); + // + // engine.eval("res := ds2[aggr " + + // "stddev_popAge := stddev_pop(age), " + + // "stddev_popWeight := stddev_pop(weight), " + + // "stddev_sampAge := stddev_samp(age), " + + // "stddev_sampWeight := stddev_samp(weight), " + + // "var_popAge := var_pop(age), " + + // "var_popWeight := var_pop(weight), " + + // "var_sampAge := var_samp(age), " + + // "var_sampWeight := var_samp(weight)" + + // " group by country];"); + // + // assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + // + // var fr = ((Dataset) engine.getContext().getAttribute("res")).getDataAsMap().get(0); + // + // assertThat((Double) fr.get("stddev_popAge")).isCloseTo(1.118, + // Percentage.withPercentage(2)); + // assertThat((Double) fr.get("stddev_popWeight")).isCloseTo(3.640, + // Percentage.withPercentage(2)); + // assertThat((Double) fr.get("stddev_sampAge")).isCloseTo(1.290, + // Percentage.withPercentage(2)); + // assertThat((Double) fr.get("stddev_sampWeight")).isCloseTo(4.2, + // Percentage.withPercentage(2)); + // assertThat((Double) fr.get("var_popAge")).isEqualTo(1.25); + // assertThat((Double) fr.get("var_popWeight")).isEqualTo(13.25); + // assertThat((Double) fr.get("var_sampAge")).isCloseTo(1.666, + // Percentage.withPercentage(2)); + // assertThat((Double) fr.get("var_sampWeight")).isCloseTo(17.666, + // Percentage.withPercentage(2)); + // + // var no = ((Dataset) engine.getContext().getAttribute("res")).getDataAsMap().get(1); + // + // assertThat((Double) no.get("stddev_popAge")).isEqualTo(0.0); + // assertThat((Double) no.get("stddev_popWeight")).isEqualTo(0.0); + // assertThat((Double) no.get("stddev_sampAge")).isEqualTo(0.0); + // assertThat((Double) no.get("stddev_sampWeight")).isEqualTo(0.0); + // assertThat((Double) no.get("var_popAge")).isEqualTo(0.0); + // assertThat((Double) no.get("var_popWeight")).isEqualTo(0.0); + // assertThat((Double) no.get("var_sampAge")).isEqualTo(0.0); + // assertThat((Double) no.get("var_sampWeight")).isEqualTo(0.0); + + } + + @Test + public void testMinMax() throws ScriptException { + + InMemoryDataset ds = + new InMemoryDataset( + List.of( + List.of( + "Hadrien", "No", 10L, 11D, true, "01/01/1990" + // , + // "2000-01-01/2000-12-31", + // "2000M12" + ), + List.of( + "Nico", "Fr", 10L, 11D, false, "01/01/1991" + // , + // "2000-01-01/2000-01-31", + // "2000M11" + ), + List.of( + "Franck", "Fr", 12L, 9D, true, "01/01/1989" + // , + // "2000-01-01/2000-03-31", + // "2010M10" + )), + List.of( + new Structured.Component("name", String.class, Dataset.Role.MEASURE), + new Structured.Component("country", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("l", Long.class, Dataset.Role.MEASURE), + new Structured.Component("dou", Double.class, Dataset.Role.MEASURE), + new Structured.Component("boo", Boolean.class, Dataset.Role.MEASURE), + new Structured.Component("d", String.class, Dataset.Role.MEASURE) + // , + // new Structured.Component("dur", String.class, Dataset.Role.MEASURE), + // new Structured.Component("tp", String.class, Dataset.Role.MEASURE) + )); + + engine.put("ds", ds); + engine.eval( + "res := ds[calc d := cast(d, date, \"dd/MM/yyyy\")" + + + // ", " + // + " dur := cast(dur, string), " + // + " tp := cast(tp, string)" + + "]" + + "[aggr " + + "minName := min(name)," + + "maxName := max(name)," + + "minL := min(l)," + + "maxL := max(l)," + + "minDou := min(dou)," + + "maxDou := max(dou)," + + "minBoo := min(boo)," + + "maxBoo := max(boo)," + + "minD := min(d)," + + "maxD := max(d)" + // + "," + // + "minDur := min(dur)," + // + "maxDur := max(dur)," + // + "minTp := min(tp)," + // + "maxTp := max(tp)" + + " group by country]" + + " [calc minD := cast(minD, string, \"dd/MM/yyyy\")," + + " maxD := cast(maxD, string, \"dd/MM/yyyy\")" + // + "," + // + " minDur := cast(minDur, string)," + // + " maxDur := cast(maxDur, string)," + // + " minTp := cast(minTp, string)," + // + " maxTp := cast(maxTp, string)" + + " ];"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + assertThat(((Dataset) engine.getContext().getAttribute("res")).getDataAsList()) + .containsExactlyInAnyOrder( + List.of( + "No", + "Hadrien", + "Hadrien", + 10L, + 10L, + 11D, + 11D, + true, + true, + "01/01/1990", + "01/01/1990" + // , + // "2000-01-01/2000-12-31", + // "2000-01-01/2000-12-31", + // "2000M12", + // "2000M12" + ), + List.of( + "Fr", "Franck", "Nico", 10L, 12L, 9D, 11D, false, true, "01/01/1989", "01/01/1991" + // , + // "2000-01-01/2000-01-31", + // "2000-01-01/2000-03-31", + // "2000M11", + // "2010Q1" + )); + } +} diff --git a/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/CalcTest.java b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/CalcTest.java new file mode 100644 index 000000000..64e6c9f09 --- /dev/null +++ b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/CalcTest.java @@ -0,0 +1,83 @@ +package fr.insee.vtl.spark.processing.engine; + +import static org.assertj.core.api.Assertions.assertThat; + +import fr.insee.vtl.engine.VtlScriptEngine; +import fr.insee.vtl.model.Dataset; +import fr.insee.vtl.model.InMemoryDataset; +import fr.insee.vtl.model.Structured; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import javax.script.ScriptContext; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import org.apache.spark.sql.SparkSession; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class CalcTest { + + private SparkSession spark; + private ScriptEngine engine; + + @BeforeEach + public void setUp() { + + ScriptEngineManager mgr = new ScriptEngineManager(); + engine = mgr.getEngineByExtension("vtl"); + + spark = SparkSession.builder().appName("test").master("local").getOrCreate(); + SparkSession.setActiveSession(spark); + + engine.put(VtlScriptEngine.PROCESSING_ENGINE_NAMES, "spark4"); + } + + @AfterEach + public void tearDown() throws IOException { + if (spark != null) spark.close(); + } + + InMemoryDataset dataset = + new InMemoryDataset( + List.of( + Map.of("name", "Hadrien", "age", 10L, "weight", 11L), + Map.of("name", "Nico", "age", 11L, "weight", 10L), + Map.of("name", "Franck", "age", 12L, "weight", 9L)), + Map.of("name", String.class, "age", Long.class, "weight", Long.class), + Map.of( + "name", + Dataset.Role.IDENTIFIER, + "age", + Dataset.Role.MEASURE, + "weight", + Dataset.Role.MEASURE)); + + @Test + public void testCalcClause() throws ScriptException, InterruptedException { + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", dataset, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "ds := ds1[calc test := between(age, 10, 11), age := age * 2, attribute wisdom := (weight + age) / 2];"); + + var ds = (Dataset) engine.getContext().getAttribute("ds"); + assertThat(ds).isInstanceOf(Dataset.class); + assertThat(ds.getDataAsMap()) + .isEqualTo( + List.of( + Map.of("name", "Hadrien", "age", 20L, "test", true, "weight", 11L, "wisdom", 10.5D), + Map.of("name", "Nico", "age", 22L, "test", true, "weight", 10L, "wisdom", 10.5D), + Map.of( + "name", "Franck", "age", 24L, "test", false, "weight", 9L, "wisdom", 10.5D))); + assertThat(ds.getDataStructure()) + .containsValues( + new Structured.Component("name", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("age", Long.class, Dataset.Role.MEASURE), + new Structured.Component("test", Boolean.class, Dataset.Role.MEASURE), + new Structured.Component("weight", Long.class, Dataset.Role.MEASURE), + new Structured.Component("wisdom", Double.class, Dataset.Role.ATTRIBUTE)); + } +} diff --git a/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/FilterTest.java b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/FilterTest.java new file mode 100644 index 000000000..1b0d36cd3 --- /dev/null +++ b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/FilterTest.java @@ -0,0 +1,94 @@ +package fr.insee.vtl.spark.processing.engine; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import fr.insee.vtl.engine.VtlScriptEngine; +import fr.insee.vtl.engine.exceptions.ConflictingTypesException; +import fr.insee.vtl.model.Dataset; +import fr.insee.vtl.model.InMemoryDataset; +import fr.insee.vtl.model.Structured; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import javax.script.ScriptContext; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import org.apache.spark.sql.SparkSession; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class FilterTest { + + InMemoryDataset dataset = + new InMemoryDataset( + List.of( + Map.of("name", "Hadrien", "age", 10L, "weight", 11L), + Map.of("name", "Nico", "age", 11L, "weight", 10L), + Map.of("name", "Franck", "age", 12L, "weight", 9L)), + Map.of("name", String.class, "age", Long.class, "weight", Long.class), + Map.of( + "name", + Dataset.Role.IDENTIFIER, + "age", + Dataset.Role.MEASURE, + "weight", + Dataset.Role.MEASURE)); + private SparkSession spark; + private ScriptEngine engine; + + @BeforeEach + public void setUp() { + + ScriptEngineManager mgr = new ScriptEngineManager(); + engine = mgr.getEngineByExtension("vtl"); + + spark = SparkSession.builder().appName("test").master("local").getOrCreate(); + SparkSession.setActiveSession(spark); + + engine.put(VtlScriptEngine.PROCESSING_ENGINE_NAMES, "spark4"); + } + + @AfterEach + public void tearDown() throws IOException { + if (spark != null) spark.close(); + } + + @Test + public void testLessThanBadTypes() { + ScriptContext context = engine.getContext(); + context.setAttribute("ds", dataset, ScriptContext.ENGINE_SCOPE); + assertThatThrownBy(() -> engine.eval("ds_out := ds[filter name < 1];")) + .isInstanceOf(ConflictingTypesException.class) + .hasMessage("conflicting types: [String, Long]"); + } + + @Test + public void testLessThanGoodTypes() throws ScriptException { + ScriptContext context = engine.getContext(); + context.setAttribute("ds", dataset, ScriptContext.ENGINE_SCOPE); + engine.eval("ds_out := ds[filter 18.1 < age];"); + } + + @Test + public void testFilterClause() throws ScriptException { + + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", dataset, ScriptContext.ENGINE_SCOPE); + + engine.eval("ds := ds1[filter age > 10 and age < 12];"); + + assertThat(engine.getContext().getAttribute("ds")).isInstanceOf(Dataset.class); + var ds = (Dataset) engine.getContext().getAttribute("ds"); + assertThat(ds.getDataAsMap()) + .isEqualTo(List.of(Map.of("name", "Nico", "age", 11L, "weight", 10L))); + + assertThat(ds.getDataStructure()) + .containsValues( + new Structured.Component("name", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("age", Long.class, Dataset.Role.MEASURE), + new Structured.Component("weight", Long.class, Dataset.Role.MEASURE)); + } +} diff --git a/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/JoinTest.java b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/JoinTest.java new file mode 100644 index 000000000..8374c1d6c --- /dev/null +++ b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/JoinTest.java @@ -0,0 +1,278 @@ +package fr.insee.vtl.spark.processing.engine; + +import static org.assertj.core.api.Assertions.assertThat; + +import fr.insee.vtl.engine.VtlScriptEngine; +import fr.insee.vtl.model.Dataset; +import fr.insee.vtl.model.InMemoryDataset; +import fr.insee.vtl.model.Structured; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import javax.script.ScriptContext; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import org.apache.spark.sql.SparkSession; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class JoinTest { + + private final InMemoryDataset dataset1 = + new InMemoryDataset( + List.of( + List.of("a", 1L, 2L), + List.of("b", 3L, 4L), + List.of("c", 5L, 6L), + List.of("d", 7L, 8L)), + List.of( + new Structured.Component("name", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("age", Long.class, Dataset.Role.MEASURE), + new Structured.Component("weight", Long.class, Dataset.Role.MEASURE))); + private final InMemoryDataset dataset2 = + new InMemoryDataset( + List.of( + List.of(9L, "a", 10L), + List.of(11L, "b", 12L), + List.of(12L, "c", 13L), + List.of(14L, "c", 15L)), + List.of( + new Structured.Component("age2", Long.class, Dataset.Role.MEASURE), + new Structured.Component("name", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("weight2", Long.class, Dataset.Role.MEASURE))); + private final InMemoryDataset dataset3 = + new InMemoryDataset( + List.of( + List.of(16L, "a", 17L), + List.of(18L, "b", 19L), + List.of(20L, "c", 21L), + List.of(22L, "c", 23L)), + List.of( + new Structured.Component("age3", Long.class, Dataset.Role.MEASURE), + new Structured.Component("name", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("weight3", Long.class, Dataset.Role.MEASURE))); + private SparkSession spark; + private ScriptEngine engine; + + @BeforeEach + public void setUp() { + + ScriptEngineManager mgr = new ScriptEngineManager(); + engine = mgr.getEngineByExtension("vtl"); + + spark = SparkSession.builder().appName("test").master("local").getOrCreate(); + SparkSession.setActiveSession(spark); + + engine.put(VtlScriptEngine.PROCESSING_ENGINE_NAMES, "spark4"); + } + + @AfterEach + public void tearDown() throws IOException { + if (spark != null) spark.close(); + } + + @Test + public void testLeftJoin() throws ScriptException { + + ScriptContext context = engine.getContext(); + context.getBindings(ScriptContext.ENGINE_SCOPE).put("ds_1", dataset1); + context.getBindings(ScriptContext.ENGINE_SCOPE).put("ds_2", dataset2); + context.getBindings(ScriptContext.ENGINE_SCOPE).put("ds_3", dataset3); + + engine.eval("result := left_join(ds_1 as ds1, ds_2 as ds2, ds_3 as ds3);"); + + var result = (Dataset) context.getAttribute("result"); + assertThat(result.getDataAsList()) + .containsExactlyInAnyOrder( + Arrays.asList("a", 2L, 16L, 17L, 10L, 1L, 9L), + Arrays.asList("b", 4L, 18L, 19L, 12L, 3L, 11L), + Arrays.asList("c", 6L, 22L, 23L, 15L, 5L, 14L), + Arrays.asList("c", 6L, 20L, 21L, 15L, 5L, 14L), + Arrays.asList("c", 6L, 22L, 23L, 13L, 5L, 12L), + Arrays.asList("c", 6L, 20L, 21L, 13L, 5L, 12L), + Arrays.asList("d", 8L, null, null, null, 7L, null)); + + assertThat(result.getDataStructure()) + .containsValues( + new Structured.Component("name", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("age", Long.class, Dataset.Role.MEASURE), + new Structured.Component("weight", Long.class, Dataset.Role.MEASURE), + new Structured.Component("age2", Long.class, Dataset.Role.MEASURE), + new Structured.Component("weight2", Long.class, Dataset.Role.MEASURE), + new Structured.Component("age3", Long.class, Dataset.Role.MEASURE), + new Structured.Component("weight3", Long.class, Dataset.Role.MEASURE)); + } + + @Test + public void testInnerJoin() throws ScriptException { + ScriptContext context = engine.getContext(); + context.getBindings(ScriptContext.ENGINE_SCOPE).put("ds1", dataset1); + context.getBindings(ScriptContext.ENGINE_SCOPE).put("ds2", dataset2); + context.getBindings(ScriptContext.ENGINE_SCOPE).put("ds3", dataset3); + + engine.eval("result := inner_join(ds1 as dsOne, ds2, ds3);"); + + var resultInner = (Dataset) context.getAttribute("result"); + assertThat(resultInner.getDataAsList()) + .containsExactlyInAnyOrder( + Arrays.asList("a", 2L, 16L, 17L, 10L, 1L, 9L), + Arrays.asList("b", 4L, 18L, 19L, 12L, 3L, 11L), + Arrays.asList("c", 6L, 22L, 23L, 15L, 5L, 14L), + Arrays.asList("c", 6L, 20L, 21L, 15L, 5L, 14L), + Arrays.asList("c", 6L, 22L, 23L, 13L, 5L, 12L), + Arrays.asList("c", 6L, 20L, 21L, 13L, 5L, 12L)); + + assertThat(resultInner.getDataStructure()) + .containsValues( + new Structured.Component("name", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("age", Long.class, Dataset.Role.MEASURE), + new Structured.Component("weight", Long.class, Dataset.Role.MEASURE), + new Structured.Component("age2", Long.class, Dataset.Role.MEASURE), + new Structured.Component("weight2", Long.class, Dataset.Role.MEASURE), + new Structured.Component("age3", Long.class, Dataset.Role.MEASURE), + new Structured.Component("weight3", Long.class, Dataset.Role.MEASURE)); + } + + @Test + public void testFullJoin() throws ScriptException { + ScriptContext context = engine.getContext(); + + var ds1 = + new InMemoryDataset( + List.of( + new Structured.Component("id", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("m1", Long.class, Dataset.Role.MEASURE)), + Arrays.asList("b", 1L), + Arrays.asList("c", 2L), + Arrays.asList("d", 3L)); + + var ds2 = + new InMemoryDataset( + List.of( + new Structured.Component("id", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("m1", Long.class, Dataset.Role.MEASURE)), + Arrays.asList("a", 4L), + Arrays.asList("b", 5L), + Arrays.asList("c", 6L)); + + var ds3 = + new InMemoryDataset( + List.of( + new Structured.Component("id", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("m1", Long.class, Dataset.Role.MEASURE)), + Arrays.asList("a", 7L), + Arrays.asList("d", 8L)); + + context.getBindings(ScriptContext.ENGINE_SCOPE).put("ds_1", ds1); + context.getBindings(ScriptContext.ENGINE_SCOPE).put("ds_2", ds2); + context.getBindings(ScriptContext.ENGINE_SCOPE).put("ds_3", ds3); + + engine.eval("result := full_join(ds_1 as ds1, ds_2 as ds2, ds_3 as ds3);"); + + var result = (Dataset) context.getAttribute("result"); + + assertThat(result.getDataStructure().values()) + .containsExactlyInAnyOrder( + new Structured.Component("id", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("m1", Long.class, Dataset.Role.MEASURE)); + + assertThat(result.getDataAsList()) + .containsExactlyInAnyOrder( + Arrays.asList(8L, "d"), + Arrays.asList(null, "c"), + Arrays.asList(null, "b"), + Arrays.asList(7L, "a")); + } + + @Test + public void testCrossJoin() throws ScriptException { + ScriptContext context = engine.getContext(); + context.getBindings(ScriptContext.ENGINE_SCOPE).put("ds1", dataset1); + context.getBindings(ScriptContext.ENGINE_SCOPE).put("ds2", dataset2); + context.getBindings(ScriptContext.ENGINE_SCOPE).put("ds3", dataset3); + + engine.eval("result := cross_join(ds1 as dsOne, ds2, ds3);"); + + var resultCross = (Dataset) context.getAttribute("result"); + assertThat(resultCross.getDataAsList()) + .containsExactlyInAnyOrder( + Arrays.asList("a", 1L, 2L, 9L, "a", 10L, 16L, "a", 17L), + Arrays.asList("a", 1L, 2L, 9L, "a", 10L, 18L, "b", 19L), + Arrays.asList("a", 1L, 2L, 9L, "a", 10L, 20L, "c", 21L), + Arrays.asList("a", 1L, 2L, 9L, "a", 10L, 22L, "c", 23L), + Arrays.asList("a", 1L, 2L, 11L, "b", 12L, 16L, "a", 17L), + Arrays.asList("a", 1L, 2L, 11L, "b", 12L, 18L, "b", 19L), + Arrays.asList("a", 1L, 2L, 11L, "b", 12L, 20L, "c", 21L), + Arrays.asList("a", 1L, 2L, 11L, "b", 12L, 22L, "c", 23L), + Arrays.asList("a", 1L, 2L, 12L, "c", 13L, 16L, "a", 17L), + Arrays.asList("a", 1L, 2L, 12L, "c", 13L, 18L, "b", 19L), + Arrays.asList("a", 1L, 2L, 12L, "c", 13L, 20L, "c", 21L), + Arrays.asList("a", 1L, 2L, 12L, "c", 13L, 22L, "c", 23L), + Arrays.asList("a", 1L, 2L, 14L, "c", 15L, 16L, "a", 17L), + Arrays.asList("a", 1L, 2L, 14L, "c", 15L, 18L, "b", 19L), + Arrays.asList("a", 1L, 2L, 14L, "c", 15L, 20L, "c", 21L), + Arrays.asList("a", 1L, 2L, 14L, "c", 15L, 22L, "c", 23L), + Arrays.asList("b", 3L, 4L, 9L, "a", 10L, 16L, "a", 17L), + Arrays.asList("b", 3L, 4L, 9L, "a", 10L, 18L, "b", 19L), + Arrays.asList("b", 3L, 4L, 9L, "a", 10L, 20L, "c", 21L), + Arrays.asList("b", 3L, 4L, 9L, "a", 10L, 22L, "c", 23L), + Arrays.asList("b", 3L, 4L, 11L, "b", 12L, 16L, "a", 17L), + Arrays.asList("b", 3L, 4L, 11L, "b", 12L, 18L, "b", 19L), + Arrays.asList("b", 3L, 4L, 11L, "b", 12L, 20L, "c", 21L), + Arrays.asList("b", 3L, 4L, 11L, "b", 12L, 22L, "c", 23L), + Arrays.asList("b", 3L, 4L, 12L, "c", 13L, 16L, "a", 17L), + Arrays.asList("b", 3L, 4L, 12L, "c", 13L, 18L, "b", 19L), + Arrays.asList("b", 3L, 4L, 12L, "c", 13L, 20L, "c", 21L), + Arrays.asList("b", 3L, 4L, 12L, "c", 13L, 22L, "c", 23L), + Arrays.asList("b", 3L, 4L, 14L, "c", 15L, 16L, "a", 17L), + Arrays.asList("b", 3L, 4L, 14L, "c", 15L, 18L, "b", 19L), + Arrays.asList("b", 3L, 4L, 14L, "c", 15L, 20L, "c", 21L), + Arrays.asList("b", 3L, 4L, 14L, "c", 15L, 22L, "c", 23L), + Arrays.asList("c", 5L, 6L, 9L, "a", 10L, 16L, "a", 17L), + Arrays.asList("c", 5L, 6L, 9L, "a", 10L, 18L, "b", 19L), + Arrays.asList("c", 5L, 6L, 9L, "a", 10L, 20L, "c", 21L), + Arrays.asList("c", 5L, 6L, 9L, "a", 10L, 22L, "c", 23L), + Arrays.asList("c", 5L, 6L, 11L, "b", 12L, 16L, "a", 17L), + Arrays.asList("c", 5L, 6L, 11L, "b", 12L, 18L, "b", 19L), + Arrays.asList("c", 5L, 6L, 11L, "b", 12L, 20L, "c", 21L), + Arrays.asList("c", 5L, 6L, 11L, "b", 12L, 22L, "c", 23L), + Arrays.asList("c", 5L, 6L, 12L, "c", 13L, 16L, "a", 17L), + Arrays.asList("c", 5L, 6L, 12L, "c", 13L, 18L, "b", 19L), + Arrays.asList("c", 5L, 6L, 12L, "c", 13L, 20L, "c", 21L), + Arrays.asList("c", 5L, 6L, 12L, "c", 13L, 22L, "c", 23L), + Arrays.asList("c", 5L, 6L, 14L, "c", 15L, 16L, "a", 17L), + Arrays.asList("c", 5L, 6L, 14L, "c", 15L, 18L, "b", 19L), + Arrays.asList("c", 5L, 6L, 14L, "c", 15L, 20L, "c", 21L), + Arrays.asList("c", 5L, 6L, 14L, "c", 15L, 22L, "c", 23L), + Arrays.asList("d", 7L, 8L, 9L, "a", 10L, 16L, "a", 17L), + Arrays.asList("d", 7L, 8L, 9L, "a", 10L, 18L, "b", 19L), + Arrays.asList("d", 7L, 8L, 9L, "a", 10L, 20L, "c", 21L), + Arrays.asList("d", 7L, 8L, 9L, "a", 10L, 22L, "c", 23L), + Arrays.asList("d", 7L, 8L, 11L, "b", 12L, 16L, "a", 17L), + Arrays.asList("d", 7L, 8L, 11L, "b", 12L, 18L, "b", 19L), + Arrays.asList("d", 7L, 8L, 11L, "b", 12L, 20L, "c", 21L), + Arrays.asList("d", 7L, 8L, 11L, "b", 12L, 22L, "c", 23L), + Arrays.asList("d", 7L, 8L, 12L, "c", 13L, 16L, "a", 17L), + Arrays.asList("d", 7L, 8L, 12L, "c", 13L, 18L, "b", 19L), + Arrays.asList("d", 7L, 8L, 12L, "c", 13L, 20L, "c", 21L), + Arrays.asList("d", 7L, 8L, 12L, "c", 13L, 22L, "c", 23L), + Arrays.asList("d", 7L, 8L, 14L, "c", 15L, 16L, "a", 17L), + Arrays.asList("d", 7L, 8L, 14L, "c", 15L, 18L, "b", 19L), + Arrays.asList("d", 7L, 8L, 14L, "c", 15L, 20L, "c", 21L), + Arrays.asList("d", 7L, 8L, 14L, "c", 15L, 22L, "c", 23L)); + + assertThat(resultCross.getDataStructure().values()) + .containsExactly( + new Structured.Component("dsOne#name", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("age", Long.class, Dataset.Role.MEASURE), + new Structured.Component("weight", Long.class, Dataset.Role.MEASURE), + new Structured.Component("age2", Long.class, Dataset.Role.MEASURE), + new Structured.Component("ds2#name", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("weight2", Long.class, Dataset.Role.MEASURE), + new Structured.Component("age3", Long.class, Dataset.Role.MEASURE), + new Structured.Component("ds3#name", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("weight3", Long.class, Dataset.Role.MEASURE)); + } +} diff --git a/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/OperatorsTest.java b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/OperatorsTest.java new file mode 100644 index 000000000..bd19e6b6c --- /dev/null +++ b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/OperatorsTest.java @@ -0,0 +1,94 @@ +package fr.insee.vtl.spark.processing.engine; + +import static org.assertj.core.api.Assertions.assertThat; + +import fr.insee.vtl.engine.VtlScriptEngine; +import fr.insee.vtl.model.Dataset; +import fr.insee.vtl.spark.samples.DatasetSamples; +import java.io.IOException; +import java.util.Map; +import javax.script.ScriptContext; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import org.apache.spark.sql.SparkSession; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class OperatorsTest { + + private SparkSession spark; + private ScriptEngine engine; + + @BeforeEach + public void setUp() { + + ScriptEngineManager mgr = new ScriptEngineManager(); + engine = mgr.getEngineByExtension("vtl"); + + spark = SparkSession.builder().appName("test").master("local").getOrCreate(); + SparkSession.setActiveSession(spark); + + engine.put(VtlScriptEngine.PROCESSING_ENGINE_NAMES, "spark4"); + } + + @AfterEach + public void tearDown() throws IOException { + if (spark != null) spark.close(); + } + + @Test + public void testOperators() throws ScriptException { + + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", DatasetSamples.ds1, ScriptContext.ENGINE_SCOPE); + context.setAttribute("ds2", DatasetSamples.ds2, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := ds1#long1; " + + "res1 := isnull(ds1); " + + "ds_1 := ds1[keep long1, double1]; ds_2 := ds2[keep long1, double1]; " + + "res2 := ds_1 + ds_2; " + + "res3 := ds_1 - ds_2; " + + "res4 := ds_1 * ds_2; " + + "res5 := ds_1 / ds_2; " + + "res6 := ds_1 = ds_2; " + + "res7 := ds_1 <> ds_2; " + + "res8 := ds_1 < ds_2; " + + "res9 := ds_1 <= ds_2; " + + "res10 := ds_1 > ds_2; " + + "res11 := ds_1 >= ds_2; " + + "res12 := + ds_1; " + + "res13 := - ds_1; " + + "res14 := ceil(floor(ln(exp(abs(ds_1))))); " + + "res15 := round(ds_1, 5); " + + "res16 := trunc(ds_1, 5); " + + "res17 := sqrt(abs(ds_1)); " + + "res18 := mod(ds_1, 5); " + + "res19 := power(ds_1, 5); " + + "res20 := log(abs(ds_1), 5); " + + "ds_11 := ds1[keep string1, string2]; " + + "ds_22 := ds2[keep string1][calc string2 := string1]; " + + "res21 := ds_11 || ds_22; "); + var res = engine.getContext().getAttribute("res21"); + assertThat(((Dataset) res).getDataStructure().get("string1").getType()).isEqualTo(String.class); + } + + @Test + public void testPlan() throws ScriptException { + engine.getContext().setAttribute("ds_1", DatasetSamples.ds1, ScriptContext.ENGINE_SCOPE); + engine.getContext().setAttribute("ds_2", DatasetSamples.ds2, ScriptContext.ENGINE_SCOPE); + engine.eval( + "ds1 := ds_1[keep long1][rename long1 to bool_var]; " + + "ds2 := ds_2[keep long1][rename long1 to bool_var]; " + + "res := if ds1 > ds2 then ds1 else ds2;"); + var res = engine.getContext().getAttribute("res"); + assertThat(((Dataset) res).getDataAsMap()) + .containsExactlyInAnyOrder( + Map.of("id", "Hadrien", "bool_var", 150L), + Map.of("id", "Nico", "bool_var", 20L), + Map.of("id", "Franck", "bool_var", 100L)); + assertThat(((Dataset) res).getDataStructure().get("bool_var").getType()).isEqualTo(Long.class); + } +} diff --git a/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/PivotTest.java b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/PivotTest.java new file mode 100644 index 000000000..e84d95d38 --- /dev/null +++ b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/PivotTest.java @@ -0,0 +1,108 @@ +package fr.insee.vtl.spark.processing.engine; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import fr.insee.vtl.engine.VtlScriptEngine; +import fr.insee.vtl.model.Dataset; +import fr.insee.vtl.model.InMemoryDataset; +import fr.insee.vtl.model.Structured; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import javax.script.ScriptContext; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import org.apache.spark.sql.SparkSession; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class PivotTest { + + private SparkSession spark; + private ScriptEngine engine; + + @BeforeEach + public void setUp() { + + ScriptEngineManager mgr = new ScriptEngineManager(); + engine = mgr.getEngineByExtension("vtl"); + + spark = SparkSession.builder().appName("test").master("local").getOrCreate(); + SparkSession.setActiveSession(spark); + + engine.put(VtlScriptEngine.PROCESSING_ENGINE_NAMES, "spark4"); + } + + @AfterEach + public void tearDown() throws IOException { + if (spark != null) spark.close(); + } + + @Test + public void testPivot() throws ScriptException { + + var dataset = + new InMemoryDataset( + List.of( + List.of(1L, "A", 5L, "E"), + List.of(1L, "B", 2L, "F"), + List.of(1L, "C", 7L, "F"), + List.of(2L, "A", 3L, "E"), + List.of(2L, "B", 4L, "E"), + List.of(2L, "C", 9L, "F")), + List.of( + new Structured.Component("Id_1", Long.class, Dataset.Role.IDENTIFIER), + new Structured.Component("Id_2", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("Me_1", Long.class, Dataset.Role.MEASURE), + new Structured.Component("At_1", String.class, Dataset.Role.ATTRIBUTE))); + + var dataset2 = + new InMemoryDataset( + List.of( + List.of(1L, 1L, 5L, "E"), + List.of(1L, 2L, 2L, "F"), + List.of(1L, 3L, 7L, "F"), + List.of(2L, 1L, 3L, "E"), + List.of(2L, 2L, 4L, "E"), + List.of(2L, 3L, 9L, "F")), + List.of( + new Structured.Component("Id_1", Long.class, Dataset.Role.IDENTIFIER), + new Structured.Component("Id_2", Long.class, Dataset.Role.IDENTIFIER), + new Structured.Component("Me_1", Long.class, Dataset.Role.MEASURE), + new Structured.Component("At_1", String.class, Dataset.Role.ATTRIBUTE))); + + ScriptContext context = engine.getContext(); + context.getBindings(ScriptContext.ENGINE_SCOPE).put("Ds_1", dataset); + context.getBindings(ScriptContext.ENGINE_SCOPE).put("Ds_2", dataset2); + + engine.eval("DS_r := Ds_1 [ pivot Id_2, Me_1 ];" + "DS_r2 := Ds_2 [ pivot Id_2, Me_1 ];"); + + var result = (Dataset) context.getAttribute("DS_r"); + assertThat(result.getDataAsList()) + .containsExactlyInAnyOrder(Arrays.asList(1L, 5L, 2L, 7L), Arrays.asList(2L, 3L, 4L, 9L)); + + var result2 = (Dataset) context.getAttribute("DS_r2"); + assertThat(result2.getDataAsList()) + .containsExactlyInAnyOrder(Arrays.asList(1L, 5L, 2L, 7L), Arrays.asList(2L, 3L, 4L, 9L)); + + Exception exception = + assertThrows( + fr.insee.vtl.engine.exceptions.InvalidArgumentException.class, + () -> engine.eval("DS_id_issue := Ds_1 [ pivot bad, Me_1 ];")); + String expectedMessage = "bad is not part of the dataset identifiers"; + String actualMessage = exception.getMessage(); + assertTrue(actualMessage.contains(expectedMessage)); + + Exception exceptionMe = + assertThrows( + fr.insee.vtl.engine.exceptions.InvalidArgumentException.class, + () -> engine.eval("DS_id_issue := Ds_1 [ pivot Id_2, bad ];")); + String expectedMessageMe = "bad is not part of the dataset measures"; + String actualMessageMe = exceptionMe.getMessage(); + assertTrue(actualMessageMe.contains(expectedMessageMe)); + } +} diff --git a/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/ProjectTest.java b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/ProjectTest.java new file mode 100644 index 000000000..29dcb9e10 --- /dev/null +++ b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/ProjectTest.java @@ -0,0 +1,89 @@ +package fr.insee.vtl.spark.processing.engine; + +import static org.assertj.core.api.Assertions.assertThat; + +import fr.insee.vtl.engine.VtlScriptEngine; +import fr.insee.vtl.model.Dataset; +import fr.insee.vtl.model.InMemoryDataset; +import fr.insee.vtl.model.Structured; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import javax.script.ScriptContext; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import org.apache.spark.sql.SparkSession; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class ProjectTest { + + private final InMemoryDataset dataset = + new InMemoryDataset( + List.of( + Map.of("name", "Hadrien", "age", 10L, "weight", 11L), + Map.of("name", "Nico", "age", 11L, "weight", 10L), + Map.of("name", "Franck", "age", 12L, "weight", 9L)), + Map.of("name", String.class, "age", Long.class, "weight", Long.class), + Map.of( + "name", + Dataset.Role.IDENTIFIER, + "age", + Dataset.Role.MEASURE, + "weight", + Dataset.Role.MEASURE)); + private SparkSession spark; + private ScriptEngine engine; + + @BeforeEach + public void setUp() { + + ScriptEngineManager mgr = new ScriptEngineManager(); + engine = mgr.getEngineByExtension("vtl"); + + spark = SparkSession.builder().appName("test").master("local").getOrCreate(); + SparkSession.setActiveSession(spark); + + engine.put(VtlScriptEngine.PROCESSING_ENGINE_NAMES, "spark4"); + } + + @AfterEach + public void tearDown() throws IOException { + if (spark != null) spark.close(); + } + + @Test + public void testProjection() throws ScriptException { + + ScriptContext context = engine.getContext(); + context.setAttribute("ds", dataset, ScriptContext.ENGINE_SCOPE); + + engine.eval("ds1 := ds[keep age];"); + + assertThat(engine.getContext().getAttribute("ds")) + .isInstanceOf(fr.insee.vtl.model.Dataset.class); + assertThat( + ((fr.insee.vtl.model.Dataset) engine.getContext().getAttribute("ds1")).getDataAsMap()) + .containsExactly( + Map.of("name", "Hadrien", "age", 10L), + Map.of("name", "Nico", "age", 11L), + Map.of("name", "Franck", "age", 12L)); + + engine.eval("ds2 := ds[drop weight];"); + + assertThat(engine.getContext().getAttribute("ds2")) + .isInstanceOf(fr.insee.vtl.model.Dataset.class); + var ds = (Dataset) engine.getContext().getAttribute("ds2"); + assertThat(ds.getDataAsMap()) + .containsExactly( + Map.of("name", "Hadrien", "age", 10L), + Map.of("name", "Nico", "age", 11L), + Map.of("name", "Franck", "age", 12L)); + assertThat(ds.getDataStructure()) + .containsValues( + new Structured.Component("name", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("age", Long.class, Dataset.Role.MEASURE)); + } +} diff --git a/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/RenameTest.java b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/RenameTest.java new file mode 100644 index 000000000..531380a6f --- /dev/null +++ b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/RenameTest.java @@ -0,0 +1,78 @@ +package fr.insee.vtl.spark.processing.engine; + +import static org.assertj.core.api.Assertions.assertThat; + +import fr.insee.vtl.engine.VtlScriptEngine; +import fr.insee.vtl.model.Dataset; +import fr.insee.vtl.model.InMemoryDataset; +import fr.insee.vtl.model.Structured; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import javax.script.ScriptContext; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import org.apache.spark.sql.SparkSession; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class RenameTest { + + private final InMemoryDataset dataset = + new InMemoryDataset( + List.of( + Map.of("name", "Hadrien", "age", 10L, "weight", 11L), + Map.of("name", "Nico", "age", 11L, "weight", 10L), + Map.of("name", "Franck", "age", 12L, "weight", 9L)), + Map.of("name", String.class, "age", Long.class, "weight", Long.class), + Map.of( + "name", + Dataset.Role.IDENTIFIER, + "age", + Dataset.Role.MEASURE, + "weight", + Dataset.Role.MEASURE)); + private SparkSession spark; + private ScriptEngine engine; + + @BeforeEach + public void setUp() { + + ScriptEngineManager mgr = new ScriptEngineManager(); + engine = mgr.getEngineByExtension("vtl"); + + spark = SparkSession.builder().appName("test").master("local").getOrCreate(); + SparkSession.setActiveSession(spark); + + engine.put(VtlScriptEngine.PROCESSING_ENGINE_NAMES, "spark4"); + } + + @AfterEach + public void tearDown() throws IOException { + if (spark != null) spark.close(); + } + + @Test + public void testRename() throws ScriptException { + + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", dataset, ScriptContext.ENGINE_SCOPE); + + engine.eval("ds := ds1[rename age to weight, weight to age, name to pseudo];"); + + assertThat(engine.getContext().getAttribute("ds")).isInstanceOf(Dataset.class); + var ds = (Dataset) engine.getContext().getAttribute("ds"); + assertThat(ds.getDataAsMap()) + .containsExactlyInAnyOrder( + Map.of("pseudo", "Hadrien", "weight", 10L, "age", 11L), + Map.of("pseudo", "Nico", "weight", 11L, "age", 10L), + Map.of("pseudo", "Franck", "weight", 12L, "age", 9L)); + assertThat(ds.getDataStructure()) + .containsValues( + new Structured.Component("pseudo", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("age", Long.class, Dataset.Role.MEASURE), + new Structured.Component("weight", Long.class, Dataset.Role.MEASURE)); + } +} diff --git a/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/ServiceLoaderTest.java b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/ServiceLoaderTest.java new file mode 100644 index 000000000..05083e92e --- /dev/null +++ b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/ServiceLoaderTest.java @@ -0,0 +1,22 @@ +package fr.insee.vtl.spark.processing.engine; + +import static org.assertj.core.api.Assertions.assertThat; + +import fr.insee.vtl.model.ProcessingEngineFactory; +import java.util.List; +import java.util.ServiceLoader; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Test; + +public class ServiceLoaderTest { + + @Test + public void testServiceLoader() { + List processingEngines = + ServiceLoader.load(ProcessingEngineFactory.class).stream() + .map(ServiceLoader.Provider::get) + .map(ProcessingEngineFactory::getName) + .collect(Collectors.toList()); + assertThat(processingEngines).containsExactlyInAnyOrder("memory", "spark4"); + } +} diff --git a/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/UnionTest.java b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/UnionTest.java new file mode 100644 index 000000000..5ea92a9c0 --- /dev/null +++ b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/UnionTest.java @@ -0,0 +1,477 @@ +package fr.insee.vtl.spark.processing.engine; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import fr.insee.vtl.engine.VtlScriptEngine; +import fr.insee.vtl.model.Dataset; +import fr.insee.vtl.model.InMemoryDataset; +import fr.insee.vtl.model.Structured; +import fr.insee.vtl.spark.SparkDataset; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import javax.script.ScriptContext; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import org.apache.spark.sql.SparkSession; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class UnionTest { + + private final InMemoryDataset unionDS1 = + new InMemoryDataset( + List.of( + Map.of("Id_1", "2012", "Id_2", "B", "Id_3", "Total", "Id_4", "Total", "Me_1", 5L), + Map.of("Id_1", "2012", "Id_2", "G", "Id_3", "Total", "Id_4", "Total", "Me_1", 2L), + Map.of("Id_1", "2012", "Id_2", "F", "Id_3", "Total", "Id_4", "Total", "Me_1", 3L)), + Map.of( + "Id_1", + String.class, + "Id_2", + String.class, + "Id_3", + String.class, + "Id_4", + String.class, + "Me_1", + Long.class), + Map.of( + "Id_1", + Dataset.Role.IDENTIFIER, + "Id_2", + Dataset.Role.IDENTIFIER, + "Id_3", + Dataset.Role.IDENTIFIER, + "Id_4", + Dataset.Role.IDENTIFIER, + "Me_1", + Dataset.Role.MEASURE)); + private final InMemoryDataset unionDS2 = + new InMemoryDataset( + List.of( + Map.of("Id_1", "2012", "Id_2", "N", "Id_3", "Total", "Id_4", "Total", "Me_1", 23L), + Map.of("Id_1", "2012", "Id_2", "S", "Id_3", "Total", "Id_4", "Total", "Me_1", 5L)), + Map.of( + "Id_1", + String.class, + "Id_2", + String.class, + "Id_3", + String.class, + "Id_4", + String.class, + "Me_1", + Long.class), + Map.of( + "Id_1", + Dataset.Role.IDENTIFIER, + "Id_2", + Dataset.Role.IDENTIFIER, + "Id_3", + Dataset.Role.IDENTIFIER, + "Id_4", + Dataset.Role.IDENTIFIER, + "Me_1", + Dataset.Role.MEASURE)); + private final InMemoryDataset unionDS3 = + new InMemoryDataset( + List.of( + Map.of("Id_1", "2012", "Id_2", "L", "Id_3", "Total", "Id_4", "Total", "Me_1", 5L), + Map.of("Id_1", "2012", "Id_2", "M", "Id_3", "Total", "Id_4", "Total", "Me_1", 2L), + Map.of("Id_1", "2012", "Id_2", "X", "Id_3", "Total", "Id_4", "Total", "Me_1", 3L)), + Map.of( + "Id_1", + String.class, + "Id_2", + String.class, + "Id_3", + String.class, + "Id_4", + String.class, + "Me_1", + Long.class), + Map.of( + "Id_1", + Dataset.Role.IDENTIFIER, + "Id_2", + Dataset.Role.IDENTIFIER, + "Id_3", + Dataset.Role.IDENTIFIER, + "Id_4", + Dataset.Role.IDENTIFIER, + "Me_1", + Dataset.Role.MEASURE)); + private final InMemoryDataset unionDS4 = + new InMemoryDataset( + List.of( + Map.of("Id_1", "2012", "Id_2", "N", "Id_3", "Total", "Id_4", "Total", "Me_1", 5L), + Map.of("Id_1", "2012", "Id_2", "S", "Id_3", "Total", "Id_4", "Total", "Me_1", 2L), + Map.of("Id_1", "2012", "Id_2", "X", "Id_3", "Total", "Id_4", "Total", "Me_1", 3L)), + Map.of( + "Id_1", + String.class, + "Id_2", + String.class, + "Id_3", + String.class, + "Id_4", + String.class, + "Me_1", + Long.class), + Map.of( + "Id_1", + Dataset.Role.IDENTIFIER, + "Id_2", + Dataset.Role.IDENTIFIER, + "Id_3", + Dataset.Role.IDENTIFIER, + "Id_4", + Dataset.Role.IDENTIFIER, + "Me_1", + Dataset.Role.MEASURE)); + private final InMemoryDataset unionDS5 = + new InMemoryDataset( + List.of( + Map.of("Id_1", "2012", "Id_2", "N", "Id_3", "Total", "Me_1", 5L), + Map.of("Id_1", "2012", "Id_2", "S", "Id_3", "Total", "Me_1", 2L), + Map.of("Id_1", "2012", "Id_2", "X", "Id_3", "Total", "Me_1", 3L)), + Map.of( + "Id_1", String.class, "Id_2", String.class, "Id_3", String.class, "Me_1", Long.class), + Map.of( + "Id_1", + Dataset.Role.IDENTIFIER, + "Id_2", + Dataset.Role.IDENTIFIER, + "Id_3", + Dataset.Role.IDENTIFIER, + "Me_1", + Dataset.Role.MEASURE)); + private final String DEFAULT_NULL_STR = "null"; + private SparkSession spark; + private ScriptEngine engine; + + private static Map replaceNullValues(Map map, T defaultValue) { + + // Replace the null value + map = + map.entrySet().stream() + .map( + entry -> { + if (entry.getValue() == null) entry.setValue(defaultValue); + return entry; + }) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + return map; + } + + @BeforeEach + public void setUp() { + + ScriptEngineManager mgr = new ScriptEngineManager(); + engine = mgr.getEngineByExtension("vtl"); + + spark = SparkSession.builder().appName("test").master("local").getOrCreate(); + SparkSession.setActiveSession(spark); + + engine.put(VtlScriptEngine.PROCESSING_ENGINE_NAMES, "spark4"); + } + + @AfterEach + public void tearDown() throws IOException { + if (spark != null) spark.close(); + } + + @Test + public void testDataStructureAfterUnion() throws ScriptException { + // Union Test case 1 : After union, the result should have the same data structure as the input + // dataframe + /* Input dataset ds1 and ds2 has below data structure + Component{Id_1, type=class java.lang.String, role=IDENTIFIER} + Component{Id_2, type=class java.lang.String, role=IDENTIFIER} + Component{Id_3, type=class java.lang.String, role=IDENTIFIER} + Component{Id_4, type=class java.lang.String, role=IDENTIFIER} + Component{Me_1, type=class java.lang.Long, role=MEASURE} + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", unionDS1, ScriptContext.ENGINE_SCOPE); + context.setAttribute("ds2", unionDS2, ScriptContext.ENGINE_SCOPE); + engine.eval("res := union (ds1, ds2);"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * the result df should have the same data structure + * */ + + Collection actualStructure = + ((SparkDataset) engine.getContext().getAttribute("res")).getDataStructure().values(); + + assertThat(actualStructure) + .containsExactlyInAnyOrder( + new Structured.Component("Id_1", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("Id_2", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("Id_3", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("Id_4", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("Me_1", Long.class, Dataset.Role.MEASURE)); + } + + @Test + public void testUnionTwoWithoutDup() throws ScriptException { + // Union Test case 1 : union on two df without duplicate + /* Input dataset ds1 + +----+----+-----+-----+----+ + |Id_1|Id_2| Id_3| Id_4|Me_1| + +----+----+-----+-----+----+ + |2012| B|Total|Total| 5| + |2012| G|Total|Total| 2| + |2012| F|Total|Total| 3| + +----+----+-----+-----+----+ + + **************************** + * Input dataset ds2 + +----+----+-----+-----+----+ + |Id_1|Id_2| Id_3| Id_4|Me_1| + +----+----+-----+-----+----+ + |2012| N|Total|Total| 23| + |2012| S|Total|Total| 5| + +----+----+-----+-----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", unionDS1, ScriptContext.ENGINE_SCOPE); + context.setAttribute("ds2", unionDS2, ScriptContext.ENGINE_SCOPE); + engine.eval("res := union (ds1, ds2);"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * result df + +----+----+-----+-----+----+ + |Id_1|Id_2| Id_3| Id_4|Me_1| + +----+----+-----+-----+----+ + |2012| B|Total|Total| 5| + |2012| G|Total|Total| 2| + |2012| F|Total|Total| 3| + |2012| N|Total|Total| 23| + |2012| S|Total|Total| 5| + +----+----+-----+-----+----+ + * */ + + List> actualWithNull = + ((Dataset) engine.getContext().getAttribute("res")).getDataAsMap(); + + List> actual = new ArrayList<>(); + for (Map map : actualWithNull) { + actual.add(replaceNullValues(map, DEFAULT_NULL_STR)); + } + List> expected = + List.of( + Map.of("Id_1", "2012", "Id_2", "B", "Id_3", "Total", "Id_4", "Total", "Me_1", 5L), + Map.of("Id_1", "2012", "Id_2", "G", "Id_3", "Total", "Id_4", "Total", "Me_1", 2L), + Map.of("Id_1", "2012", "Id_2", "F", "Id_3", "Total", "Id_4", "Total", "Me_1", 3L), + Map.of("Id_1", "2012", "Id_2", "N", "Id_3", "Total", "Id_4", "Total", "Me_1", 23L), + Map.of("Id_1", "2012", "Id_2", "S", "Id_3", "Total", "Id_4", "Total", "Me_1", 5L)); + assertEquals(new HashSet<>(actual), new HashSet<>(expected)); + } + + @Test + public void testUnionThreeWithoutDup() throws ScriptException { + // Union Test case 2 : union on three df without duplicate + /* Input dataset ds1 + +----+----+-----+-----+----+ + |Id_1|Id_2| Id_3| Id_4|Me_1| + +----+----+-----+-----+----+ + |2012| B|Total|Total| 5| + |2012| G|Total|Total| 2| + |2012| F|Total|Total| 3| + +----+----+-----+-----+----+ + + **************************** + * Input dataset ds2 + +----+----+-----+-----+----+ + |Id_1|Id_2| Id_3| Id_4|Me_1| + +----+----+-----+-----+----+ + |2012| N|Total|Total| 23| + |2012| S|Total|Total| 5| + +----+----+-----+-----+----+ + + ************************** + * Input dataset ds3 + +----+----+-----+-----+----+ + |Id_1|Id_2| Id_3| Id_4|Me_1| + +----+----+-----+-----+----+ + |2012| L|Total|Total| 5| + |2012| M|Total|Total| 2| + |2012| X|Total|Total| 3| + +----+----+-----+-----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", unionDS1, ScriptContext.ENGINE_SCOPE); + context.setAttribute("ds2", unionDS2, ScriptContext.ENGINE_SCOPE); + context.setAttribute("ds3", unionDS3, ScriptContext.ENGINE_SCOPE); + engine.eval("res := union (ds1, ds2, ds3);"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * result df + +----+----+-----+-----+----+ + |Id_1|Id_2| Id_3| Id_4|Me_1| + +----+----+-----+-----+----+ + |2012| B|Total|Total| 5| + |2012| G|Total|Total| 2| + |2012| F|Total|Total| 3| + |2012| N|Total|Total| 23| + |2012| S|Total|Total| 5| + +----+----+-----+-----+----+ + * */ + + List> actualWithNull = + ((Dataset) engine.getContext().getAttribute("res")).getDataAsMap(); + + List> actual = new ArrayList<>(); + for (Map map : actualWithNull) { + actual.add(replaceNullValues(map, DEFAULT_NULL_STR)); + } + List> expected = + List.of( + Map.of("Id_1", "2012", "Id_2", "B", "Id_3", "Total", "Id_4", "Total", "Me_1", 5L), + Map.of("Id_1", "2012", "Id_2", "G", "Id_3", "Total", "Id_4", "Total", "Me_1", 2L), + Map.of("Id_1", "2012", "Id_2", "F", "Id_3", "Total", "Id_4", "Total", "Me_1", 3L), + Map.of("Id_1", "2012", "Id_2", "S", "Id_3", "Total", "Id_4", "Total", "Me_1", 5L), + Map.of("Id_1", "2012", "Id_2", "N", "Id_3", "Total", "Id_4", "Total", "Me_1", 23L), + Map.of("Id_1", "2012", "Id_2", "X", "Id_3", "Total", "Id_4", "Total", "Me_1", 3L), + Map.of("Id_1", "2012", "Id_2", "M", "Id_3", "Total", "Id_4", "Total", "Me_1", 2L), + Map.of("Id_1", "2012", "Id_2", "L", "Id_3", "Total", "Id_4", "Total", "Me_1", 5L)); + assertEquals(new HashSet<>(actual), new HashSet<>(expected)); + } + + @Test + public void testUnionThreeWithDup() throws ScriptException { + // Union Test case 3 : union on three df with duplicate, note for the duplicated rows, the first + // appeared row + // are kept, the rest all dropped + /* Input dataset ds1 + +----+----+-----+-----+----+ + |Id_1|Id_2| Id_3| Id_4|Me_1| + +----+----+-----+-----+----+ + |2012| B|Total|Total| 5| + |2012| G|Total|Total| 2| + |2012| F|Total|Total| 3| + +----+----+-----+-----+----+ + + **************************** + * Input dataset ds2 + +----+----+-----+-----+----+ + |Id_1|Id_2| Id_3| Id_4|Me_1| + +----+----+-----+-----+----+ + |2012| N|Total|Total| 23| + |2012| S|Total|Total| 5| + +----+----+-----+-----+----+ + + ************************** + * Input dataset ds4 + +----+----+-----+-----+----+ + |Id_1|Id_2| Id_3| Id_4|Me_1| + +----+----+-----+-----+----+ + |2012| N|Total|Total| 5| + |2012| S|Total|Total| 2| + |2012| X|Total|Total| 3| + +----+----+-----+-----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", unionDS1, ScriptContext.ENGINE_SCOPE); + context.setAttribute("ds2", unionDS2, ScriptContext.ENGINE_SCOPE); + context.setAttribute("ds4", unionDS4, ScriptContext.ENGINE_SCOPE); + engine.eval("res := union (ds1, ds2, ds4);"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * result df + +----+----+-----+-----+----+ + |Id_1|Id_2| Id_3| Id_4|Me_1| + +----+----+-----+-----+----+ + |2012| F|Total|Total| 3| + |2012| B|Total|Total| 5| + |2012| G|Total|Total| 2| + |2012| N|Total|Total| 23| + |2012| S|Total|Total| 5| + |2012| X|Total|Total| 3| + +----+----+-----+-----+----+ + * */ + + List> actualWithNull = + ((Dataset) engine.getContext().getAttribute("res")).getDataAsMap(); + + List> actual = new ArrayList<>(); + for (Map map : actualWithNull) { + actual.add(replaceNullValues(map, DEFAULT_NULL_STR)); + } + List> expected = + List.of( + Map.of("Id_1", "2012", "Id_2", "G", "Id_3", "Total", "Id_4", "Total", "Me_1", 2L), + Map.of("Id_1", "2012", "Id_2", "F", "Id_3", "Total", "Id_4", "Total", "Me_1", 3L), + Map.of("Id_1", "2012", "Id_2", "B", "Id_3", "Total", "Id_4", "Total", "Me_1", 5L), + Map.of("Id_1", "2012", "Id_2", "S", "Id_3", "Total", "Id_4", "Total", "Me_1", 5L), + Map.of("Id_1", "2012", "Id_2", "N", "Id_3", "Total", "Id_4", "Total", "Me_1", 23L), + Map.of("Id_1", "2012", "Id_2", "X", "Id_3", "Total", "Id_4", "Total", "Me_1", 3L)); + assertEquals(new HashSet<>(actual), new HashSet<>(expected)); + } + + @Test + public void testUnionIncompatibleSchema() throws ScriptException { + // Union Test case 4 : union on three df with incompatible schema, should throw exception + // here we assert if the right exception is thrown. + /* Input dataset ds1 + +----+----+-----+-----+----+ + |Id_1|Id_2| Id_3| Id_4|Me_1| + +----+----+-----+-----+----+ + |2012| B|Total|Total| 5| + |2012| G|Total|Total| 2| + |2012| F|Total|Total| 3| + +----+----+-----+-----+----+ + + **************************** + * Input dataset ds2 + +----+----+-----+-----+----+ + |Id_1|Id_2| Id_3| Id_4|Me_1| + +----+----+-----+-----+----+ + |2012| N|Total|Total| 23| + |2012| S|Total|Total| 5| + +----+----+-----+-----+----+ + + ************************** + * Input dataset ds5 + +----+----+-----+----+ + |Id_1|Id_2| Id_3|Me_1| + +----+----+-----+----+ + |2012| N|Total| 5| + |2012| S|Total| 2| + |2012| X|Total| 3| + +----+----+-----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", unionDS1, ScriptContext.ENGINE_SCOPE); + context.setAttribute("ds2", unionDS2, ScriptContext.ENGINE_SCOPE); + context.setAttribute("ds5", unionDS5, ScriptContext.ENGINE_SCOPE); + + // engine.getContext().getAttribute("res"); + Exception exception = + assertThrows( + fr.insee.vtl.engine.exceptions.InvalidArgumentException.class, + () -> { + engine.eval("res := union ( ds1, ds2, ds5 ) ;"); + }); + String expectedMessage = "dataset structure of ds5 is incompatible"; + String actualMessage = exception.getMessage(); + System.out.println(actualMessage); + assertTrue(actualMessage.contains(expectedMessage)); + } +} diff --git a/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/ValidationTest.java b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/ValidationTest.java new file mode 100644 index 000000000..9f0ea2d5e --- /dev/null +++ b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/ValidationTest.java @@ -0,0 +1,2042 @@ +package fr.insee.vtl.spark.processing.engine; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import fr.insee.vtl.engine.VtlScriptEngine; +import fr.insee.vtl.model.Dataset; +import fr.insee.vtl.model.InMemoryDataset; +import fr.insee.vtl.model.Structured; +import fr.insee.vtl.spark.SparkDataset; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.script.*; +import org.apache.spark.sql.Row; +import org.apache.spark.sql.SparkSession; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class ValidationTest { + + private final String DEFAULT_NULL_STR = "null"; + private final InMemoryDataset dataset = + new InMemoryDataset( + List.of( + List.of("2011", "I", "CREDIT", 10L), + List.of("2011", "I", "DEBIT", -2L), + List.of("2012", "I", "CREDIT", 10L), + List.of("2012", "I", "DEBIT", 2L)), + List.of( + new Structured.Component("Id_1", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("Id_2", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component( + "Id_3", String.class, Dataset.Role.IDENTIFIER, null, "vd_id_3"), + new Structured.Component("Me_1", Long.class, Dataset.Role.MEASURE, null, "vd_me_1"))); + private final InMemoryDataset ds_1_check = + new InMemoryDataset( + List.of( + List.of("2010", "I", 1L), + List.of("2011", "I", 2L), + List.of("2012", "I", 10L), + List.of("2013", "I", 4L), + List.of("2014", "I", 5L), + List.of("2015", "I", 6L), + List.of("2010", "D", 25L), + List.of("2011", "D", 35L), + List.of("2012", "D", 45L), + List.of("2013", "D", 55L), + List.of("2014", "D", 50L), + List.of("2015", "D", 75L)), + List.of( + new Structured.Component("Id_1", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("Id_2", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("Me_1", Long.class, Dataset.Role.MEASURE))); + private final InMemoryDataset ds_2_check = + new InMemoryDataset( + List.of( + List.of("2010", "I", 9L), + List.of("2011", "I", 2L), + List.of("2012", "I", 10L), + List.of("2013", "I", 7L), + List.of("2014", "I", 5L), + List.of("2015", "I", 6L), + List.of("2010", "D", 50L), + List.of("2011", "D", 35L), + List.of("2012", "D", 40L), + List.of("2013", "D", 55L), + List.of("2014", "D", 65L), + List.of("2015", "D", 75L)), + List.of( + new Structured.Component("Id_1", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("Id_2", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("Me_1", Long.class, Dataset.Role.MEASURE))); + private final Dataset dsExpr = + new InMemoryDataset( + List.of( + List.of("2011", "I", "CREDIT", true), + List.of("2011", "I", "DEBIT", false), + List.of("2012", "I", "CREDIT", false), + List.of("2012", "I", "DEBIT", true)), + List.of( + new Structured.Component("Id_1", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("Id_2", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("Id_3", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("bool_var", Boolean.class, Dataset.Role.MEASURE))); + private final Dataset dsImbalance = + new InMemoryDataset( + List.of( + List.of("2011", "I", "CREDIT", 1L), + List.of("2011", "I", "DEBIT", 2L), + List.of("2012", "I", "CREDIT", 2L), + List.of("2012", "I", "DEBIT", 3L)), + List.of( + new Structured.Component("Id_1", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("Id_2", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("Id_3", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("imbalance", Long.class, Dataset.Role.MEASURE))); + private final Dataset dsImbalanceToRename = + new InMemoryDataset( + List.of( + List.of("2011", "I", "CREDIT", 1L), + List.of("2011", "I", "DEBIT", 2L), + List.of("2012", "I", "CREDIT", 2L), + List.of("2012", "I", "DEBIT", 3L)), + List.of( + new Structured.Component("Id_1", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("Id_2", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("Id_3", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("imbalance_1", Long.class, Dataset.Role.MEASURE))); + private final String hierarchicalRulesetDef = + """ + define hierarchical ruleset HR_1 (variable rule Me_1) is\s + R010 : A = J + K + L errorlevel 5; + R020 : B = M + N + O errorlevel 5; + R030 : C = P + Q errorcode "XX" errorlevel 5; + R040 : D = R + S errorlevel 1; + R050 : E = T + U + V errorlevel 0; + R060 : F = Y + W + Z errorlevel 7; + R070 : G = B + C; + R080 : H = D + E errorlevel 0; + R090 : I = D + G errorcode "YY" errorlevel 0; + R100 : M >= N errorlevel 5; + R110 : M <= G errorlevel 5 + end hierarchical ruleset;\s + """; + private final Dataset DS_1_HR = + new InMemoryDataset( + List.of( + List.of("2010", "A", 5L), + List.of("2010", "B", 11L), + List.of("2010", "C", 0L), + List.of("2010", "G", 19L), + Stream.of("2010", "H", null).collect(Collectors.toList()), + List.of("2010", "I", 14L), + List.of("2010", "M", 2L), + List.of("2010", "N", 5L), + List.of("2010", "O", 4L), + List.of("2010", "P", 7L), + List.of("2010", "Q", -7L), + List.of("2010", "S", 3L), + List.of("2010", "T", 9L), + Stream.of("2010", "U", null).collect(Collectors.toList()), + List.of("2010", "V", 6L)), + List.of( + new Structured.Component("Id_1", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("Id_2", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("Me_1", Long.class, Dataset.Role.MEASURE))); + private static SparkSession spark; + private ScriptEngine engine; + + private static Map replaceNullValues(Map map, T defaultValue) { + + // Replace the null value + map = + map.entrySet().stream() + .map( + entry -> { + if (entry.getValue() == null) entry.setValue(defaultValue); + return entry; + }) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + return map; + } + + @BeforeEach + public void setUp() { + + ScriptEngineManager mgr = new ScriptEngineManager(); + engine = mgr.getEngineByExtension("vtl"); + + spark = SparkSession.builder().appName("test").master("local").getOrCreate(); + SparkSession.setActiveSession(spark); + + engine.put(VtlScriptEngine.PROCESSING_ENGINE_NAMES, "spark4"); + } + + @AfterAll + public static void tearDown() throws IOException { + if (spark != null) spark.close(); + } + + @Test + public void testValidateDPruleset() throws ScriptException { + + ScriptContext context = engine.getContext(); + context.setAttribute("DS_1", dataset, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "define datapoint ruleset dpr1 (variable Id_3, Me_1) is " + + "ruleA : when Id_3 = \"CREDIT\" then Me_1 >= 0 errorcode \"Bad credit\"; " + + "when Id_3 = \"DEBIT\" then Me_1 >= 0 errorcode \"Bad debit\" errorlevel 1 " + + "end datapoint ruleset; " + + "DS_r := check_datapoint(DS_1, dpr1); " + + "DS_r_invalid := check_datapoint(DS_1, dpr1 invalid); " + + "DS_r_all := check_datapoint(DS_1, dpr1 all); " + + "DS_r_all_measures := check_datapoint(DS_1, dpr1 all_measures);"); + + Dataset DS_r = (Dataset) engine.getContext().getAttribute("DS_r"); + assertThat(DS_r).isInstanceOf(Dataset.class); + Dataset DS_r_invalid = (Dataset) engine.getContext().getAttribute("DS_r_invalid"); + assertThat(DS_r_invalid).isInstanceOf(Dataset.class); + Dataset DS_r_all = (Dataset) engine.getContext().getAttribute("DS_r_all"); + assertThat(DS_r).isInstanceOf(Dataset.class); + Dataset DS_r_all_measures = (Dataset) engine.getContext().getAttribute("DS_r_all_measures"); + assertThat(DS_r_all_measures).isInstanceOf(Dataset.class); + + List> DS_rWithNull = DS_r.getDataAsMap(); + List> DS_r_invalidWithNull = DS_r_invalid.getDataAsMap(); + List> DS_r_allWithNull = DS_r_all.getDataAsMap(); + List> DS_r_all_measuresWithNull = DS_r_all_measures.getDataAsMap(); + + List> DS_rWithoutNull = new ArrayList<>(); + for (Map map : DS_rWithNull) { + DS_rWithoutNull.add(replaceNullValues(map, DEFAULT_NULL_STR)); + } + List> DS_r_invalidWithoutNull = new ArrayList<>(); + for (Map map : DS_r_invalidWithNull) { + DS_r_invalidWithoutNull.add(replaceNullValues(map, DEFAULT_NULL_STR)); + } + List> DS_r_allWithoutNull = new ArrayList<>(); + for (Map map : DS_r_allWithNull) { + DS_r_allWithoutNull.add(replaceNullValues(map, DEFAULT_NULL_STR)); + } + List> DS_r_all_measuresWithoutNull = new ArrayList<>(); + for (Map map : DS_r_all_measuresWithNull) { + DS_r_all_measuresWithoutNull.add(replaceNullValues(map, DEFAULT_NULL_STR)); + } + + assertThat(DS_rWithoutNull) + .containsExactlyInAnyOrder( + Map.of( + "Id_1", + "2011", + "Id_2", + "I", + "Id_3", + "DEBIT", + "Me_1", + -2L, + "ruleid", + "dpr1_2", + "errorcode", + "Bad debit", + "errorlevel", + 1L)) + .containsExactlyInAnyOrderElementsOf(DS_r_invalidWithoutNull); + + assertThat(DS_r_allWithoutNull) + .containsExactlyInAnyOrder( + Map.of( + "Id_1", + "2011", + "Id_2", + "I", + "Id_3", + "CREDIT", + "Me_1", + 10L, + "ruleid", + "ruleA", + "bool_var", + true, + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2011", + "Id_2", + "I", + "Id_3", + "CREDIT", + "Me_1", + 10L, + "ruleid", + "dpr1_2", + "bool_var", + true, + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2011", + "Id_2", + "I", + "Id_3", + "DEBIT", + "Me_1", + -2L, + "ruleid", + "ruleA", + "bool_var", + true, + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2011", + "Id_2", + "I", + "Id_3", + "DEBIT", + "Me_1", + -2L, + "ruleid", + "dpr1_2", + "bool_var", + false, + "errorcode", + "Bad debit", + "errorlevel", + 1L), + Map.of( + "Id_1", + "2012", + "Id_2", + "I", + "Id_3", + "CREDIT", + "Me_1", + 10L, + "ruleid", + "ruleA", + "bool_var", + true, + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2012", + "Id_2", + "I", + "Id_3", + "CREDIT", + "Me_1", + 10L, + "ruleid", + "dpr1_2", + "bool_var", + true, + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2012", + "Id_2", + "I", + "Id_3", + "DEBIT", + "Me_1", + 2L, + "ruleid", + "ruleA", + "bool_var", + true, + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2012", + "Id_2", + "I", + "Id_3", + "DEBIT", + "Me_1", + 2L, + "ruleid", + "dpr1_2", + "bool_var", + true, + "errorcode", + "null", + "errorlevel", + "null")) + .containsExactlyInAnyOrderElementsOf(DS_r_all_measuresWithoutNull); + } + + @Test + public void testValidateDPrulesetWithValuedomain() throws ScriptException { + + ScriptContext context = engine.getContext(); + context.setAttribute("DS_1", dataset, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "define datapoint ruleset dpr1 (valuedomain vd_id_3, vd_me_1 as vd) is " + + "ruleA : vd_id_3 = \"AA\" and vd > 0 errorcode \"CREDIT or DEBIT\" " + + "end datapoint ruleset; " + + "DS_r := check_datapoint(DS_1, dpr1); "); + + Dataset DS_r = (Dataset) engine.getContext().getAttribute("DS_r"); + assertThat(DS_r).isInstanceOf(Dataset.class); + assertThat(DS_r.getDataPoints().size()).isEqualTo(4); + } + + @Test + public void testValidateDPrulesetWithAlias() throws ScriptException { + + ScriptContext context = engine.getContext(); + context.setAttribute("DS_1", dataset, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "define datapoint ruleset dpr1 (variable Id_3 as AA, Me_1) is " + + "when AA = \"CREDIT\" then Me_1 >= 0 errorcode \"Bad credit\"; " + + "when AA = \"DEBIT\" then Me_1 >= 0 errorcode \"Bad debit\" " + + "end datapoint ruleset; " + + "DS_r := check_datapoint(DS_1, dpr1);"); + + Dataset DS_r = (Dataset) engine.getContext().getAttribute("DS_r"); + assertThat(DS_r).isInstanceOf(Dataset.class); + + List> DS_rWithNull = DS_r.getDataAsMap(); + List> DS_rWithoutNull = new ArrayList<>(); + for (Map map : DS_rWithNull) { + DS_rWithoutNull.add(replaceNullValues(map, DEFAULT_NULL_STR)); + } + + assertThat(DS_rWithoutNull) + .containsExactlyInAnyOrder( + Map.of( + "Id_1", + "2011", + "Id_2", + "I", + "Id_3", + "DEBIT", + "Me_1", + -2L, + "ruleid", + "dpr1_2", + "errorcode", + "Bad debit", + "errorlevel", + "null")); + } + + @Test + public void testCheck() throws ScriptException { + ScriptContext context = engine.getContext(); + context.setAttribute("DS1", ds_1_check, ScriptContext.ENGINE_SCOPE); + context.setAttribute("DS2", ds_2_check, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "ds := check(DS1 >= DS2 errorcode \"err\" errorlevel 1 imbalance DS1 - DS2);" + + "ds1 := check(DS1 >= DS2 errorcode \"err\" errorlevel 1 imbalance DS1 - DS2 invalid);"); + + var ds = (Dataset) engine.getContext().getAttribute("ds"); + assertThat(ds).isInstanceOf(Dataset.class); + + List> dsWithNull = ds.getDataAsMap(); + List> dsWithoutNull = new ArrayList<>(); + for (Map map : dsWithNull) { + dsWithoutNull.add(replaceNullValues(map, DEFAULT_NULL_STR)); + } + + assertThat(dsWithoutNull) + .isEqualTo( + List.of( + Map.of( + "Id_1", + "2010", + "Id_2", + "I", + "bool_var", + false, + "imbalance", + -8L, + "errorcode", + "err", + "errorlevel", + 1L), + Map.of( + "Id_1", + "2011", + "Id_2", + "I", + "bool_var", + true, + "imbalance", + 0L, + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2012", + "Id_2", + "I", + "bool_var", + true, + "imbalance", + 0L, + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2013", + "Id_2", + "I", + "bool_var", + false, + "imbalance", + -3L, + "errorcode", + "err", + "errorlevel", + 1L), + Map.of( + "Id_1", + "2014", + "Id_2", + "I", + "bool_var", + true, + "imbalance", + 0L, + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2015", + "Id_2", + "I", + "bool_var", + true, + "imbalance", + 0L, + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "D", + "bool_var", + false, + "imbalance", + -25L, + "errorcode", + "err", + "errorlevel", + 1L), + Map.of( + "Id_1", + "2011", + "Id_2", + "D", + "bool_var", + true, + "imbalance", + 0L, + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2012", + "Id_2", + "D", + "bool_var", + true, + "imbalance", + 5L, + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2013", + "Id_2", + "D", + "bool_var", + true, + "imbalance", + 0L, + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2014", + "Id_2", + "D", + "bool_var", + false, + "imbalance", + -15L, + "errorcode", + "err", + "errorlevel", + 1L), + Map.of( + "Id_1", + "2015", + "Id_2", + "D", + "bool_var", + true, + "imbalance", + 0L, + "errorcode", + "null", + "errorlevel", + "null"))); + assertThat(ds.getDataStructure()) + .containsValues( + new Structured.Component("Id_1", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("Id_2", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("bool_var", Boolean.class, Dataset.Role.MEASURE), + new Structured.Component("imbalance", Long.class, Dataset.Role.MEASURE), + new Structured.Component("errorcode", String.class, Dataset.Role.MEASURE), + new Structured.Component("errorlevel", Long.class, Dataset.Role.MEASURE)); + + var ds1 = (Dataset) engine.getContext().getAttribute("ds1"); + assertThat(ds1).isInstanceOf(Dataset.class); + + List> ds1WithNull = ds1.getDataAsMap(); + List> ds1WithoutNull = new ArrayList<>(); + for (Map map : ds1WithNull) { + ds1WithoutNull.add(replaceNullValues(map, DEFAULT_NULL_STR)); + } + + assertThat(ds1WithoutNull) + .isEqualTo( + List.of( + Map.of( + "Id_1", + "2010", + "Id_2", + "I", + "imbalance", + -8L, + "errorcode", + "err", + "errorlevel", + 1L), + Map.of( + "Id_1", + "2013", + "Id_2", + "I", + "imbalance", + -3L, + "errorcode", + "err", + "errorlevel", + 1L), + Map.of( + "Id_1", + "2010", + "Id_2", + "D", + "imbalance", + -25L, + "errorcode", + "err", + "errorlevel", + 1L), + Map.of( + "Id_1", + "2014", + "Id_2", + "D", + "imbalance", + -15L, + "errorcode", + "err", + "errorlevel", + 1L))); + } + + @Test + public void testValidationSimpleException() throws ScriptException { + + ScriptContext context = engine.getContext(); + context.setAttribute("dsExpr", dsExpr, ScriptContext.ENGINE_SCOPE); + context.setAttribute("dsImbalance", dsImbalance, ScriptContext.ENGINE_SCOPE); + context.setAttribute("dsImbalanceToRename", dsImbalanceToRename, ScriptContext.ENGINE_SCOPE); + + engine.eval("DS_r := check(dsExpr errorcode \"error\" errorlevel 1 imbalance dsImbalance);"); + engine.eval( + "DS_r_invalid := check(dsExpr errorcode \"error\" errorlevel 1 imbalance dsImbalance invalid);"); + engine.eval( + "DS_r_to_rename := check(dsExpr errorcode \"error\" errorlevel 1 imbalance dsImbalanceToRename);"); + Dataset DS_r = (Dataset) engine.getContext().getAttribute("DS_r"); + assertThat(DS_r.getDataAsMap().size()).isEqualTo(4); + Dataset DS_r_invalid = (Dataset) engine.getContext().getAttribute("DS_r_invalid"); + assertThat(DS_r_invalid.getDataAsMap().size()).isEqualTo(2); + Dataset DS_r_to_rename = (Dataset) engine.getContext().getAttribute("DS_r_to_rename"); + List DS_r_to_renameMeasure = + DS_r_to_rename.getDataStructure().values().stream() + .filter(Structured.Component::isMeasure) + .map(Structured.Component::getName) + .toList(); + assertThat(DS_r_to_renameMeasure.size()).isEqualTo(4); + assertThat(DS_r_to_renameMeasure.contains("imbalance")).isTrue(); + } + + @Test + public void serializationCheckDatapointTest() throws ScriptException { + ScriptContext context = engine.getContext(); + org.apache.spark.sql.Dataset ds1_csv = + spark + .read() + .option("delimiter", ";") + .option("header", "true") + .csv("src/main/resources/ds1.csv"); + SparkDataset sparkDataset1 = new SparkDataset(ds1_csv); + context.setAttribute("ds1", sparkDataset1, ScriptContext.ENGINE_SCOPE); + org.apache.spark.sql.Dataset ds2_csv = + spark + .read() + .option("delimiter", ";") + .option("header", "true") + .csv("src/main/resources/ds2.csv"); + SparkDataset sparkDataset2 = new SparkDataset(ds1_csv); + context.setAttribute("ds2", sparkDataset2, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "ds1_1 := ds1[calc identifier id := id, long1 := cast(long1, integer), double1 := cast(double1, number), bool1 := cast(bool1, boolean)]; " + + "ds2_1 := ds2[calc identifier id := id, long1 := cast(long1, integer), double1 := cast(double1, number), bool1 := cast(bool1, boolean)]; " + + "ds_concat := ds1_1#string1 || \" and \" || ds2_1#string1; " + + "ds1_num := ds1_1[keep long1, double1]; " + + "ds2_num := ds2_1[keep long1, double1]; " + + "ds_mod := mod(ds1_num, 2); " + + "ds_sum := ds1_num + ds2_num; " + + "ds_compare := ds1_num = ds2_num; " + + "define datapoint ruleset dpr1 ( variable double1, long1 ) is " + + " my_rule_1 : double1 > 0 errorcode \"Double <= 0\" errorlevel 1; " + + " my_rule_2 : long1 > 0 errorcode \"Long <= 0\" errorlevel 100 " + + "end datapoint ruleset; " + + "ds_check_datapoint := check_datapoint(ds1_num, dpr1 all); " + + "ds_check := check(ds1_1#long1 > ds2_1#long1 errorcode \"error\" errorlevel 1 imbalance ds1_1#long1 + ds2_1#long1 invalid);"); + List dsCheckDatapoint = + ((Dataset) engine.getContext().getAttribute("ds_check_datapoint")).getDataPoints(); + } + + @Test + public void checkHierarchyOutputMode() throws ScriptException { + ScriptContext context = engine.getContext(); + context.setAttribute("DS_1", DS_1_HR, ScriptContext.ENGINE_SCOPE); + + engine.eval( + hierarchicalRulesetDef + + "DS_r := check_hierarchy(DS_1, HR_1 rule Id_2); " + + "DS_r_all := check_hierarchy(DS_1, HR_1 rule Id_2 all); " + + "DS_r_all_measures := check_hierarchy(DS_1, HR_1 rule Id_2 all_measures);"); + + Dataset dsR = (Dataset) engine.getContext().getAttribute("DS_r"); + List> dsRWithNull = dsR.getDataAsMap(); + List> dsRWithoutNull = new ArrayList<>(); + for (Map map : dsRWithNull) { + dsRWithoutNull.add(replaceNullValues(map, DEFAULT_NULL_STR)); + } + assertThat(dsRWithoutNull) + .isEqualTo( + List.of( + Map.of( + "Id_1", + "2010", + "Id_2", + "G", + "ruleid", + "R070", + "Me_1", + 19L, + "imbalance", + 8L, + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "M", + "ruleid", + "R100", + "Me_1", + 2L, + "imbalance", + -3L, + "errorcode", + "null", + "errorlevel", + 5L))); + assertThat(dsR.getDataStructure()) + .containsValues( + new Structured.Component("Id_1", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("Id_2", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("ruleid", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("Me_1", Long.class, Dataset.Role.MEASURE), + new Structured.Component("imbalance", Long.class, Dataset.Role.MEASURE), + new Structured.Component("errorcode", String.class, Dataset.Role.MEASURE), + new Structured.Component("errorlevel", Long.class, Dataset.Role.MEASURE)); + + Dataset dsRAll = (Dataset) engine.getContext().getAttribute("DS_r_all"); + List> dsRAllWithNull = dsRAll.getDataAsMap(); + List> dsRAllWithoutNull = new ArrayList<>(); + for (Map map : dsRAllWithNull) { + dsRAllWithoutNull.add(replaceNullValues(map, DEFAULT_NULL_STR)); + } + assertThat(dsRAllWithoutNull) + .isEqualTo( + List.of( + Map.of( + "Id_1", + "2010", + "Id_2", + "B", + "ruleid", + "R020", + "bool_var", + true, + "imbalance", + 0L, + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "C", + "ruleid", + "R030", + "bool_var", + true, + "imbalance", + 0L, + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "G", + "ruleid", + "R070", + "bool_var", + false, + "imbalance", + 8L, + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "M", + "ruleid", + "R100", + "bool_var", + false, + "imbalance", + -3L, + "errorcode", + "null", + "errorlevel", + 5L), + Map.of( + "Id_1", + "2010", + "Id_2", + "M", + "ruleid", + "R110", + "bool_var", + true, + "imbalance", + -17L, + "errorcode", + "null", + "errorlevel", + "null"))); + assertThat(dsRAll.getDataStructure()) + .containsValues( + new Structured.Component("Id_1", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("Id_2", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("ruleid", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("bool_var", Boolean.class, Dataset.Role.MEASURE), + new Structured.Component("imbalance", Long.class, Dataset.Role.MEASURE), + new Structured.Component("errorcode", String.class, Dataset.Role.MEASURE), + new Structured.Component("errorlevel", Long.class, Dataset.Role.MEASURE)); + + Dataset dsRAllMeasures = (Dataset) engine.getContext().getAttribute("DS_r_all_measures"); + List> dsRAllMeasuresWithNull = dsRAllMeasures.getDataAsMap(); + List> dsRAllMeasuresWithoutNull = new ArrayList<>(); + for (Map map : dsRAllMeasuresWithNull) { + dsRAllMeasuresWithoutNull.add(replaceNullValues(map, DEFAULT_NULL_STR)); + } + assertThat(dsRAllMeasuresWithoutNull) + .isEqualTo( + List.of( + Map.of( + "Id_1", + "2010", + "Id_2", + "B", + "ruleid", + "R020", + "bool_var", + true, + "Me_1", + 11L, + "imbalance", + 0L, + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "C", + "ruleid", + "R030", + "bool_var", + true, + "Me_1", + 0L, + "imbalance", + 0L, + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "G", + "ruleid", + "R070", + "bool_var", + false, + "Me_1", + 19L, + "imbalance", + 8L, + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "M", + "ruleid", + "R100", + "bool_var", + false, + "Me_1", + 2L, + "imbalance", + -3L, + "errorcode", + "null", + "errorlevel", + 5L), + Map.of( + "Id_1", + "2010", + "Id_2", + "M", + "ruleid", + "R110", + "bool_var", + true, + "Me_1", + 2L, + "imbalance", + -17L, + "errorcode", + "null", + "errorlevel", + "null"))); + assertThat(dsRAllMeasures.getDataStructure()) + .containsValues( + new Structured.Component("Id_1", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("Id_2", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("ruleid", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("Me_1", Long.class, Dataset.Role.MEASURE), + new Structured.Component("bool_var", Boolean.class, Dataset.Role.MEASURE), + new Structured.Component("imbalance", Long.class, Dataset.Role.MEASURE), + new Structured.Component("errorcode", String.class, Dataset.Role.MEASURE), + new Structured.Component("errorlevel", Long.class, Dataset.Role.MEASURE)); + } + + @Test + public void checkHierarchyValidationMode() throws ScriptException { + ScriptContext context = engine.getContext(); + context.setAttribute("DS_1", DS_1_HR, ScriptContext.ENGINE_SCOPE); + + engine.eval( + hierarchicalRulesetDef + + "DS_r_non_null := check_hierarchy(DS_1, HR_1 rule Id_2 non_null all); " + + "DS_r_non_zero := check_hierarchy(DS_1, HR_1 rule Id_2 non_zero all); " + + "DS_r_partial_null := check_hierarchy(DS_1, HR_1 rule Id_2 partial_null all); " + + "DS_r_partial_zero := check_hierarchy(DS_1, HR_1 rule Id_2 partial_zero all); " + + "DS_r_always_null := check_hierarchy(DS_1, HR_1 rule Id_2 always_null all); " + + "DS_r_always_zero := check_hierarchy(DS_1, HR_1 rule Id_2 always_zero all);"); + + Dataset dsRNonNull = (Dataset) engine.getContext().getAttribute("DS_r_non_null"); + List> dsRNonNullWithNull = dsRNonNull.getDataAsMap(); + List> dsRNonNullWithoutNull = new ArrayList<>(); + for (Map map : dsRNonNullWithNull) { + dsRNonNullWithoutNull.add(replaceNullValues(map, DEFAULT_NULL_STR)); + } + assertThat(dsRNonNullWithoutNull) + .isEqualTo( + List.of( + Map.of( + "Id_1", + "2010", + "Id_2", + "B", + "ruleid", + "R020", + "bool_var", + true, + "imbalance", + 0L, + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "C", + "ruleid", + "R030", + "bool_var", + true, + "imbalance", + 0L, + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "G", + "ruleid", + "R070", + "bool_var", + false, + "imbalance", + 8L, + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "M", + "ruleid", + "R100", + "bool_var", + false, + "imbalance", + -3L, + "errorcode", + "null", + "errorlevel", + 5L), + Map.of( + "Id_1", + "2010", + "Id_2", + "M", + "ruleid", + "R110", + "bool_var", + true, + "imbalance", + -17L, + "errorcode", + "null", + "errorlevel", + "null"))); + + Dataset dsRNonZero = (Dataset) engine.getContext().getAttribute("DS_r_non_zero"); + List> dsRNonZeroWithNull = dsRNonZero.getDataAsMap(); + List> dsRNonZeroWithoutNull = new ArrayList<>(); + for (Map map : dsRNonZeroWithNull) { + dsRNonZeroWithoutNull.add(replaceNullValues(map, DEFAULT_NULL_STR)); + } + assertThat(dsRNonZeroWithoutNull) + .isEqualTo( + List.of( + Map.of( + "Id_1", + "2010", + "Id_2", + "A", + "ruleid", + "R010", + "bool_var", + false, + "imbalance", + 5L, + "errorcode", + "null", + "errorlevel", + 5L), + Map.of( + "Id_1", + "2010", + "Id_2", + "B", + "ruleid", + "R020", + "bool_var", + true, + "imbalance", + 0L, + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "C", + "ruleid", + "R030", + "bool_var", + true, + "imbalance", + 0L, + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "D", + "ruleid", + "R040", + "bool_var", + false, + "imbalance", + -3L, + "errorcode", + "null", + "errorlevel", + 1L), + Map.of( + "Id_1", + "2010", + "Id_2", + "E", + "ruleid", + "R050", + "bool_var", + "null", + "imbalance", + "null", + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "G", + "ruleid", + "R070", + "bool_var", + false, + "imbalance", + 8L, + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "H", + "ruleid", + "R080", + "bool_var", + "null", + "imbalance", + "null", + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "I", + "ruleid", + "R090", + "bool_var", + false, + "imbalance", + -5L, + "errorcode", + "YY", + "errorlevel", + 0L), + Map.of( + "Id_1", + "2010", + "Id_2", + "M", + "ruleid", + "R100", + "bool_var", + false, + "imbalance", + -3L, + "errorcode", + "null", + "errorlevel", + 5L), + Map.of( + "Id_1", + "2010", + "Id_2", + "M", + "ruleid", + "R110", + "bool_var", + true, + "imbalance", + -17L, + "errorcode", + "null", + "errorlevel", + "null"))); + + Dataset dsRPartialNull = (Dataset) engine.getContext().getAttribute("DS_r_partial_null"); + List> dsRPartialNullWithNull = dsRPartialNull.getDataAsMap(); + List> dsRPartialNullWithoutNull = new ArrayList<>(); + for (Map map : dsRPartialNullWithNull) { + dsRPartialNullWithoutNull.add(replaceNullValues(map, DEFAULT_NULL_STR)); + } + + assertThat(dsRPartialNullWithoutNull) + .isEqualTo( + List.of( + Map.of( + "Id_1", + "2010", + "Id_2", + "A", + "ruleid", + "R010", + "bool_var", + "null", + "imbalance", + "null", + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "B", + "ruleid", + "R020", + "bool_var", + true, + "imbalance", + 0L, + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "C", + "ruleid", + "R030", + "bool_var", + true, + "imbalance", + 0L, + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "D", + "ruleid", + "R040", + "bool_var", + "null", + "imbalance", + "null", + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "E", + "ruleid", + "R050", + "bool_var", + "null", + "imbalance", + "null", + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "G", + "ruleid", + "R070", + "bool_var", + false, + "imbalance", + 8L, + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "I", + "ruleid", + "R090", + "bool_var", + "null", + "imbalance", + "null", + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "M", + "ruleid", + "R100", + "bool_var", + false, + "imbalance", + -3L, + "errorcode", + "null", + "errorlevel", + 5L), + Map.of( + "Id_1", + "2010", + "Id_2", + "M", + "ruleid", + "R110", + "bool_var", + true, + "imbalance", + -17L, + "errorcode", + "null", + "errorlevel", + "null"))); + + Dataset dsRPartialZero = (Dataset) engine.getContext().getAttribute("DS_r_partial_zero"); + List> dsRPartialZeroWithNull = dsRPartialZero.getDataAsMap(); + List> dsRPartialZeroWithoutNull = new ArrayList<>(); + for (Map map : dsRPartialZeroWithNull) { + dsRPartialZeroWithoutNull.add(replaceNullValues(map, DEFAULT_NULL_STR)); + } + + assertThat(dsRPartialZeroWithoutNull) + .isEqualTo( + List.of( + Map.of( + "Id_1", + "2010", + "Id_2", + "A", + "ruleid", + "R010", + "bool_var", + false, + "imbalance", + 5L, + "errorcode", + "null", + "errorlevel", + 5L), + Map.of( + "Id_1", + "2010", + "Id_2", + "B", + "ruleid", + "R020", + "bool_var", + true, + "imbalance", + 0L, + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "C", + "ruleid", + "R030", + "bool_var", + true, + "imbalance", + 0L, + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "D", + "ruleid", + "R040", + "bool_var", + false, + "imbalance", + -3L, + "errorcode", + "null", + "errorlevel", + 1L), + Map.of( + "Id_1", + "2010", + "Id_2", + "E", + "ruleid", + "R050", + "bool_var", + "null", + "imbalance", + "null", + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "G", + "ruleid", + "R070", + "bool_var", + false, + "imbalance", + 8L, + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "I", + "ruleid", + "R090", + "bool_var", + false, + "imbalance", + -5L, + "errorcode", + "YY", + "errorlevel", + 0L), + Map.of( + "Id_1", + "2010", + "Id_2", + "M", + "ruleid", + "R100", + "bool_var", + false, + "imbalance", + -3L, + "errorcode", + "null", + "errorlevel", + 5L), + Map.of( + "Id_1", + "2010", + "Id_2", + "M", + "ruleid", + "R110", + "bool_var", + true, + "imbalance", + -17L, + "errorcode", + "null", + "errorlevel", + "null"))); + + Dataset dsRAlwaysNull = (Dataset) engine.getContext().getAttribute("DS_r_always_null"); + List> dsRAlwaysNullWithNull = dsRAlwaysNull.getDataAsMap(); + List> dsRAlwaysNullWithoutNull = new ArrayList<>(); + for (Map map : dsRAlwaysNullWithNull) { + dsRAlwaysNullWithoutNull.add(replaceNullValues(map, DEFAULT_NULL_STR)); + } + + assertThat(dsRAlwaysNullWithoutNull) + .isEqualTo( + List.of( + Map.of( + "Id_1", + "2010", + "Id_2", + "A", + "ruleid", + "R010", + "bool_var", + "null", + "imbalance", + "null", + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "B", + "ruleid", + "R020", + "bool_var", + true, + "imbalance", + 0L, + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "C", + "ruleid", + "R030", + "bool_var", + true, + "imbalance", + 0L, + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "D", + "ruleid", + "R040", + "bool_var", + "null", + "imbalance", + "null", + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "E", + "ruleid", + "R050", + "bool_var", + "null", + "imbalance", + "null", + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "F", + "ruleid", + "R060", + "bool_var", + "null", + "imbalance", + "null", + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "G", + "ruleid", + "R070", + "bool_var", + false, + "imbalance", + 8L, + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "H", + "ruleid", + "R080", + "bool_var", + "null", + "imbalance", + "null", + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "I", + "ruleid", + "R090", + "bool_var", + "null", + "imbalance", + "null", + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "M", + "ruleid", + "R100", + "bool_var", + false, + "imbalance", + -3L, + "errorcode", + "null", + "errorlevel", + 5L), + Map.of( + "Id_1", + "2010", + "Id_2", + "M", + "ruleid", + "R110", + "bool_var", + true, + "imbalance", + -17L, + "errorcode", + "null", + "errorlevel", + "null"))); + + Dataset dsRAlwaysZero = (Dataset) engine.getContext().getAttribute("DS_r_always_zero"); + List> dsRAlwaysZeroWithNull = dsRAlwaysZero.getDataAsMap(); + List> dsRAlwaysZeroWithoutNull = new ArrayList<>(); + for (Map map : dsRAlwaysZeroWithNull) { + dsRAlwaysZeroWithoutNull.add(replaceNullValues(map, DEFAULT_NULL_STR)); + } + + assertThat(dsRAlwaysZeroWithoutNull) + .isEqualTo( + List.of( + Map.of( + "Id_1", + "2010", + "Id_2", + "A", + "ruleid", + "R010", + "bool_var", + false, + "imbalance", + 5L, + "errorcode", + "null", + "errorlevel", + 5L), + Map.of( + "Id_1", + "2010", + "Id_2", + "B", + "ruleid", + "R020", + "bool_var", + true, + "imbalance", + 0L, + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "C", + "ruleid", + "R030", + "bool_var", + true, + "imbalance", + 0L, + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "D", + "ruleid", + "R040", + "bool_var", + false, + "imbalance", + -3L, + "errorcode", + "null", + "errorlevel", + 1L), + Map.of( + "Id_1", + "2010", + "Id_2", + "E", + "ruleid", + "R050", + "bool_var", + "null", + "imbalance", + "null", + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "F", + "ruleid", + "R060", + "bool_var", + true, + "imbalance", + 0L, + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "G", + "ruleid", + "R070", + "bool_var", + false, + "imbalance", + 8L, + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "H", + "ruleid", + "R080", + "bool_var", + "null", + "imbalance", + "null", + "errorcode", + "null", + "errorlevel", + "null"), + Map.of( + "Id_1", + "2010", + "Id_2", + "I", + "ruleid", + "R090", + "bool_var", + false, + "imbalance", + -5L, + "errorcode", + "YY", + "errorlevel", + 0L), + Map.of( + "Id_1", + "2010", + "Id_2", + "M", + "ruleid", + "R100", + "bool_var", + false, + "imbalance", + -3L, + "errorcode", + "null", + "errorlevel", + 5L), + Map.of( + "Id_1", + "2010", + "Id_2", + "M", + "ruleid", + "R110", + "bool_var", + true, + "imbalance", + -17L, + "errorcode", + "null", + "errorlevel", + "null"))); + } + + @Test + public void checkHierarchyException() { + Dataset DS_2_HR = + new InMemoryDataset( + List.of(List.of("2010", "A", 5L, 5L)), + List.of( + new Structured.Component("Id_1", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("Id_2", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("Me_1", Long.class, Dataset.Role.MEASURE), + new Structured.Component("Me_2", Long.class, Dataset.Role.MEASURE))); + + Dataset DS_3_HR = + new InMemoryDataset( + List.of(List.of("2010", "A", "5")), + List.of( + new Structured.Component("Id_1", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("Id_2", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("Me_1", String.class, Dataset.Role.MEASURE))); + + Dataset DS_4_HR = + new InMemoryDataset( + List.of(List.of("2010", "A", 5L)), + List.of( + new Structured.Component("Id_1", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("Id_2", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("Me_1", Long.class, Dataset.Role.MEASURE))); + + ScriptContext context = engine.getContext(); + context.setAttribute("DS_1", DS_1_HR, ScriptContext.ENGINE_SCOPE); + context.setAttribute("DS_2", DS_2_HR, ScriptContext.ENGINE_SCOPE); + context.setAttribute("DS_3", DS_3_HR, ScriptContext.ENGINE_SCOPE); + context.setAttribute("DS_4", DS_4_HR, ScriptContext.ENGINE_SCOPE); + + assertThatThrownBy( + () -> + engine.eval( + hierarchicalRulesetDef + + "DS_r := check_hierarchy(DS_1, HR_1 rule Id_2 dataset_priority all);")) + .hasMessageContaining("dataset_priority input mode is not supported in check_hierarchy"); + + assertThatThrownBy( + () -> + engine.eval( + hierarchicalRulesetDef + + "DS_r := check_hierarchy(DS_2, HR_1 rule Id_2 partial_null all);")) + .hasMessageContaining("Dataset DS_2 is not monomeasure"); + + assertThatThrownBy( + () -> + engine.eval( + hierarchicalRulesetDef + + "DS_r := check_hierarchy(DS_3, HR_1 rule Id_2 partial_null all);")) + .hasMessageContaining("Dataset DS_3 measure Me_1 has to have number type"); + + assertThatThrownBy( + () -> + engine.eval( + hierarchicalRulesetDef + + "DS_r := check_hierarchy(DS_4, HR_1 rule Id_3 partial_null all);")) + .hasMessageContaining("ComponentID Id_3 not contained in dataset DS_4"); + } + + @Test + void testCH() throws ScriptException { + SparkSession.Builder sparkBuilder = SparkSession.builder().appName("vtl-lab").master("local"); + SparkSession spark = sparkBuilder.getOrCreate(); + + ScriptEngine engine = new ScriptEngineManager().getEngineByName("vtl"); + ScriptContext context = engine.getContext(); + Bindings bindings = new SimpleBindings(); + org.apache.spark.sql.Dataset ds1 = + spark + .read() + .option("delimiter", ";") + .option("header", "true") + .csv("src/main/resources/c_h.csv"); + bindings.put("ds1", new SparkDataset(ds1)); + context.setBindings(bindings, ScriptContext.ENGINE_SCOPE); + engine.put("$vtl.engine.processing_engine_names", "spark4"); + engine.put("$vtl.spark.session", spark); + + engine.eval( + """ + // Ensure ds1 metadata definition is good + ds1_1 := ds1[calc identifier id := id, Me := cast(Me, integer)];\ + ds2_1 := ds1_1[filter id = "A"]; + + // Define hierarchical ruleset + define hierarchical ruleset hr (variable rule Me) is + My_Rule : ABC = A + B + C errorcode "ABC is not sum of A,B,C" errorlevel 1; + DEF = D + E + F errorcode "DEF is not sum of D,E,F"; + HIJ : HIJ = H + I - J errorcode "HIJ is not H + I - J" errorlevel 10 + end hierarchical ruleset; + ds_all := check_hierarchy(ds1_1, hr rule id all); + ds_all_empty := check_hierarchy(ds2_1, hr rule id all); + ds_invalid := check_hierarchy(ds1_1, hr rule id always_zero invalid); + ds_all_measures := check_hierarchy(ds1_1, hr rule id always_null all_measures);\ + """); + + fr.insee.vtl.model.Dataset ds_all = + (fr.insee.vtl.model.Dataset) engine.getContext().getAttribute("ds_all"); + assertThat(ds_all.getDataPoints()).hasSize(1); + + fr.insee.vtl.model.Dataset ds_all_empty = + (fr.insee.vtl.model.Dataset) engine.getContext().getAttribute("ds_all_empty"); + assertThat(ds_all_empty.getDataPoints()).isEmpty(); + + fr.insee.vtl.model.Dataset ds_invalid = + (fr.insee.vtl.model.Dataset) engine.getContext().getAttribute("ds_invalid"); + assertThat(ds_invalid.getDataPoints()).hasSize(1); + + fr.insee.vtl.model.Dataset ds_all_measures = + (fr.insee.vtl.model.Dataset) engine.getContext().getAttribute("ds_all_measures"); + assertThat(ds_all_measures.getDataPoints()).hasSize(3); + } +} diff --git a/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticAvgTest.java b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticAvgTest.java new file mode 100644 index 000000000..077de35b6 --- /dev/null +++ b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticAvgTest.java @@ -0,0 +1,445 @@ +package fr.insee.vtl.spark.processing.engine.analytic; + +import static org.assertj.core.api.Assertions.assertThat; + +import fr.insee.vtl.engine.VtlScriptEngine; +import fr.insee.vtl.model.Dataset; +import fr.insee.vtl.model.InMemoryDataset; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import javax.script.ScriptContext; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import org.apache.spark.sql.SparkSession; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class AnalyticAvgTest { + + private final InMemoryDataset anCountDS1 = + new InMemoryDataset( + List.of( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 3L, "Me_2", 1D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 4L, "Me_2", 9D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 7L, "Me_2", 5D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 6L, "Me_2", 8D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 9L, "Me_2", 3D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 5L, "Me_2", 4D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 10L, "Me_2", 2D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 5L, "Me_2", 7D)), + Map.of( + "Id_1", + String.class, + "Id_2", + String.class, + "Year", + Long.class, + "Me_1", + Long.class, + "Me_2", + Double.class), + Map.of( + "Id_1", + Dataset.Role.IDENTIFIER, + "Id_2", + Dataset.Role.IDENTIFIER, + "Year", + Dataset.Role.IDENTIFIER, + "Me_1", + Dataset.Role.MEASURE, + "Me_2", + Dataset.Role.MEASURE)); + + private static SparkSession spark; + private static ScriptEngine engine; + + @BeforeEach + public void setUp() { + + ScriptEngineManager mgr = new ScriptEngineManager(); + engine = mgr.getEngineByExtension("vtl"); + + spark = SparkSession.builder().appName("test").master("local").getOrCreate(); + SparkSession.setActiveSession(spark); + + engine.put(VtlScriptEngine.PROCESSING_ENGINE_NAMES, "spark4"); + } + + @AfterAll + public static void tearDown() throws IOException { + if (spark != null) spark.close(); + } + + @Test + public void testAnAvgWithCalcClause() throws ScriptException { + + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", anCountDS1, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := ds1 [ calc avg_Me_1:= avg ( Me_1 over ( partition by Id_1,Id_2 order by Year) )];"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * + +----+----+----+----+----+-----------------+ + |Id_1|Id_2|Year|Me_1|Me_2| avg_Me_1| + +----+----+----+----+----+-----------------+ + | A| XX|2000| 3| 1.0| 3.0| + | A| XX|2001| 4| 9.0| 3.5| + | A| XX|2002| 7| 5.0|4.666666666666667| + | A| XX|2003| 6| 8.0| 5.0| + | A| YY|2000| 9| 3.0| 9.0| + | A| YY|2001| 5| 4.0| 7.0| + | A| YY|2002| 10| 2.0| 8.0| + | A| YY|2003| 5| 7.0| 7.25| + +----+----+----+----+----+-----------------+ + * */ + List> actual = + ((Dataset) engine.getContext().getAttribute("res")).getDataAsMap(); + + assertThat(actual) + .containsExactly( + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 2000L, + "Me_1", + 3L, + "Me_2", + 1.0D, + "avg_Me_1", + 3.0D), + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 2001L, + "Me_1", + 4L, + "Me_2", + 9.0D, + "avg_Me_1", + 3.5D), + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 2002L, + "Me_1", + 7L, + "Me_2", + 5.0D, + "avg_Me_1", + 4.666666666666667D), + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 2003L, + "Me_1", + 6L, + "Me_2", + 8.0D, + "avg_Me_1", + 5.0D), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2000L, + "Me_1", + 9L, + "Me_2", + 3.0D, + "avg_Me_1", + 9.0D), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2001L, + "Me_1", + 5L, + "Me_2", + 4.0D, + "avg_Me_1", + 7.0D), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2002L, + "Me_1", + 10L, + "Me_2", + 2.0D, + "avg_Me_1", + 8.0D), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2003L, + "Me_1", + 5L, + "Me_2", + 7.0D, + "avg_Me_1", + 7.25D)); + } + + @Test + public void testAnAvgWithPartitionClause() throws ScriptException { + + // Analytical function Test case 1 : avg on window with partition + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", anCountDS1, ScriptContext.ENGINE_SCOPE); + + engine.eval("res := avg ( ds1 over ( partition by Id_1, Id_2 ) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * + * +----+----+----+----+----+----------+----------+ + |Id_1|Id_2|Year|Me_1|Me_2|avg_Me_1|avg_Me_2| + +----+----+----+----+----+--------+--------+ + | A| XX|2000| 3| 1.0| 5.0| 5.75| + | A| XX|2001| 4| 9.0| 5.0| 5.75| + | A| XX|2002| 7| 5.0| 5.0| 5.75| + | A| XX|2003| 6| 8.0| 5.0| 5.75| + | A| YY|2000| 9| 3.0| 7.25| 4.0| + | A| YY|2001| 5| 4.0| 7.25| 4.0| + | A| YY|2002| 10| 2.0| 7.25| 4.0| + | A| YY|2003| 5| 7.0| 7.25| 4.0| + +----+----+----+----+----+--------+--------+ + * */ + assertThat(((Dataset) engine.getContext().getAttribute("res")).getDataAsMap()) + .containsExactly( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 5.0D, "Me_2", 5.75D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 5.0D, "Me_2", 5.75D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 5.0D, "Me_2", 5.75D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 5.0D, "Me_2", 5.75D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 7.25D, "Me_2", 4.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 7.25D, "Me_2", 4.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 7.25D, "Me_2", 4.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 7.25D, "Me_2", 4.0D)); + } + + @Test + public void testAnAvgWithPartitionOrderByClause() throws ScriptException { + + // Analytical function Test case 2 : avg on window with partition and order by + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", anCountDS1, ScriptContext.ENGINE_SCOPE); + + engine.eval("res := avg ( ds1 over ( partition by Id_1, Id_2 order by Year) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * +----+----+----+----+----+----------+----------+ + |Id_1|Id_2|Year|Me_1|Me_2| avg_Me_1|avg_Me_2| + +----+----+----+----+----+-----------------+--------+ + | A| XX|2000| 3| 1.0| 3.0| 1.0| + | A| XX|2001| 4| 9.0| 3.5| 5.0| + | A| XX|2002| 7| 5.0|4.666666666666667| 5.0| + | A| XX|2003| 6| 8.0| 5.0| 5.75| + | A| YY|2000| 9| 3.0| 9.0| 3.0| + | A| YY|2001| 5| 4.0| 7.0| 3.5| + | A| YY|2002| 10| 2.0| 8.0| 3.0| + | A| YY|2003| 5| 7.0| 7.25| 4.0| + +----+----+----+----+----+-----------------+--------+ + + * */ + List> res = + AnalyticTest.roundDecimalInDataset( + (Dataset) engine.getContext().getAttribute("res"), AnalyticTest.DEFAULT_PRECISION); + + assertThat(res) + .contains( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 3.0D, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 3.5D, "Me_2", 5.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 4.67D, "Me_2", 5.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 5.0D, "Me_2", 5.75D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 9.0D, "Me_2", 3.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 7.0D, "Me_2", 3.5D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 8.0D, "Me_2", 3.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 7.25D, "Me_2", 4.0D)); + } + + @Test + public void testAnAvgWithPartitionOrderByDPClause() throws ScriptException { + + // Analytical function count test case 3 : avg on window with partition, orderBy and data points + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", anCountDS1, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := avg ( ds1 over ( partition by Id_1 order by Id_2 data points between 2 preceding and 2 following) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * The result data frame + * + * +----+----+----+----+----+----------+----------+ + |Id_1|Id_2|Year|Me_1|Me_2| avg_Me_1| avg_Me_2| + +----+----+----+----+----+-----------------+-----------------+ + | A| XX|2000| 3| 1.0|4.666666666666667| 5.0| + | A| XX|2001| 4| 9.0| 5.0| 5.75| + | A| XX|2002| 7| 5.0| 5.8| 5.2| + | A| XX|2003| 6| 8.0| 6.2| 5.8| + | A| YY|2000| 9| 3.0| 7.4| 4.4| + | A| YY|2001| 5| 4.0| 7.0| 4.8| + | A| YY|2002| 10| 2.0| 7.25| 4.0| + | A| YY|2003| 5| 7.0|6.666666666666667|4.333333333333333| + +----+----+----+----+----+-----------------+-----------------+ + + * */ + List> res = + AnalyticTest.roundDecimalInDataset( + (Dataset) engine.getContext().getAttribute("res"), AnalyticTest.DEFAULT_PRECISION); + assertThat(res) + .containsExactly( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 4.67D, "Me_2", 5.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 5.0D, "Me_2", 5.75D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 5.8D, "Me_2", 5.2D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 6.2D, "Me_2", 5.8D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 7.4D, "Me_2", 4.4D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 7.0D, "Me_2", 4.8D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 7.25D, "Me_2", 4.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 6.67D, "Me_2", 4.33D)); + } + + @Test + public void testAnAvgWithPartitionOrderByRangeClause() throws ScriptException { + + // Analytical function count test case 5 : avg on window with partition, orderBy and range + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", anCountDS1, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := avg ( ds1 over ( partition by Id_1 order by Year range between 1 preceding and 1 following) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * The result data frame + * + * +----+----+----+----+----+----------+----------+ + |Id_1|Id_2|Year|Me_1|Me_2| avg_Me_1| avg_Me_2| + +----+----+----+----+----+-----------------+-----------------+ + | A| XX|2000| 3| 1.0| 5.25| 4.25| + | A| YY|2000| 9| 3.0| 5.25| 4.25| + | A| XX|2001| 4| 9.0|6.333333333333333| 4.0| + | A| YY|2001| 5| 4.0|6.333333333333333| 4.0| + | A| XX|2002| 7| 5.0|6.166666666666667|5.833333333333333| + | A| YY|2002| 10| 2.0|6.166666666666667|5.833333333333333| + | A| XX|2003| 6| 8.0| 7.0| 5.5| + | A| YY|2003| 5| 7.0| 7.0| 5.5| + +----+----+----+----+----+-----------------+-----------------+ + + * */ + List> res = + AnalyticTest.roundDecimalInDataset( + (Dataset) engine.getContext().getAttribute("res"), AnalyticTest.DEFAULT_PRECISION); + assertThat(res) + .containsExactlyInAnyOrder( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 5.25D, "Me_2", 4.25D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 6.33D, "Me_2", 4D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 6.17D, "Me_2", 5.83D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 7D, "Me_2", 5.5D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 5.25D, "Me_2", 4.25D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 6.33D, "Me_2", 4D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 6.17D, "Me_2", 5.83D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 7.0D, "Me_2", 5.5D)); + } +} diff --git a/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticCountTest.java b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticCountTest.java new file mode 100644 index 000000000..952a9eab1 --- /dev/null +++ b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticCountTest.java @@ -0,0 +1,746 @@ +package fr.insee.vtl.spark.processing.engine.analytic; + +import static org.assertj.core.api.Assertions.assertThat; + +import fr.insee.vtl.engine.VtlScriptEngine; +import fr.insee.vtl.model.Dataset; +import fr.insee.vtl.model.InMemoryDataset; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import javax.script.ScriptContext; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import org.apache.spark.sql.SparkSession; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class AnalyticCountTest { + + private final InMemoryDataset anCountDS1 = + new InMemoryDataset( + List.of( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 3L, "Me_2", 1D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 4L, "Me_2", 9D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 7L, "Me_2", 5D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 6L, "Me_2", 8D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 9L, "Me_2", 3D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 5L, "Me_2", 4D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 10L, "Me_2", 2D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 5L, "Me_2", 7D)), + Map.of( + "Id_1", + String.class, + "Id_2", + String.class, + "Year", + Long.class, + "Me_1", + Long.class, + "Me_2", + Double.class), + Map.of( + "Id_1", + Dataset.Role.IDENTIFIER, + "Id_2", + Dataset.Role.IDENTIFIER, + "Year", + Dataset.Role.IDENTIFIER, + "Me_1", + Dataset.Role.MEASURE, + "Me_2", + Dataset.Role.MEASURE)); + + private static SparkSession spark; + private static ScriptEngine engine; + + @BeforeEach + public void setUp() { + + ScriptEngineManager mgr = new ScriptEngineManager(); + engine = mgr.getEngineByExtension("vtl"); + + spark = SparkSession.builder().appName("test").master("local").getOrCreate(); + SparkSession.setActiveSession(spark); + + engine.put(VtlScriptEngine.PROCESSING_ENGINE_NAMES, "spark4"); + } + + @AfterAll + public static void tearDown() throws IOException { + if (spark != null) spark.close(); + } + + /* + * Test with calc statement */ + @Test + public void testAnCountWithCalcClause() throws ScriptException { + + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", anCountDS1, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := ds1 [ calc count_Me_1:= count ( Me_1 over ( partition by Id_1,Id_2 order by Year ) )];"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * + * +----+----+----+----+----+----------+ + |Id_1|Id_2|Year|Me_1|Me_2|count_Me_1| + +----+----+----+----+----+----------+ + | A| XX|2000| 3| 1.0| 1| + | A| XX|2001| 4| 9.0| 2| + | A| XX|2002| 7| 5.0| 3| + | A| XX|2003| 6| 8.0| 4| + | A| YY|2000| 9| 3.0| 1| + | A| YY|2001| 5| 4.0| 2| + | A| YY|2002| 10| 2.0| 3| + | A| YY|2003| 5| 7.0| 4| + +----+----+----+----+----+----------+ + * */ + List> actual = + ((Dataset) engine.getContext().getAttribute("res")).getDataAsMap(); + + assertThat(actual) + .containsExactly( + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 2000L, + "Me_1", + 3L, + "Me_2", + 1.0D, + "count_Me_1", + 1L), + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 2001L, + "Me_1", + 4L, + "Me_2", + 9.0D, + "count_Me_1", + 2L), + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 2002L, + "Me_1", + 7L, + "Me_2", + 5.0D, + "count_Me_1", + 3L), + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 2003L, + "Me_1", + 6L, + "Me_2", + 8.0D, + "count_Me_1", + 4L), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2000L, + "Me_1", + 9L, + "Me_2", + 3.0D, + "count_Me_1", + 1L), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2001L, + "Me_1", + 5L, + "Me_2", + 4.0D, + "count_Me_1", + 2L), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2002L, + "Me_1", + 10L, + "Me_2", + 2.0D, + "count_Me_1", + 3L), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2003L, + "Me_1", + 5L, + "Me_2", + 7.0D, + "count_Me_1", + 4L)); + } + + @Test + public void testAnCountDPWithCalcClause() throws ScriptException { + + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", anCountDS1, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := ds1 [ calc count_Me_1:= count ( Me_1 over ( partition by Id_1,Id_2 order by Year data points between 2 preceding and 2 following) )];"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * + +----+----+----+----+----+----------+ + |Id_1|Id_2|Year|Me_1|Me_2|count_Me_1| + +----+----+----+----+----+----------+ + | A| XX|2000| 3| 1.0| 3| + | A| XX|2001| 4| 9.0| 4| + | A| XX|2002| 7| 5.0| 4| + | A| XX|2003| 6| 8.0| 3| + | A| YY|2000| 9| 3.0| 3| + | A| YY|2001| 5| 4.0| 4| + | A| YY|2002| 10| 2.0| 4| + | A| YY|2003| 5| 7.0| 3| + +----+----+----+----+----+----------+ + * */ + List> actual = + ((Dataset) engine.getContext().getAttribute("res")).getDataAsMap(); + + assertThat(actual) + .containsExactly( + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 2000L, + "Me_1", + 3L, + "Me_2", + 1.0D, + "count_Me_1", + 3L), + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 2001L, + "Me_1", + 4L, + "Me_2", + 9.0D, + "count_Me_1", + 4L), + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 2002L, + "Me_1", + 7L, + "Me_2", + 5.0D, + "count_Me_1", + 4L), + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 2003L, + "Me_1", + 6L, + "Me_2", + 8.0D, + "count_Me_1", + 3L), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2000L, + "Me_1", + 9L, + "Me_2", + 3.0D, + "count_Me_1", + 3L), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2001L, + "Me_1", + 5L, + "Me_2", + 4.0D, + "count_Me_1", + 4L), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2002L, + "Me_1", + 10L, + "Me_2", + 2.0D, + "count_Me_1", + 4L), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2003L, + "Me_1", + 5L, + "Me_2", + 7.0D, + "count_Me_1", + 3L)); + } + + @Test + public void testAnCountRangeWithCalcClause() throws ScriptException { + + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", anCountDS1, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := ds1 [ calc count_Me_1:= count ( Me_1 over ( partition by Id_1,Id_2 order by Year range between 1 preceding and 1 following) )];"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * + +----+----+----+----+----+----------+ + |Id_1|Id_2|Year|Me_1|Me_2|count_Me_1| + +----+----+----+----+----+----------+ + | A| XX|2000| 3| 1.0| 2| + | A| XX|2001| 4| 9.0| 3| + | A| XX|2002| 7| 5.0| 3| + | A| XX|2003| 6| 8.0| 2| + | A| YY|2000| 9| 3.0| 2| + | A| YY|2001| 5| 4.0| 3| + | A| YY|2002| 10| 2.0| 3| + | A| YY|2003| 5| 7.0| 2| + +----+----+----+----+----+----------+ + * */ + List> actual = + ((Dataset) engine.getContext().getAttribute("res")).getDataAsMap(); + + assertThat(actual) + .containsExactly( + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 2000L, + "Me_1", + 3L, + "Me_2", + 1.0D, + "count_Me_1", + 2L), + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 2001L, + "Me_1", + 4L, + "Me_2", + 9.0D, + "count_Me_1", + 3L), + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 2002L, + "Me_1", + 7L, + "Me_2", + 5.0D, + "count_Me_1", + 3L), + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 2003L, + "Me_1", + 6L, + "Me_2", + 8.0D, + "count_Me_1", + 2L), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2000L, + "Me_1", + 9L, + "Me_2", + 3.0D, + "count_Me_1", + 2L), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2001L, + "Me_1", + 5L, + "Me_2", + 4.0D, + "count_Me_1", + 3L), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2002L, + "Me_1", + 10L, + "Me_2", + 2.0D, + "count_Me_1", + 3L), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2003L, + "Me_1", + 5L, + "Me_2", + 7.0D, + "count_Me_1", + 2L)); + } + + @Test + public void testAnCountWithPartitionClause() throws ScriptException { + + // Analytical function Test case 1 : count on window with partition + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", anCountDS1, ScriptContext.ENGINE_SCOPE); + + // Transform to res := ds1[ calc count_Me_1 := count ( Me_1 over ( partition by Id_1))];" + engine.eval("res := count ( ds1 over ( partition by Id_1) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * The result data frame need to check mutable or not mutable on Mesaument column + * + * +----+----+----+----+----+----------+----------+ + |Id_1|Id_2|Year|Me_1|Me_2|count_Me_1|count_Me_2| + +----+----+----+----+----+----------+----------+ + | A| XX|2000| 3| 1.0| 8| 8| + | A| XX|2001| 4| 9.0| 8| 8| + | A| XX|2002| 7| 5.0| 8| 8| + | A| XX|2003| 6| 8.0| 8| 8| + | A| YY|2000| 9| 3.0| 8| 8| + | A| YY|2001| 5| 4.0| 8| 8| + | A| YY|2002| 10| 2.0| 8| 8| + | A| YY|2003| 5| 7.0| 8| 8| + +----+----+----+----+----+----------+----------+ + * */ + List> actual = + ((Dataset) engine.getContext().getAttribute("res")).getDataAsMap(); + + assertThat(actual) + .containsExactly( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 8L, "Me_2", 8L), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 8L, "Me_2", 8L), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 8L, "Me_2", 8L), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 8L, "Me_2", 8L), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 8L, "Me_2", 8L), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 8L, "Me_2", 8L), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 8L, "Me_2", 8L), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 8L, "Me_2", 8L)); + } + + @Test + public void testAnCountWithPartitionOrderByClause() throws ScriptException { + + // Analytical function Test case 2 : count on window with partition and orderBy + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", anCountDS1, ScriptContext.ENGINE_SCOPE); + + engine.eval("res := count ( ds1 over ( partition by Id_1 order by Id_2) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * The result data frame + * + * +----+----+----+----+----+----------+----------+ + |Id_1|Id_2|Year|Me_1|Me_2|count_Me_1|count_Me_2| + +----+----+----+----+----+----------+----------+ + | A| XX|2000| 3| 1.0| 4| 4| + | A| XX|2001| 4| 9.0| 4| 4| + | A| XX|2002| 7| 5.0| 4| 4| + | A| XX|2003| 6| 8.0| 4| 4| + | A| YY|2000| 9| 3.0| 8| 8| + | A| YY|2001| 5| 4.0| 8| 8| + | A| YY|2002| 10| 2.0| 8| 8| + | A| YY|2003| 5| 7.0| 8| 8| + +----+----+----+----+----+----------+----------+ + * */ + assertThat(((Dataset) engine.getContext().getAttribute("res")).getDataAsMap()) + .containsExactly( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 4L, "Me_2", 4L), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 4L, "Me_2", 4L), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 4L, "Me_2", 4L), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 4L, "Me_2", 4L), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 8L, "Me_2", 8L), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 8L, "Me_2", 8L), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 8L, "Me_2", 8L), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 8L, "Me_2", 8L)); + } + + @Test + public void testAnCountWithPartitionOrderByDPClause() throws ScriptException { + + // Analytical function count test case 3 : count on window with partition, orderBy and data + // points + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", anCountDS1, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := count ( ds1 over ( partition by Id_1 order by Id_2 data points between 2 preceding and 2 following) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * The result data frame + * + * +----+----+----+----+----+----------+----------+ + |Id_1|Id_2|Year|Me_1|Me_2|count_Me_1|count_Me_2| + +----+----+----+----+----+----------+----------+ + | A| XX|2000| 3| 1.0| 3| 3| + | A| XX|2001| 4| 9.0| 4| 4| + | A| XX|2002| 7| 5.0| 5| 5| + | A| XX|2003| 6| 8.0| 5| 5| + | A| YY|2000| 9| 3.0| 5| 5| + | A| YY|2001| 5| 4.0| 5| 5| + | A| YY|2002| 10| 2.0| 4| 4| + | A| YY|2003| 5| 7.0| 3| 3| + +----+----+----+----+----+----------+----------+ + + * */ + assertThat(((Dataset) engine.getContext().getAttribute("res")).getDataAsMap()) + .containsExactly( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 3L, "Me_2", 3L), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 4L, "Me_2", 4L), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 5L, "Me_2", 5L), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 5L, "Me_2", 5L), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 5L, "Me_2", 5L), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 5L, "Me_2", 5L), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 4L, "Me_2", 4L), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 3L, "Me_2", 3L)); + } + + @Test + public void testAnCountWithPartitionOrderByRange() throws ScriptException { + // Analytical function count test case 4 : count on window with partition, orderBy and range + // Because range build window based on the ordered column value, and Id_2 is string, so we + // change + // the order by column to year. + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", anCountDS1, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := count ( ds1 over ( partition by Id_1 order by Year range between 1 preceding and 1 following) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * The result data frame + * + * +----+----+----+----+----+----------+----------+ + |Id_1|Id_2|Year|Me_1|Me_2|count_Me_1|count_Me_2| + +----+----+----+----+----+----------+----------+ + | A| XX|2000| 3| 1.0| 4| 4| + | A| YY|2000| 9| 3.0| 4| 4| + | A| XX|2001| 4| 9.0| 6| 6| + | A| YY|2001| 5| 4.0| 6| 6| + | A| XX|2002| 7| 5.0| 6| 6| + | A| YY|2002| 10| 2.0| 6| 6| + | A| XX|2003| 6| 8.0| 4| 4| + | A| YY|2003| 5| 7.0| 4| 4| + +----+----+----+----+----+----------+----------+ + + * */ + assertThat(((Dataset) engine.getContext().getAttribute("res")).getDataAsMap()) + .containsExactly( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 4L, "Me_2", 4L), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 4L, "Me_2", 4L), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 6L, "Me_2", 6L), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 6L, "Me_2", 6L), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 6L, "Me_2", 6L), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 6L, "Me_2", 6L), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 4L, "Me_2", 4L), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 4L, "Me_2", 4L)); + } +} diff --git a/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticFirstTest.java b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticFirstTest.java new file mode 100644 index 000000000..80096f49b --- /dev/null +++ b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticFirstTest.java @@ -0,0 +1,222 @@ +package fr.insee.vtl.spark.processing.engine.analytic; + +import static org.assertj.core.api.Assertions.assertThat; + +import fr.insee.vtl.model.Dataset; +import java.util.Map; +import javax.script.ScriptContext; +import javax.script.ScriptException; +import org.junit.jupiter.api.Test; + +public class AnalyticFirstTest extends AnalyticTest { + + @Test + public void testAnFirstWithPartitionClause() throws ScriptException { + + // Analytical function Test case 1 : first on window with partition + /* Input dataset + +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|1993| 3| 1.0| + | A| XX|1994| 4| 9.0| + | A| XX|1995| 7| 5.0| + | A| XX|1996| 6| 8.0| + | A| YY|1993| 9| 3.0| + | A| YY|1994| 5| 4.0| + | A| YY|1995| 10| 2.0| + | A| YY|1996| 2| 7.0| + +----+----+----+----+----+ + + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds2", ds2, ScriptContext.ENGINE_SCOPE); + + engine.eval("res := first_value ( ds2 over ( partition by Id_1, Id_2) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * + +----+----+----+----+----+----------+----------+ + |Id_1|Id_2|Year|Me_1|Me_2|first_Me_1|first_Me_2| + +----+----+----+----+----+----------+----------+ + | A| XX|1993| 3| 1.0| 3| 1.0| + | A| XX|1994| 4| 9.0| 3| 1.0| + | A| XX|1995| 7| 5.0| 3| 1.0| + | A| XX|1996| 6| 8.0| 3| 1.0| + | A| YY|1993| 9| 3.0| 9| 3.0| + | A| YY|1994| 5| 4.0| 9| 3.0| + | A| YY|1995| 10| 2.0| 9| 3.0| + | A| YY|1996| 2| 7.0| 9| 3.0| + +----+----+----+----+----+----------+----------+ + * */ + assertThat(((Dataset) engine.getContext().getAttribute("res")).getDataAsMap()) + .containsExactly( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1993L, "Me_1", 3L, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1994L, "Me_1", 3L, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1995L, "Me_1", 3L, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1996L, "Me_1", 3L, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1993L, "Me_1", 9L, "Me_2", 3.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1994L, "Me_1", 9L, "Me_2", 3.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1995L, "Me_1", 9L, "Me_2", 3.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1996L, "Me_1", 9L, "Me_2", 3.0D)); + } + + @Test + public void testAnFirstPartitionOrderByDesc() throws ScriptException { + + // Analytical function Test case 2 : first on window with partition and desc order + /* Input dataset + +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|1993| 3| 1.0| + | A| XX|1994| 4| 9.0| + | A| XX|1995| 7| 5.0| + | A| XX|1996| 6| 8.0| + | A| YY|1993| 9| 3.0| + | A| YY|1994| 5| 4.0| + | A| YY|1995| 10| 2.0| + | A| YY|1996| 2| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + // will also test dot espacing + context.setAttribute("ds.ds2", ds2, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := first_value ( ds.ds2 over ( partition by Id_1, Id_2 order by Year desc) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * + +----+----+----+----+----+----------+----------+ + |Id_1|Id_2|Year|Me_1|Me_2|first_Me_1|first_Me_2| + +----+----+----+----+----+----------+----------+ + | A| XX|1996| 6| 8.0| 6| 8.0| + | A| XX|1995| 7| 5.0| 6| 8.0| + | A| XX|1994| 4| 9.0| 6| 8.0| + | A| XX|1993| 3| 1.0| 6| 8.0| + | A| YY|1996| 2| 7.0| 2| 7.0| + | A| YY|1995| 10| 2.0| 2| 7.0| + | A| YY|1994| 5| 4.0| 2| 7.0| + | A| YY|1993| 9| 3.0| 2| 7.0| + +----+----+----+----+----+----------+----------+ + * */ + assertThat(((Dataset) engine.getContext().getAttribute("res")).getDataAsMap()) + .containsExactly( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1996L, "Me_1", 6L, "Me_2", 8.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1995L, "Me_1", 6L, "Me_2", 8.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1994L, "Me_1", 6L, "Me_2", 8.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1993L, "Me_1", 6L, "Me_2", 8.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1996L, "Me_1", 2L, "Me_2", 7.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1995L, "Me_1", 2L, "Me_2", 7.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1994L, "Me_1", 2L, "Me_2", 7.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1993L, "Me_1", 2L, "Me_2", 7.0D)); + } + + @Test + public void testAnFirstWithPartitionOrderByDPClause() throws ScriptException { + + // Analytical function Test case 3 : first on window with partition, order by and data points + /* Input dataset + +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|1993| 3| 1.0| + | A| XX|1994| 4| 9.0| + | A| XX|1995| 7| 5.0| + | A| XX|1996| 6| 8.0| + | A| YY|1993| 9| 3.0| + | A| YY|1994| 5| 4.0| + | A| YY|1995| 10| 2.0| + | A| YY|1996| 2| 7.0| + +----+----+----+----+----+ + + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds2", ds2, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := first_value ( ds2 over ( partition by Id_1 order by Id_2 data points between 2 preceding and 2 following) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * + +----+----+----+----+----+----------+----------+ + |Id_1|Id_2|Year|Me_1|Me_2|first_Me_1|first_Me_2| + +----+----+----+----+----+----------+----------+ + | A| XX|1993| 3| 1.0| 3| 1.0| + | A| XX|1994| 4| 9.0| 3| 1.0| + | A| XX|1995| 7| 5.0| 3| 1.0| + | A| XX|1996| 6| 8.0| 4| 9.0| + | A| YY|1993| 9| 3.0| 7| 5.0| + | A| YY|1994| 5| 4.0| 6| 8.0| + | A| YY|1995| 10| 2.0| 9| 3.0| + | A| YY|1996| 2| 7.0| 5| 4.0| + +----+----+----+----+----+----------+----------+ + * */ + assertThat(((Dataset) engine.getContext().getAttribute("res")).getDataAsMap()) + .containsExactly( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1993L, "Me_1", 3L, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1994L, "Me_1", 3L, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1995L, "Me_1", 3L, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1996L, "Me_1", 4L, "Me_2", 9.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1993L, "Me_1", 7L, "Me_2", 5.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1994L, "Me_1", 6L, "Me_2", 8.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1995L, "Me_1", 9L, "Me_2", 3.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1996L, "Me_1", 5L, "Me_2", 4.0D)); + } + + @Test + public void testAnFirstPartitionOrderByRangeClause() throws ScriptException { + + // Analytical function Test case 4 : first on window with partition, order by and range + /* Input dataset + +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|1993| 3| 1.0| + | A| XX|1994| 4| 9.0| + | A| XX|1995| 7| 5.0| + | A| XX|1996| 6| 8.0| + | A| YY|1993| 9| 3.0| + | A| YY|1994| 5| 4.0| + | A| YY|1995| 10| 2.0| + | A| YY|1996| 2| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds2", ds2, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := first_value ( ds2 over ( partition by Id_1 order by Year range between 1 preceding and 1 following) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * + +----+----+----+----+----+----------+----------+ + |Id_1|Id_2|Year|Me_1|Me_2|first_Me_1|first_Me_2| + +----+----+----+----+----+----------+----------+ + | A| XX|1993| 3| 1.0| 3| 1.0| + | A| YY|1993| 9| 3.0| 3| 1.0| + | A| XX|1994| 4| 9.0| 3| 1.0| + | A| YY|1994| 5| 4.0| 3| 1.0| + | A| XX|1995| 7| 5.0| 4| 9.0| + | A| YY|1995| 10| 2.0| 4| 9.0| + | A| XX|1996| 6| 8.0| 7| 5.0| + | A| YY|1996| 2| 7.0| 7| 5.0| + +----+----+----+----+----+----------+----------+ + * */ + assertThat(((Dataset) engine.getContext().getAttribute("res")).getDataAsMap()) + .containsExactlyInAnyOrder( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1993L, "Me_1", 3L, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1993L, "Me_1", 3L, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1994L, "Me_1", 3L, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1994L, "Me_1", 3L, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1995L, "Me_1", 4L, "Me_2", 9.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1995L, "Me_1", 4L, "Me_2", 9.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1996L, "Me_1", 7L, "Me_2", 5.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1996L, "Me_1", 7L, "Me_2", 5.0D)); + } +} diff --git a/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticLagTest.java b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticLagTest.java new file mode 100644 index 000000000..20f9eda52 --- /dev/null +++ b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticLagTest.java @@ -0,0 +1,193 @@ +package fr.insee.vtl.spark.processing.engine.analytic; + +import static org.assertj.core.api.Assertions.assertThat; + +import fr.insee.vtl.model.Dataset; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import javax.script.ScriptContext; +import javax.script.ScriptException; +import org.junit.jupiter.api.Test; + +public class AnalyticLagTest extends AnalyticTest { + + @Test + public void testAnLagWithCalcClause() throws ScriptException { + + // Analytical function Test case 1 : lead on window with partition, order by and range + /* Input dataset + +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|1993| 3| 1.0| + | A| XX|1994| 4| 9.0| + | A| XX|1995| 7| 5.0| + | A| XX|1996| 6| 8.0| + | A| YY|1993| 9| 3.0| + | A| YY|1994| 5| 4.0| + | A| YY|1995| 10| 2.0| + | A| YY|1996| 2| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds2", ds2, ScriptContext.ENGINE_SCOPE); + engine.eval( + "res := ds2 [ calc lag_Me_1 := lag ( Me_1 , 1 over ( partition by Id_1 , Id_2 order by Year ) )] ;"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * + +----+----+----+----+----+--------+ + |Id_1|Id_2|Year|Me_1|Me_2|lag_Me_1| + +----+----+----+----+----+--------+ + | A| XX|1993| 3| 1.0| null| + | A| XX|1994| 4| 9.0| 3| + | A| XX|1995| 7| 5.0| 4| + | A| XX|1996| 6| 8.0| 7| + | A| YY|1993| 9| 3.0| null| + | A| YY|1994| 5| 4.0| 9| + | A| YY|1995| 10| 2.0| 5| + | A| YY|1996| 2| 7.0| 10| + +----+----+----+----+----+--------+ + * */ + + List> actualWithNull = + ((Dataset) engine.getContext().getAttribute("res")).getDataAsMap(); + + List> actual = new ArrayList<>(); + for (Map map : actualWithNull) { + actual.add(replaceNullValues(map, DEFAULT_NULL_STR)); + } + assertThat(actual) + .containsExactly( + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 1993L, + "Me_1", + 3L, + "Me_2", + 1.0D, + "lag_Me_1", + "null"), + Map.of( + "Id_1", "A", "Id_2", "XX", "Year", 1994L, "Me_1", 4L, "Me_2", 9.0D, "lag_Me_1", 3L), + Map.of( + "Id_1", "A", "Id_2", "XX", "Year", 1995L, "Me_1", 7L, "Me_2", 5.0D, "lag_Me_1", 4L), + Map.of( + "Id_1", "A", "Id_2", "XX", "Year", 1996L, "Me_1", 6L, "Me_2", 8.0D, "lag_Me_1", 7L), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 1993L, + "Me_1", + 9L, + "Me_2", + 3.0D, + "lag_Me_1", + "null"), + Map.of( + "Id_1", "A", "Id_2", "YY", "Year", 1994L, "Me_1", 5L, "Me_2", 4.0D, "lag_Me_1", 9L), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 1995L, + "Me_1", + 10L, + "Me_2", + 2.0D, + "lag_Me_1", + 5L), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 1996L, + "Me_1", + 2L, + "Me_2", + 7.0D, + "lag_Me_1", + 10L)); + } + + /* + * Test case for analytic function lag + * The lag function take two argument: + * - input dataframe + * - step + * Analytic clause restriction: + * - Must have orderClause + * - The windowClause such as data points and range are not allowed + * */ + @Test + public void testAnLag() throws ScriptException { + + // Analytical function Test case 1 : lag on window with partition, order by and range + /* Input dataset + +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|1993| 3| 1.0| + | A| XX|1994| 4| 9.0| + | A| XX|1995| 7| 5.0| + | A| XX|1996| 6| 8.0| + | A| YY|1993| 9| 3.0| + | A| YY|1994| 5| 4.0| + | A| YY|1995| 10| 2.0| + | A| YY|1996| 2| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds2", ds2, ScriptContext.ENGINE_SCOPE); + + engine.eval("res := lag ( ds2 , 1 over ( partition by Id_1 , Id_2 order by Year ) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * + +----+----+----+----+----+---------+---------+ + |Id_1|Id_2|Year|Me_1|Me_2|lead_Me_1|lead_Me_2| + +----+----+----+----+----+---------+---------+ + | A| XX|1993| 3| 1.0| null| null| + | A| XX|1994| 4| 9.0| 3| 1.0| + | A| XX|1995| 7| 5.0| 4| 9.0| + | A| XX|1996| 6| 8.0| 7| 5.0| + | A| YY|1993| 9| 3.0| null| null| + | A| YY|1994| 5| 4.0| 9| 3.0| + | A| YY|1995| 10| 2.0| 5| 4.0| + | A| YY|1996| 2| 7.0| 10| 2.0| + +----+----+----+----+----+---------+---------+ + * */ + List> actualWithNull = + ((Dataset) engine.getContext().getAttribute("res")).getDataAsMap(); + var actual = + actualWithNull.stream() + .map(map -> replaceNullValues(map, DEFAULT_NULL_STR)) + .collect(Collectors.toList()); + + assertThat(actual) + .containsExactly( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1993L, "Me_1", "null", "Me_2", "null"), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1994L, "Me_1", 3L, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1995L, "Me_1", 4L, "Me_2", 9.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1996L, "Me_1", 7L, "Me_2", 5.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1993L, "Me_1", "null", "Me_2", "null"), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1994L, "Me_1", 9L, "Me_2", 3.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1995L, "Me_1", 5L, "Me_2", 4.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1996L, "Me_1", 10L, "Me_2", 2.0D)); + } +} diff --git a/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticLastTest.java b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticLastTest.java new file mode 100644 index 000000000..1e16a4874 --- /dev/null +++ b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticLastTest.java @@ -0,0 +1,224 @@ +package fr.insee.vtl.spark.processing.engine.analytic; + +import static org.assertj.core.api.Assertions.assertThat; + +import fr.insee.vtl.model.Dataset; +import java.util.Map; +import javax.script.ScriptContext; +import javax.script.ScriptException; +import org.junit.jupiter.api.Test; + +public class AnalyticLastTest extends AnalyticTest { + + /* + * Test case for analytic function last + * + * */ + @Test + public void testAnLastWithPartitionClause() throws ScriptException { + + // Analytical function Test case 1 : last on window with partition + /* Input dataset + +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|1993| 3| 1.0| + | A| XX|1994| 4| 9.0| + | A| XX|1995| 7| 5.0| + | A| XX|1996| 6| 8.0| + | A| YY|1993| 9| 3.0| + | A| YY|1994| 5| 4.0| + | A| YY|1995| 10| 2.0| + | A| YY|1996| 2| 7.0| + +----+----+----+----+----+ + + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds2", ds2, ScriptContext.ENGINE_SCOPE); + + engine.eval("res := last_value ( ds2 over ( partition by Id_1, Id_2) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * + +----+----+----+----+----+---------+---------+ + |Id_1|Id_2|Year|Me_1|Me_2|last_Me_1|last_Me_2| + +----+----+----+----+----+---------+---------+ + | A| XX|1993| 3| 1.0| 6| 8.0| + | A| XX|1994| 4| 9.0| 6| 8.0| + | A| XX|1995| 7| 5.0| 6| 8.0| + | A| XX|1996| 6| 8.0| 6| 8.0| + | A| YY|1993| 9| 3.0| 2| 7.0| + | A| YY|1994| 5| 4.0| 2| 7.0| + | A| YY|1995| 10| 2.0| 2| 7.0| + | A| YY|1996| 2| 7.0| 2| 7.0| + +----+----+----+----+----+---------+---------+ + * */ + assertThat(((Dataset) engine.getContext().getAttribute("res")).getDataAsMap()) + .containsExactly( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1993L, "Me_1", 6L, "Me_2", 8.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1994L, "Me_1", 6L, "Me_2", 8.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1995L, "Me_1", 6L, "Me_2", 8.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1996L, "Me_1", 6L, "Me_2", 8.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1993L, "Me_1", 2L, "Me_2", 7.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1994L, "Me_1", 2L, "Me_2", 7.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1995L, "Me_1", 2L, "Me_2", 7.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1996L, "Me_1", 2L, "Me_2", 7.0D)); + } + + @Test + public void testAnLastPartitionOrderByDesc() throws ScriptException { + + // Analytical function Test case 2 : last on window with partition and desc order + /* Input dataset + +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|1993| 3| 1.0| + | A| XX|1994| 4| 9.0| + | A| XX|1995| 7| 5.0| + | A| XX|1996| 6| 8.0| + | A| YY|1993| 9| 3.0| + | A| YY|1994| 5| 4.0| + | A| YY|1995| 10| 2.0| + | A| YY|1996| 2| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds2", ds2, ScriptContext.ENGINE_SCOPE); + + engine.eval("res := last_value ( ds2 over ( partition by Id_1, Id_2 order by Year desc) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * + +----+----+----+----+----+---------+---------+ + |Id_1|Id_2|Year|Me_1|Me_2|last_Me_1|last_Me_2| + +----+----+----+----+----+---------+---------+ + | A| XX|1996| 6| 8.0| 6| 8.0| + | A| XX|1995| 7| 5.0| 7| 5.0| + | A| XX|1994| 4| 9.0| 4| 9.0| + | A| XX|1993| 3| 1.0| 3| 1.0| + | A| YY|1996| 2| 7.0| 2| 7.0| + | A| YY|1995| 10| 2.0| 10| 2.0| + | A| YY|1994| 5| 4.0| 5| 4.0| + | A| YY|1993| 9| 3.0| 9| 3.0| + +----+----+----+----+----+---------+---------+ + * */ + assertThat(((Dataset) engine.getContext().getAttribute("res")).getDataAsMap()) + .containsExactly( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1996L, "Me_1", 6L, "Me_2", 8.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1995L, "Me_1", 7L, "Me_2", 5.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1994L, "Me_1", 4L, "Me_2", 9.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1993L, "Me_1", 3L, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1996L, "Me_1", 2L, "Me_2", 7.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1995L, "Me_1", 10L, "Me_2", 2.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1994L, "Me_1", 5L, "Me_2", 4.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1993L, "Me_1", 9L, "Me_2", 3.0D)); + } + + @Test + public void testAnLastWithPartitionOrderByDPClause() throws ScriptException { + + // Analytical function Test case 3 : last on window with partition, order by and data points + /* Input dataset + +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|1993| 3| 1.0| + | A| XX|1994| 4| 9.0| + | A| XX|1995| 7| 5.0| + | A| XX|1996| 6| 8.0| + | A| YY|1993| 9| 3.0| + | A| YY|1994| 5| 4.0| + | A| YY|1995| 10| 2.0| + | A| YY|1996| 2| 7.0| + +----+----+----+----+----+ + + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds2", ds2, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := last_value ( ds2 over ( partition by Id_1 order by Id_2 data points between 2 preceding and 2 following) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * + +----+----+----+----+----+---------+---------+ + |Id_1|Id_2|Year|Me_1|Me_2|last_Me_1|last_Me_2| + +----+----+----+----+----+---------+---------+ + | A| XX|1993| 3| 1.0| 7| 5.0| + | A| XX|1994| 4| 9.0| 6| 8.0| + | A| XX|1995| 7| 5.0| 9| 3.0| + | A| XX|1996| 6| 8.0| 5| 4.0| + | A| YY|1993| 9| 3.0| 10| 2.0| + | A| YY|1994| 5| 4.0| 2| 7.0| + | A| YY|1995| 10| 2.0| 2| 7.0| + | A| YY|1996| 2| 7.0| 2| 7.0| + +----+----+----+----+----+---------+---------+ + * */ + assertThat(((Dataset) engine.getContext().getAttribute("res")).getDataAsMap()) + .containsExactly( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1993L, "Me_1", 7L, "Me_2", 5.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1994L, "Me_1", 6L, "Me_2", 8.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1995L, "Me_1", 9L, "Me_2", 3.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1996L, "Me_1", 5L, "Me_2", 4.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1993L, "Me_1", 10L, "Me_2", 2.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1994L, "Me_1", 2L, "Me_2", 7.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1995L, "Me_1", 2L, "Me_2", 7.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1996L, "Me_1", 2L, "Me_2", 7.0D)); + } + + @Test + public void testAnLastPartitionOrderByRangeClause() throws ScriptException { + + // Analytical function Test case 4 : last on window with partition, order by and range + /* Input dataset + +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|1993| 3| 1.0| + | A| XX|1994| 4| 9.0| + | A| XX|1995| 7| 5.0| + | A| XX|1996| 6| 8.0| + | A| YY|1993| 9| 3.0| + | A| YY|1994| 5| 4.0| + | A| YY|1995| 10| 2.0| + | A| YY|1996| 2| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds2", ds2, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := last_value ( ds2 over ( partition by Id_1, Id_2 order by Year range between 1 preceding and 1 following) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * + +----+----+----+----+----+---------+---------+ + |Id_1|Id_2|Year|Me_1|Me_2|last_Me_1|last_Me_2| + +----+----+----+----+----+---------+---------+ + | A| XX|1993| 3| 1.0| 4| 9.0| + | A| XX|1994| 4| 9.0| 7| 5.0| + | A| XX|1995| 7| 5.0| 6| 8.0| + | A| XX|1996| 6| 8.0| 6| 8.0| + | A| YY|1993| 9| 3.0| 5| 4.0| + | A| YY|1994| 5| 4.0| 10| 2.0| + | A| YY|1995| 10| 2.0| 2| 7.0| + | A| YY|1996| 2| 7.0| 2| 7.0| + +----+----+----+----+----+---------+---------+ + * */ + assertThat(((Dataset) engine.getContext().getAttribute("res")).getDataAsMap()) + .containsExactlyInAnyOrder( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1993L, "Me_1", 4L, "Me_2", 9.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1994L, "Me_1", 7L, "Me_2", 5.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1995L, "Me_1", 6L, "Me_2", 8.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1996L, "Me_1", 6L, "Me_2", 8.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1993L, "Me_1", 5L, "Me_2", 4.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1994L, "Me_1", 10L, "Me_2", 2.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1995L, "Me_1", 2L, "Me_2", 7.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1996L, "Me_1", 2L, "Me_2", 7.0D)); + } +} diff --git a/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticLeadTest.java b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticLeadTest.java new file mode 100644 index 000000000..5a71e462b --- /dev/null +++ b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticLeadTest.java @@ -0,0 +1,237 @@ +package fr.insee.vtl.spark.processing.engine.analytic; + +import static org.assertj.core.api.Assertions.assertThat; + +import fr.insee.vtl.model.Dataset; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import javax.script.ScriptContext; +import javax.script.ScriptException; +import org.junit.jupiter.api.Test; + +public class AnalyticLeadTest extends AnalyticTest { + + @Test + public void testAnLeadWithCalcClause() throws ScriptException { + + // Analytical function Test case 1 : lead on window with partition, order by and range + /* Input dataset + +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|1993| 3| 1.0| + | A| XX|1994| 4| 9.0| + | A| XX|1995| 7| 5.0| + | A| XX|1996| 6| 8.0| + | A| YY|1993| 9| 3.0| + | A| YY|1994| 5| 4.0| + | A| YY|1995| 10| 2.0| + | A| YY|1996| 2| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds2", ds2, ScriptContext.ENGINE_SCOPE); + engine.eval( + "res := ds2 [ calc lead_Me_1 := lead ( Me_1 , 1 over ( partition by Id_1 , Id_2 order by Year ) )] ;"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * + +----+----+----+----+----+---------+ + |Id_1|Id_2|Year|Me_1|Me_2|lead_Me_1| + +----+----+----+----+----+---------+ + | A| XX|1993| 3| 1.0| 4| + | A| XX|1994| 4| 9.0| 7| + | A| XX|1995| 7| 5.0| 6| + | A| XX|1996| 6| 8.0| null| + | A| YY|1993| 9| 3.0| 5| + | A| YY|1994| 5| 4.0| 10| + | A| YY|1995| 10| 2.0| 2| + | A| YY|1996| 2| 7.0| null| + +----+----+----+----+----+---------+ + * */ + + List> actualWithNull = + ((Dataset) engine.getContext().getAttribute("res")).getDataAsMap(); + + List> actual = new ArrayList<>(); + for (Map map : actualWithNull) { + actual.add(replaceNullValues(map, DEFAULT_NULL_STR)); + } + + assertThat(actual) + .containsExactly( + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 1993L, + "Me_1", + 3L, + "Me_2", + 1.0D, + "lead_Me_1", + 4L), + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 1994L, + "Me_1", + 4L, + "Me_2", + 9.0D, + "lead_Me_1", + 7L), + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 1995L, + "Me_1", + 7L, + "Me_2", + 5.0D, + "lead_Me_1", + 6L), + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 1996L, + "Me_1", + 6L, + "Me_2", + 8.0D, + "lead_Me_1", + "null"), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 1993L, + "Me_1", + 9L, + "Me_2", + 3.0D, + "lead_Me_1", + 5L), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 1994L, + "Me_1", + 5L, + "Me_2", + 4.0D, + "lead_Me_1", + 10L), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 1995L, + "Me_1", + 10L, + "Me_2", + 2.0D, + "lead_Me_1", + 2L), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 1996L, + "Me_1", + 2L, + "Me_2", + 7.0D, + "lead_Me_1", + "null")); + } + + /* + * Test case for analytic function lead + * The lead function take two argument: + * - input dataframe + * - step + * Analytic clause restriction: + * - Must have orderClause + * - The windowClause such as data points and range are not allowed + * */ + + @Test + public void testAnLead() throws ScriptException { + + // Analytical function Test case 1 : lead on window with partition, order by and range + /* Input dataset + +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|1993| 3| 1.0| + | A| XX|1994| 4| 9.0| + | A| XX|1995| 7| 5.0| + | A| XX|1996| 6| 8.0| + | A| YY|1993| 9| 3.0| + | A| YY|1994| 5| 4.0| + | A| YY|1995| 10| 2.0| + | A| YY|1996| 2| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds2", ds2, ScriptContext.ENGINE_SCOPE); + + engine.eval("res := lead ( ds2 , 1 over ( partition by Id_1 , Id_2 order by Year ) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * + +----+----+----+----+----+---------+---------+ + |Id_1|Id_2|Year|Me_1|Me_2|lead_Me_1|lead_Me_2| + +----+----+----+----+----+---------+---------+ + | A| XX|1993| 3| 1.0| 4| 9.0| + | A| XX|1994| 4| 9.0| 7| 5.0| + | A| XX|1995| 7| 5.0| 6| 8.0| + | A| XX|1996| 6| 8.0| null| null| + | A| YY|1993| 9| 3.0| 5| 4.0| + | A| YY|1994| 5| 4.0| 10| 2.0| + | A| YY|1995| 10| 2.0| 2| 7.0| + | A| YY|1996| 2| 7.0| null| null| + +----+----+----+----+----+---------+---------+ + * */ + var actual = + ((Dataset) engine.getContext().getAttribute("res")) + .getDataAsMap().stream() + .map(map -> replaceNullValues(map, DEFAULT_NULL_STR)) + .collect(Collectors.toList()); + assertThat(actual) + .containsExactly( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1993L, "Me_1", 4L, "Me_2", 9.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1994L, "Me_1", 7L, "Me_2", 5.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1995L, "Me_1", 6L, "Me_2", 8.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1996L, "Me_1", "null", "Me_2", "null"), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1993L, "Me_1", 5L, "Me_2", 4.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1994L, "Me_1", 10L, "Me_2", 2.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1995L, "Me_1", 2L, "Me_2", 7.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1996L, "Me_1", "null", "Me_2", "null")); + } +} diff --git a/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticMaxTest.java b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticMaxTest.java new file mode 100644 index 000000000..e776ca0ca --- /dev/null +++ b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticMaxTest.java @@ -0,0 +1,369 @@ +package fr.insee.vtl.spark.processing.engine.analytic; + +import static org.assertj.core.api.Assertions.assertThat; + +import fr.insee.vtl.engine.VtlScriptEngine; +import fr.insee.vtl.model.Dataset; +import fr.insee.vtl.model.InMemoryDataset; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import javax.script.ScriptContext; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import org.apache.spark.sql.SparkSession; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class AnalyticMaxTest { + + private final InMemoryDataset anCountDS1 = + new InMemoryDataset( + List.of( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 3L, "Me_2", 1D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 4L, "Me_2", 9D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 7L, "Me_2", 5D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 6L, "Me_2", 8D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 9L, "Me_2", 3D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 5L, "Me_2", 4D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 10L, "Me_2", 2D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 5L, "Me_2", 7D)), + Map.of( + "Id_1", + String.class, + "Id_2", + String.class, + "Year", + Long.class, + "Me_1", + Long.class, + "Me_2", + Double.class), + Map.of( + "Id_1", + Dataset.Role.IDENTIFIER, + "Id_2", + Dataset.Role.IDENTIFIER, + "Year", + Dataset.Role.IDENTIFIER, + "Me_1", + Dataset.Role.MEASURE, + "Me_2", + Dataset.Role.MEASURE)); + + private static SparkSession spark; + private static ScriptEngine engine; + + @BeforeEach + public void setUp() { + + ScriptEngineManager mgr = new ScriptEngineManager(); + engine = mgr.getEngineByExtension("vtl"); + + spark = SparkSession.builder().appName("test").master("local").getOrCreate(); + SparkSession.setActiveSession(spark); + + engine.put(VtlScriptEngine.PROCESSING_ENGINE_NAMES, "spark4"); + } + + @AfterAll + public static void tearDown() throws IOException { + if (spark != null) spark.close(); + } + + @Test + public void testAnMaxWithCalcClause() throws ScriptException { + + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", anCountDS1, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := ds1 [ calc max_Me_1:= max ( Me_1 over ( partition by Id_1,Id_2 order by Year) )];"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * + +----+----+----+----+----+--------+ + |Id_1|Id_2|Year|Me_1|Me_2|max_Me_1| + +----+----+----+----+----+--------+ + | A| XX|2000| 3| 1.0| 3| + | A| XX|2001| 4| 9.0| 4| + | A| XX|2002| 7| 5.0| 7| + | A| XX|2003| 6| 8.0| 7| + | A| YY|2000| 9| 3.0| 9| + | A| YY|2001| 5| 4.0| 9| + | A| YY|2002| 10| 2.0| 10| + | A| YY|2003| 5| 7.0| 10| + +----+----+----+----+----+--------+ + * */ + List> actual = + ((Dataset) engine.getContext().getAttribute("res")).getDataAsMap(); + + assertThat(actual) + .containsExactly( + Map.of( + "Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 3L, "Me_2", 1.0D, "max_Me_1", 3L), + Map.of( + "Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 4L, "Me_2", 9.0D, "max_Me_1", 4L), + Map.of( + "Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 7L, "Me_2", 5.0D, "max_Me_1", 7L), + Map.of( + "Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 6L, "Me_2", 8.0D, "max_Me_1", 7L), + Map.of( + "Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 9L, "Me_2", 3.0D, "max_Me_1", 9L), + Map.of( + "Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 5L, "Me_2", 4.0D, "max_Me_1", 9L), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2002L, + "Me_1", + 10L, + "Me_2", + 2.0D, + "max_Me_1", + 10L), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2003L, + "Me_1", + 5L, + "Me_2", + 7.0D, + "max_Me_1", + 10L)); + } + + @Test + public void testAnMaxWithPartitionClause() throws ScriptException { + + // Analytical function Test case 1 : max on window with partition + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", anCountDS1, ScriptContext.ENGINE_SCOPE); + + engine.eval("res := max ( ds1 over ( partition by Id_1, Id_2 ) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * + * +----+----+----+----+----+----------+----------+ + |Id_1|Id_2|Year|Me_1|Me_2|max_Me_1|max_Me_2| + +----+----+----+----+----+--------+--------+ + | A| XX|2000| 3| 1.0| 7| 9.0| + | A| XX|2001| 4| 9.0| 7| 9.0| + | A| XX|2002| 7| 5.0| 7| 9.0| + | A| XX|2003| 6| 8.0| 7| 9.0| + | A| YY|2000| 9| 3.0| 10| 7.0| + | A| YY|2001| 5| 4.0| 10| 7.0| + | A| YY|2002| 10| 2.0| 10| 7.0| + | A| YY|2003| 5| 7.0| 10| 7.0| + +----+----+----+----+----+--------+--------+ + * */ + assertThat(((Dataset) engine.getContext().getAttribute("res")).getDataAsMap()) + .containsExactly( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 7L, "Me_2", 9.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 7L, "Me_2", 9.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 7L, "Me_2", 9.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 7L, "Me_2", 9.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 10L, "Me_2", 7.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 10L, "Me_2", 7.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 10L, "Me_2", 7.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 10L, "Me_2", 7.0D)); + } + + @Test + public void testAnMaxWithPartitionOrderByClause() throws ScriptException { + + // Analytical function Test case 2 : max on window with partition and order by + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", anCountDS1, ScriptContext.ENGINE_SCOPE); + + engine.eval("res := max ( ds1 over ( partition by Id_1, Id_2 order by Year) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * +----+----+----+----+----+----------+----------+ + |Id_1|Id_2|Year|Me_1|Me_2|max_Me_1|max_Me_2| + +----+----+----+----+----+--------+--------+ + | A| XX|2000| 3| 1.0| 3| 1.0| + | A| XX|2001| 4| 9.0| 4| 9.0| + | A| XX|2002| 7| 5.0| 7| 9.0| + | A| XX|2003| 6| 8.0| 7| 9.0| + | A| YY|2000| 9| 3.0| 9| 3.0| + | A| YY|2001| 5| 4.0| 9| 4.0| + | A| YY|2002| 10| 2.0| 10| 4.0| + | A| YY|2003| 5| 7.0| 10| 7.0| + +----+----+----+----+----+--------+--------+ + + * */ + assertThat(((Dataset) engine.getContext().getAttribute("res")).getDataAsMap()) + .containsExactly( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 3L, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 4L, "Me_2", 9.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 7L, "Me_2", 9.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 7L, "Me_2", 9.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 9L, "Me_2", 3.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 9L, "Me_2", 4.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 10L, "Me_2", 4.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 10L, "Me_2", 7.0D)); + } + + @Test + public void testAnMaxWithPartitionOrderByDPClause() throws ScriptException { + + // Analytical function count test case 3 : max on window with partition, orderBy and data points + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", anCountDS1, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := max ( ds1 over ( partition by Id_1 order by Id_2 data points between 2 preceding and 2 following) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * The result data frame + * + * +----+----+----+----+----+----------+----------+ + |Id_1|Id_2|Year|Me_1|Me_2|max_Me_1|max_Me_2| + +----+----+----+----+----+--------+--------+ + | A| XX|2000| 3| 1.0| 7| 9.0| + | A| XX|2001| 4| 9.0| 7| 9.0| + | A| XX|2002| 7| 5.0| 9| 9.0| + | A| XX|2003| 6| 8.0| 9| 9.0| + | A| YY|2000| 9| 3.0| 10| 8.0| + | A| YY|2001| 5| 4.0| 10| 8.0| + | A| YY|2002| 10| 2.0| 10| 7.0| + | A| YY|2003| 5| 7.0| 10| 7.0| + +----+----+----+----+----+--------+--------+ + + * */ + assertThat(((Dataset) engine.getContext().getAttribute("res")).getDataAsMap()) + .containsExactly( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 7L, "Me_2", 9.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 7L, "Me_2", 9.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 9L, "Me_2", 9.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 9L, "Me_2", 9.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 10L, "Me_2", 8.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 10L, "Me_2", 8.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 10L, "Me_2", 7.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 10L, "Me_2", 7.0D)); + } + + @Test + public void testAnMaxWithPartitionOrderByRangeClause() throws ScriptException { + + // Analytical function count test case 5 : max on window with partition, orderBy and range + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", anCountDS1, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := max ( ds1 over ( partition by Id_1 order by Year range between 1 preceding and 1 following) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * The result data frame + * + * +----+----+----+----+----+----------+----------+ + |Id_1|Id_2|Year|Me_1|Me_2|max_Me_1|max_Me_2| + +----+----+----+----+----+--------+--------+ + | A| XX|2000| 3| 1.0| 9| 9.0| + | A| YY|2000| 9| 3.0| 9| 9.0| + | A| XX|2001| 4| 9.0| 10| 9.0| + | A| YY|2001| 5| 4.0| 10| 9.0| + | A| XX|2002| 7| 5.0| 10| 9.0| + | A| YY|2002| 10| 2.0| 10| 9.0| + | A| XX|2003| 6| 8.0| 10| 8.0| + | A| YY|2003| 5| 7.0| 10| 8.0| + +----+----+----+----+----+--------+--------+ + + * */ + assertThat(((Dataset) engine.getContext().getAttribute("res")).getDataAsMap()) + .containsExactlyInAnyOrder( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 9L, "Me_2", 9.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 9L, "Me_2", 9.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 10L, "Me_2", 9.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 10L, "Me_2", 9.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 10L, "Me_2", 9.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 10L, "Me_2", 9.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 10L, "Me_2", 8.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 10L, "Me_2", 8.0D)); + } +} diff --git a/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticMedianTest.java b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticMedianTest.java new file mode 100644 index 000000000..3762d5987 --- /dev/null +++ b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticMedianTest.java @@ -0,0 +1,435 @@ +package fr.insee.vtl.spark.processing.engine.analytic; + +import static org.assertj.core.api.Assertions.assertThat; + +import fr.insee.vtl.engine.VtlScriptEngine; +import fr.insee.vtl.model.Dataset; +import fr.insee.vtl.model.InMemoryDataset; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import javax.script.ScriptContext; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import org.apache.spark.sql.SparkSession; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class AnalyticMedianTest { + + private final InMemoryDataset anCountDS1 = + new InMemoryDataset( + List.of( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 3L, "Me_2", 1D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 4L, "Me_2", 9D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 7L, "Me_2", 5D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 6L, "Me_2", 8D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 9L, "Me_2", 3D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 5L, "Me_2", 4D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 10L, "Me_2", 2D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 5L, "Me_2", 7D)), + Map.of( + "Id_1", + String.class, + "Id_2", + String.class, + "Year", + Long.class, + "Me_1", + Long.class, + "Me_2", + Double.class), + Map.of( + "Id_1", + Dataset.Role.IDENTIFIER, + "Id_2", + Dataset.Role.IDENTIFIER, + "Year", + Dataset.Role.IDENTIFIER, + "Me_1", + Dataset.Role.MEASURE, + "Me_2", + Dataset.Role.MEASURE)); + + private static SparkSession spark; + private static ScriptEngine engine; + + @BeforeEach + public void setUp() { + + ScriptEngineManager mgr = new ScriptEngineManager(); + engine = mgr.getEngineByExtension("vtl"); + + spark = SparkSession.builder().appName("test").master("local").getOrCreate(); + SparkSession.setActiveSession(spark); + + engine.put(VtlScriptEngine.PROCESSING_ENGINE_NAMES, "spark4"); + } + + @AfterAll + public static void tearDown() throws IOException { + if (spark != null) spark.close(); + } + + @Test + public void testAnMedianWithCalcClause() throws ScriptException { + + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", anCountDS1, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := ds1 [ calc median_Me_1:= median ( Me_1 over ( partition by Id_1,Id_2 order by Year) )];"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + +----+----+----+----+----+-----------+ + |Id_1|Id_2|Year|Me_1|Me_2|median_Me_1| + +----+----+----+----+----+-----------+ + | A| XX|2000| 3| 1.0| 3| + | A| XX|2001| 4| 9.0| 3| + | A| XX|2002| 7| 5.0| 4| + | A| XX|2003| 6| 8.0| 4| + | A| YY|2000| 9| 3.0| 9| + | A| YY|2001| 5| 4.0| 5| + | A| YY|2002| 10| 2.0| 9| + | A| YY|2003| 5| 7.0| 5| + +----+----+----+----+----+-----------+ + * */ + List> actual = + ((Dataset) engine.getContext().getAttribute("res")).getDataAsMap(); + + assertThat(actual) + .containsExactly( + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 2000L, + "Me_1", + 3L, + "Me_2", + 1.0D, + "median_Me_1", + 3L), + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 2001L, + "Me_1", + 4L, + "Me_2", + 9.0D, + "median_Me_1", + 3L), + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 2002L, + "Me_1", + 7L, + "Me_2", + 5.0D, + "median_Me_1", + 4L), + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 2003L, + "Me_1", + 6L, + "Me_2", + 8.0D, + "median_Me_1", + 4L), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2000L, + "Me_1", + 9L, + "Me_2", + 3.0D, + "median_Me_1", + 9L), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2001L, + "Me_1", + 5L, + "Me_2", + 4.0D, + "median_Me_1", + 5L), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2002L, + "Me_1", + 10L, + "Me_2", + 2.0D, + "median_Me_1", + 9L), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2003L, + "Me_1", + 5L, + "Me_2", + 7.0D, + "median_Me_1", + 5L)); + } + + @Test + public void testAnMedianWithPartitionClause() throws ScriptException { + + // Analytical function Test case 1 : median on window with partition + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", anCountDS1, ScriptContext.ENGINE_SCOPE); + + engine.eval("res := median ( ds1 over ( partition by Id_1, Id_2 ) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * + * +----+----+----+----+----+----------+----------+ + |Id_1|Id_2|Year|Me_1|Me_2|median_Me_1|median_Me_2| + +----+----+----+----+----+-----------+-----------+ + | A| XX|2000| 3| 1.0| 4| 5.0| + | A| XX|2001| 4| 9.0| 4| 5.0| + | A| XX|2002| 7| 5.0| 4| 5.0| + | A| XX|2003| 6| 8.0| 4| 5.0| + | A| YY|2000| 9| 3.0| 5| 3.0| + | A| YY|2001| 5| 4.0| 5| 3.0| + | A| YY|2002| 10| 2.0| 5| 3.0| + | A| YY|2003| 5| 7.0| 5| 3.0| + +----+----+----+----+----+-----------+-----------+ + * */ + assertThat(((Dataset) engine.getContext().getAttribute("res")).getDataAsMap()) + .containsExactly( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 4L, "Me_2", 5.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 4L, "Me_2", 5.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 4L, "Me_2", 5.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 4L, "Me_2", 5.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 5L, "Me_2", 3.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 5L, "Me_2", 3.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 5L, "Me_2", 3.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 5L, "Me_2", 3.0D)); + } + + @Test + public void testAnMedianWithPartitionOrderByClause() throws ScriptException { + + // Analytical function Test case 2 : median on window with partition and order by + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", anCountDS1, ScriptContext.ENGINE_SCOPE); + + engine.eval("res := median ( ds1 over ( partition by Id_1, Id_2 order by Year) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * +----+----+----+----+----+----------+----------+ + |Id_1|Id_2|Year|Me_1|Me_2|median_Me_1|median_Me_2| + +----+----+----+----+----+-----------+-----------+ + | A| XX|2000| 3| 1.0| 3| 1.0| + | A| XX|2001| 4| 9.0| 3| 1.0| + | A| XX|2002| 7| 5.0| 4| 5.0| + | A| XX|2003| 6| 8.0| 4| 5.0| + | A| YY|2000| 9| 3.0| 9| 3.0| + | A| YY|2001| 5| 4.0| 5| 3.0| + | A| YY|2002| 10| 2.0| 9| 3.0| + | A| YY|2003| 5| 7.0| 5| 3.0| + +----+----+----+----+----+-----------+-----------+ + + * */ + assertThat(((Dataset) engine.getContext().getAttribute("res")).getDataAsMap()) + .containsExactly( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 3L, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 3L, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 4L, "Me_2", 5.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 4L, "Me_2", 5.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 9L, "Me_2", 3.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 5L, "Me_2", 3.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 9L, "Me_2", 3.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 5L, "Me_2", 3.0D)); + } + + @Test + public void testAnMedianWithPartitionOrderByDPClause() throws ScriptException { + + // Analytical function count test case 3 : median on window with partition, orderBy and data + // points + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", anCountDS1, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := median ( ds1 over ( partition by Id_1 order by Id_2 data points between 2 preceding and 2 following) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * The result data frame + * + * +----+----+----+----+----+----------+----------+ + |Id_1|Id_2|Year|Me_1|Me_2|median_Me_1|median_Me_2| + +----+----+----+----+----+-----------+-----------+ + | A| XX|2000| 3| 1.0| 4| 5.0| + | A| XX|2001| 4| 9.0| 4| 5.0| + | A| XX|2002| 7| 5.0| 6| 5.0| + | A| XX|2003| 6| 8.0| 6| 5.0| + | A| YY|2000| 9| 3.0| 7| 4.0| + | A| YY|2001| 5| 4.0| 6| 4.0| + | A| YY|2002| 10| 2.0| 5| 3.0| + | A| YY|2003| 5| 7.0| 5| 4.0| + +----+----+----+----+----+-----------+-----------+ + + * */ + assertThat(((Dataset) engine.getContext().getAttribute("res")).getDataAsMap()) + .containsExactly( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 4L, "Me_2", 5.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 4L, "Me_2", 5.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 6L, "Me_2", 5.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 6L, "Me_2", 5.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 7L, "Me_2", 4.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 6L, "Me_2", 4.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 5L, "Me_2", 3.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 5L, "Me_2", 4.0D)); + } + + @Test + public void testAnMedianWithPartitionOrderByRangeClause() throws ScriptException { + + // Analytical function count test case 4 : median on window with partition, orderBy and range + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", anCountDS1, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := median ( ds1 over ( partition by Id_1 order by Year range between 1 preceding and 1 following) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * The result data frame + * + * +----+----+----+----+----+----------+----------+ + |Id_1|Id_2|Year|Me_1|Me_2|median_Me_1|median_Me_2| + +----+----+----+----+----+-----------+-----------+ + | A| XX|2000| 3| 1.0| 4| 3.0| + | A| YY|2000| 9| 3.0| 4| 3.0| + | A| XX|2001| 4| 9.0| 5| 3.0| + | A| YY|2001| 5| 4.0| 5| 3.0| + | A| XX|2002| 7| 5.0| 5| 5.0| + | A| YY|2002| 10| 2.0| 5| 5.0| + | A| XX|2003| 6| 8.0| 6| 5.0| + | A| YY|2003| 5| 7.0| 6| 5.0| + +----+----+----+----+----+-----------+-----------+ + + * */ + assertThat(((Dataset) engine.getContext().getAttribute("res")).getDataAsMap()) + .containsExactlyInAnyOrder( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 4L, "Me_2", 3.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 4L, "Me_2", 3.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 5L, "Me_2", 3.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 5L, "Me_2", 3.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 5L, "Me_2", 5.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 5L, "Me_2", 5.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 6L, "Me_2", 5.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 6L, "Me_2", 5.0D)); + } +} diff --git a/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticMinTest.java b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticMinTest.java new file mode 100644 index 000000000..ab98b50bd --- /dev/null +++ b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticMinTest.java @@ -0,0 +1,421 @@ +package fr.insee.vtl.spark.processing.engine.analytic; + +import static org.assertj.core.api.Assertions.assertThat; + +import fr.insee.vtl.engine.VtlScriptEngine; +import fr.insee.vtl.model.Dataset; +import fr.insee.vtl.model.InMemoryDataset; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import javax.script.ScriptContext; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import org.apache.spark.sql.SparkSession; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class AnalyticMinTest { + + private final InMemoryDataset anCountDS1 = + new InMemoryDataset( + List.of( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 3L, "Me_2", 1D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 4L, "Me_2", 9D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 7L, "Me_2", 5D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 6L, "Me_2", 8D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 9L, "Me_2", 3D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 5L, "Me_2", 4D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 10L, "Me_2", 2D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 5L, "Me_2", 7D)), + Map.of( + "Id_1", + String.class, + "Id_2", + String.class, + "Year", + Long.class, + "Me_1", + Long.class, + "Me_2", + Double.class), + Map.of( + "Id_1", + Dataset.Role.IDENTIFIER, + "Id_2", + Dataset.Role.IDENTIFIER, + "Year", + Dataset.Role.IDENTIFIER, + "Me_1", + Dataset.Role.MEASURE, + "Me_2", + Dataset.Role.MEASURE)); + + private static SparkSession spark; + private static ScriptEngine engine; + + @BeforeEach + public void setUp() { + + ScriptEngineManager mgr = new ScriptEngineManager(); + engine = mgr.getEngineByExtension("vtl"); + + spark = SparkSession.builder().appName("test").master("local").getOrCreate(); + SparkSession.setActiveSession(spark); + + engine.put(VtlScriptEngine.PROCESSING_ENGINE_NAMES, "spark4"); + } + + @AfterAll + public static void tearDown() throws IOException { + if (spark != null) spark.close(); + } + + @Test + public void testAnMinWithCalcClause() throws ScriptException { + + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", anCountDS1, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := ds1 [ calc min_Me_1:= min ( Me_1 over ( partition by Id_1,Id_2 order by Year) )];"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * + +----+----+----+----+----+--------+ + |Id_1|Id_2|Year|Me_1|Me_2|min_Me_1| + +----+----+----+----+----+--------+ + | A| XX|2000| 3| 1.0| 3| + | A| XX|2001| 4| 9.0| 3| + | A| XX|2002| 7| 5.0| 3| + | A| XX|2003| 6| 8.0| 3| + | A| YY|2000| 9| 3.0| 9| + | A| YY|2001| 5| 4.0| 5| + | A| YY|2002| 10| 2.0| 5| + | A| YY|2003| 5| 7.0| 5| + +----+----+----+----+----+--------+ + * */ + List> actual = + ((Dataset) engine.getContext().getAttribute("res")).getDataAsMap(); + + assertThat(actual) + .containsExactly( + Map.of( + "Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 3L, "Me_2", 1.0D, "min_Me_1", 3L), + Map.of( + "Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 4L, "Me_2", 9.0D, "min_Me_1", 3L), + Map.of( + "Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 7L, "Me_2", 5.0D, "min_Me_1", 3L), + Map.of( + "Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 6L, "Me_2", 8.0D, "min_Me_1", 3L), + Map.of( + "Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 9L, "Me_2", 3.0D, "min_Me_1", 9L), + Map.of( + "Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 5L, "Me_2", 4.0D, "min_Me_1", 5L), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2002L, + "Me_1", + 10L, + "Me_2", + 2.0D, + "min_Me_1", + 5L), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2003L, + "Me_1", + 5L, + "Me_2", + 7.0D, + "min_Me_1", + 5L)); + } + + @Test + public void testAnMinWithPartitionClause() throws ScriptException { + + // Analytical function Test case 1 : min on window with partition + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", anCountDS1, ScriptContext.ENGINE_SCOPE); + + engine.eval("res := min ( ds1 over ( partition by Id_1, Id_2 ) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * + * +----+----+----+----+----+----------+----------+ + |Id_1|Id_2|Year|Me_1|Me_2|min_Me_1|min_Me_2| + +----+----+----+----+----+--------+--------+ + | A| XX|2000| 3| 1.0| 3| 1.0| + | A| XX|2001| 4| 9.0| 3| 1.0| + | A| XX|2002| 7| 5.0| 3| 1.0| + | A| XX|2003| 6| 8.0| 3| 1.0| + | A| YY|2000| 9| 3.0| 5| 2.0| + | A| YY|2001| 5| 4.0| 5| 2.0| + | A| YY|2002| 10| 2.0| 5| 2.0| + | A| YY|2003| 5| 7.0| 5| 2.0| + +----+----+----+----+----+--------+--------+ + * */ + assertThat(((Dataset) engine.getContext().getAttribute("res")).getDataAsMap()) + .containsExactly( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 3L, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 3L, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 3L, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 3L, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 5L, "Me_2", 2.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 5L, "Me_2", 2.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 5L, "Me_2", 2.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 5L, "Me_2", 2.0D)); + } + + @Test + public void testAnMinWithPartitionOrderByClause() throws ScriptException { + + // Analytical function Test case 2 : min on window with partition and order by + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", anCountDS1, ScriptContext.ENGINE_SCOPE); + + engine.eval("res := min ( ds1 over ( partition by Id_1, Id_2 order by Year) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * +----+----+----+----+----+----------+----------+ + |Id_1|Id_2|Year|Me_1|Me_2|min_Me_1|min_Me_2| + +----+----+----+----+----+--------+--------+ + | A| XX|2000| 3| 1.0| 3| 1.0| + | A| XX|2001| 4| 9.0| 3| 1.0| + | A| XX|2002| 7| 5.0| 3| 1.0| + | A| XX|2003| 6| 8.0| 3| 1.0| + | A| YY|2000| 9| 3.0| 9| 3.0| + | A| YY|2001| 5| 4.0| 5| 3.0| + | A| YY|2002| 10| 2.0| 5| 2.0| + | A| YY|2003| 5| 7.0| 5| 2.0| + +----+----+----+----+----+--------+--------+ + + * */ + assertThat(((Dataset) engine.getContext().getAttribute("res")).getDataAsMap()) + .containsExactly( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 3L, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 3L, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 3L, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 3L, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 9L, "Me_2", 3.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 5L, "Me_2", 3.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 5L, "Me_2", 2.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 5L, "Me_2", 2.0D)); + } + + @Test + public void testAnMinWithOrderByClause() throws ScriptException { + + // Analytical function Test case 3 : min on window with only order by without partition + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", anCountDS1, ScriptContext.ENGINE_SCOPE); + + engine.eval("res := min ( ds1 over ( order by Id_1, Id_2, Year ) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * + * +----+----+----+----+----+----------+----------+ + |Id_1|Id_2|Year|Me_1|Me_2|min_Me_1|min_Me_2| + +----+----+----+----+----+--------+--------+ + | A| XX|2000| 3| 1.0| 3| 1.0| + | A| XX|2001| 4| 9.0| 3| 1.0| + | A| XX|2002| 7| 5.0| 3| 1.0| + | A| XX|2003| 6| 8.0| 3| 1.0| + | A| YY|2000| 9| 3.0| 3| 1.0| + | A| YY|2001| 5| 4.0| 3| 1.0| + | A| YY|2002| 10| 2.0| 3| 1.0| + | A| YY|2003| 5| 7.0| 3| 1.0| + +----+----+----+----+----+--------+--------+ + + * */ + assertThat(((Dataset) engine.getContext().getAttribute("res")).getDataAsMap()) + .containsExactly( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 3L, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 3L, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 3L, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 3L, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 3L, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 3L, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 3L, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 3L, "Me_2", 1.0D)); + } + + @Test + public void testAnMinWithPartitionOrderByDPClause() throws ScriptException { + + // Analytical function count test case 4 : min on window with partition, orderBy and data points + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", anCountDS1, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := min ( ds1 over ( partition by Id_1 order by Id_2 data points between 2 preceding and 2 following) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * The result data frame + * + * +----+----+----+----+----+----------+----------+ + |Id_1|Id_2|Year|Me_1|Me_2|min_Me_1|min_Me_2| + +----+----+----+----+----+--------+--------+ + | A| XX|2000| 3| 1.0| 3| 1.0| + | A| XX|2001| 4| 9.0| 3| 1.0| + | A| XX|2002| 7| 5.0| 3| 1.0| + | A| XX|2003| 6| 8.0| 4| 3.0| + | A| YY|2000| 9| 3.0| 5| 2.0| + | A| YY|2001| 5| 4.0| 5| 2.0| + | A| YY|2002| 10| 2.0| 5| 2.0| + | A| YY|2003| 5| 7.0| 5| 2.0| + +----+----+----+----+----+--------+--------+ + + * */ + assertThat(((Dataset) engine.getContext().getAttribute("res")).getDataAsMap()) + .containsExactly( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 3L, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 3L, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 3L, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 4L, "Me_2", 3.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 5L, "Me_2", 2.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 5L, "Me_2", 2.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 5L, "Me_2", 2.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 5L, "Me_2", 2.0D)); + } + + @Test + public void testAnMinWithPartitionOrderByRangeClause() throws ScriptException { + + // Analytical function count test case 5 : min on window with partition, orderBy and range + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", anCountDS1, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := min ( ds1 over ( partition by Id_1 order by Year range between 1 preceding and 1 following) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * The result data frame + * + * +----+----+----+----+----+----------+----------+ + |Id_1|Id_2|Year|Me_1|Me_2|min_Me_1|min_Me_2| + +----+----+----+----+----+--------+--------+ + | A| XX|2000| 3| 1.0| 3| 1.0| + | A| YY|2000| 9| 3.0| 3| 1.0| + | A| XX|2001| 4| 9.0| 3| 1.0| + | A| YY|2001| 5| 4.0| 3| 1.0| + | A| XX|2002| 7| 5.0| 4| 2.0| + | A| YY|2002| 10| 2.0| 4| 2.0| + | A| XX|2003| 6| 8.0| 5| 2.0| + | A| YY|2003| 5| 7.0| 5| 2.0| + +----+----+----+----+----+--------+--------+ + + * */ + assertThat(((Dataset) engine.getContext().getAttribute("res")).getDataAsMap()) + .containsExactlyInAnyOrder( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 3L, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 3L, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 3L, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 3L, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 4L, "Me_2", 2.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 4L, "Me_2", 2.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 5L, "Me_2", 2.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 5L, "Me_2", 2.0D)); + } +} diff --git a/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticRankTest.java b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticRankTest.java new file mode 100644 index 000000000..d0bddfd70 --- /dev/null +++ b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticRankTest.java @@ -0,0 +1,184 @@ +package fr.insee.vtl.spark.processing.engine.analytic; + +import static org.assertj.core.api.Assertions.assertThat; + +import fr.insee.vtl.model.Dataset; +import java.util.Map; +import javax.script.ScriptContext; +import javax.script.ScriptException; +import org.junit.jupiter.api.Test; + +public class AnalyticRankTest extends AnalyticTest { + + /* + * Test case for analytic function rank + * ** rank analytic clause restriction** + * - Must have `orderClause` + * - The `windowClause` such as `data points` and `range` are not allowed + * */ + @Test + public void testAnRankAscClause() throws ScriptException { + + // Analytical function Test case 1 : rank on window with partition and asc order + /* Input dataset + +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|1993| 3| 1.0| + | A| XX|1994| 4| 9.0| + | A| XX|1995| 7| 5.0| + | A| XX|1996| 6| 8.0| + | A| YY|1993| 9| 3.0| + | A| YY|1994| 5| 4.0| + | A| YY|1995| 10| 2.0| + | A| YY|1996| 2| 7.0| + +----+----+----+----+----+ + + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds2", ds2, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := ds2 [calc rank_col := rank ( over ( partition by Id_1, Id_2 order by Year) )];"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * + +----+----+----+----+----+--------+ + |Id_1|Id_2|Year|Me_1|Me_2|rank_col| + +----+----+----+----+----+--------+ + | A| XX|1993| 3| 1.0| 1| + | A| XX|1994| 4| 9.0| 2| + | A| XX|1995| 7| 5.0| 3| + | A| XX|1996| 6| 8.0| 4| + | A| YY|1993| 9| 3.0| 1| + | A| YY|1994| 5| 4.0| 2| + | A| YY|1995| 10| 2.0| 3| + | A| YY|1996| 2| 7.0| 4| + +----+----+----+----+----+--------+ + * */ + assertThat(((Dataset) engine.getContext().getAttribute("res")).getDataAsMap()) + .containsExactly( + Map.of( + "Id_1", "A", "Id_2", "XX", "Year", 1993L, "Me_1", 3L, "Me_2", 1.0D, "rank_col", 1L), + Map.of( + "Id_1", "A", "Id_2", "XX", "Year", 1994L, "Me_1", 4L, "Me_2", 9.0D, "rank_col", 2L), + Map.of( + "Id_1", "A", "Id_2", "XX", "Year", 1995L, "Me_1", 7L, "Me_2", 5.0D, "rank_col", 3L), + Map.of( + "Id_1", "A", "Id_2", "XX", "Year", 1996L, "Me_1", 6L, "Me_2", 8.0D, "rank_col", 4L), + Map.of( + "Id_1", "A", "Id_2", "YY", "Year", 1993L, "Me_1", 9L, "Me_2", 3.0D, "rank_col", 1L), + Map.of( + "Id_1", "A", "Id_2", "YY", "Year", 1994L, "Me_1", 5L, "Me_2", 4.0D, "rank_col", 2L), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 1995L, + "Me_1", + 10L, + "Me_2", + 2.0D, + "rank_col", + 3L), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 1996L, + "Me_1", + 2L, + "Me_2", + 7.0D, + "rank_col", + 4L)); + } + + @Test + public void testAnRankDesc() throws ScriptException { + + // Analytical function Test case 2 : rank on window with partition and desc order + /* Input dataset + +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|1993| 3| 1.0| + | A| XX|1994| 4| 9.0| + | A| XX|1995| 7| 5.0| + | A| XX|1996| 6| 8.0| + | A| YY|1993| 9| 3.0| + | A| YY|1994| 5| 4.0| + | A| YY|1995| 10| 2.0| + | A| YY|1996| 2| 7.0| + +----+----+----+----+----+ + + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds2", ds2, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := ds2 [calc rank_col:= rank ( over ( partition by Id_1, Id_2 order by Year desc) )];"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * + +----+----+----+----+----+--------+ + |Id_1|Id_2|Year|Me_1|Me_2|rank_col| + +----+----+----+----+----+--------+ + | A| XX|1996| 6| 8.0| 1| + | A| XX|1995| 7| 5.0| 2| + | A| XX|1994| 4| 9.0| 3| + | A| XX|1993| 3| 1.0| 4| + | A| YY|1996| 2| 7.0| 1| + | A| YY|1995| 10| 2.0| 2| + | A| YY|1994| 5| 4.0| 3| + | A| YY|1993| 9| 3.0| 4| + +----+----+----+----+----+--------+ + * */ + assertThat(((Dataset) engine.getContext().getAttribute("res")).getDataAsMap()) + .containsExactly( + Map.of( + "Id_1", "A", "Id_2", "XX", "Year", 1996L, "Me_1", 6L, "Me_2", 8.0D, "rank_col", 1L), + Map.of( + "Id_1", "A", "Id_2", "XX", "Year", 1995L, "Me_1", 7L, "Me_2", 5.0D, "rank_col", 2L), + Map.of( + "Id_1", "A", "Id_2", "XX", "Year", 1994L, "Me_1", 4L, "Me_2", 9.0D, "rank_col", 3L), + Map.of( + "Id_1", "A", "Id_2", "XX", "Year", 1993L, "Me_1", 3L, "Me_2", 1.0D, "rank_col", 4L), + Map.of( + "Id_1", "A", "Id_2", "YY", "Year", 1996L, "Me_1", 2L, "Me_2", 7.0D, "rank_col", 1L), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 1995L, + "Me_1", + 10L, + "Me_2", + 2.0D, + "rank_col", + 2L), + Map.of( + "Id_1", "A", "Id_2", "YY", "Year", 1994L, "Me_1", 5L, "Me_2", 4.0D, "rank_col", 3L), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 1993L, + "Me_1", + 9L, + "Me_2", + 3.0D, + "rank_col", + 4L)); + } +} diff --git a/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticRatioToReportTest.java b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticRatioToReportTest.java new file mode 100644 index 000000000..0adf4260f --- /dev/null +++ b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticRatioToReportTest.java @@ -0,0 +1,283 @@ +package fr.insee.vtl.spark.processing.engine.analytic; + +import static org.assertj.core.api.Assertions.assertThat; + +import fr.insee.vtl.model.Dataset; +import fr.insee.vtl.model.InMemoryDataset; +import java.util.List; +import java.util.Map; +import javax.script.ScriptContext; +import javax.script.ScriptException; +import org.junit.jupiter.api.Test; + +public class AnalyticRatioToReportTest extends AnalyticTest { + + @Test + public void testAnRatioToReportWithCalcClause() throws ScriptException { + + InMemoryDataset anDS = + new InMemoryDataset( + List.of( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 3L, "Me_2", 1D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 4L, "Me_2", 3D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 7L, "Me_2", 5D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 6L, "Me_2", 1D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 12L, "Me_2", 0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 8L, "Me_2", 8D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 6L, "Me_2", 5D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 14L, "Me_2", -3D)), + Map.of( + "Id_1", + String.class, + "Id_2", + String.class, + "Year", + Long.class, + "Me_1", + Long.class, + "Me_2", + Double.class), + Map.of( + "Id_1", + Dataset.Role.IDENTIFIER, + "Id_2", + Dataset.Role.IDENTIFIER, + "Year", + Dataset.Role.IDENTIFIER, + "Me_1", + Dataset.Role.MEASURE, + "Me_2", + Dataset.Role.MEASURE)); + /* Input dataset + +----+----+----+----+----+ + |Id_1|Id_2|Id_3|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1| + | A| XX|2001| 4| 3| + | A| XX|2002| 7| 5| + | A| XX|2003| 6| 1| + | A| YY|2000| 12| 0| + | A| YY|2001| 8| 8| + | A| YY|2002| 6| 5| + | A| YY|2003| 14| -3| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds", anDS, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := ds [ calc ratio_Me_1 := ratio_to_report ( Me_1 over ( partition by Id_1, Id_2 ) )];"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * + +----+----+----+----+----+----------+ + |Id_1|Id_2|Id_3|Me_1|Me_2|ratio_Me_1| + +----+----+----+----+----+----------+ + | A| XX|2000| 3| 1| 0.15| + | A| XX|2001| 4| 3| 0.2| + | A| XX|2002| 7| 5| 0.35| + | A| XX|2003| 6| 1| 0.3| + | A| YY|2000| 12| 0| 0.3| + | A| YY|2001| 8| 8| 0.2| + | A| YY|2002| 6| 5| 0.15| + | A| YY|2003| 14| -3| 0.35| + +----+----+----+----+----+----------+ + * */ + assertThat(((Dataset) engine.getContext().getAttribute("res")).getDataAsMap()) + .containsExactly( + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 2000L, + "Me_1", + 3L, + "Me_2", + 1.0D, + "ratio_Me_1", + 0.15D), + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 2001L, + "Me_1", + 4L, + "Me_2", + 3.0D, + "ratio_Me_1", + 0.2D), + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 2002L, + "Me_1", + 7L, + "Me_2", + 5.0D, + "ratio_Me_1", + 0.35D), + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 2003L, + "Me_1", + 6L, + "Me_2", + 1.0D, + "ratio_Me_1", + 0.3D), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2000L, + "Me_1", + 12L, + "Me_2", + 0.0D, + "ratio_Me_1", + 0.3D), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2001L, + "Me_1", + 8L, + "Me_2", + 8.0D, + "ratio_Me_1", + 0.2D), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2002L, + "Me_1", + 6L, + "Me_2", + 5.0D, + "ratio_Me_1", + 0.15D), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2003L, + "Me_1", + 14L, + "Me_2", + -3.0D, + "ratio_Me_1", + 0.35D)); + } + + /* + * Test case for analytic function ratio_to_report + * Analytic clause restriction: + * - The orderClause and windowClause of the Analytic invocation syntax are not allowed. + * */ + + @Test + public void testAnRatioToReport() throws ScriptException { + + InMemoryDataset anDS = + new InMemoryDataset( + List.of( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 3L, "Me_2", 1D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 4L, "Me_2", 3D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 7L, "Me_2", 5D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 6L, "Me_2", 1D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 12L, "Me_2", 0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 8L, "Me_2", 8D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 6L, "Me_2", 5D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 14L, "Me_2", -3D)), + Map.of( + "Id_1", + String.class, + "Id_2", + String.class, + "Year", + Long.class, + "Me_1", + Long.class, + "Me_2", + Double.class), + Map.of( + "Id_1", + Dataset.Role.IDENTIFIER, + "Id_2", + Dataset.Role.IDENTIFIER, + "Year", + Dataset.Role.IDENTIFIER, + "Me_1", + Dataset.Role.MEASURE, + "Me_2", + Dataset.Role.MEASURE)); + /* Input dataset + +----+----+----+----+----+ + |Id_1|Id_2|Id_3|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1| + | A| XX|2001| 4| 3| + | A| XX|2002| 7| 5| + | A| XX|2003| 6| 1| + | A| YY|2000| 12| 0| + | A| YY|2001| 8| 8| + | A| YY|2002| 6| 5| + | A| YY|2003| 14| -3| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds", anDS, ScriptContext.ENGINE_SCOPE); + + engine.eval("res := ratio_to_report ( ds over ( partition by Id_1, Id_2 ) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * + +----+----+----+----+----+----------+----------+----------+----------+ + |Id_1|Id_2|Id_3|Me_1|Me_2|total_Me_1|ratio_Me_1|total_Me_2|ratio_Me_2| + +----+----+----+----+----+----------+----------+----------+----------+ + | A| XX|2000| 3| 1| 20| 0.15| 10| 0.1| + | A| XX|2001| 4| 3| 20| 0.2| 10| 0.3| + | A| XX|2002| 7| 5| 20| 0.35| 10| 0.5| + | A| XX|2003| 6| 1| 20| 0.3| 10| 0.1| + | A| YY|2000| 12| 0| 40| 0.3| 10| 0.0| + | A| YY|2001| 8| 8| 40| 0.2| 10| 0.8| + | A| YY|2002| 6| 5| 40| 0.15| 10| 0.5| + | A| YY|2003| 14| -3| 40| 0.35| 10| -0.3| + +----+----+----+----+----+----------+----------+----------+----------+ + * */ + assertThat(((Dataset) engine.getContext().getAttribute("res")).getDataAsMap()) + .containsExactly( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 0.15D, "Me_2", 0.1D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 0.2D, "Me_2", 0.3D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 0.35D, "Me_2", 0.5D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 0.3D, "Me_2", 0.1D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 0.3D, "Me_2", 0.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 0.2D, "Me_2", 0.8D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 0.15D, "Me_2", 0.5D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 0.35D, "Me_2", -0.3D)); + } +} diff --git a/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticStdPopTest.java b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticStdPopTest.java new file mode 100644 index 000000000..67ee09d41 --- /dev/null +++ b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticStdPopTest.java @@ -0,0 +1,388 @@ +package fr.insee.vtl.spark.processing.engine.analytic; + +import static org.assertj.core.api.Assertions.assertThat; + +import fr.insee.vtl.model.Dataset; +import java.util.List; +import java.util.Map; +import javax.script.ScriptContext; +import javax.script.ScriptException; +import org.junit.jupiter.api.Test; + +public class AnalyticStdPopTest extends AnalyticTest { + + @Test + public void testAnStdPopWithCalcClause() throws ScriptException { + + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", ds1, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := ds1 [ calc stddev_pop_Me_1:= stddev_pop ( Me_1 over ( partition by Id_1,Id_2 order by Year) )];"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * + +----+----+----+----+----+------------------+ + |Id_1|Id_2|Year|Me_1|Me_2| stddev_pop_Me_1| + +----+----+----+----+----+------------------+ + | A| XX|2000| 3| 1.0| 0.0| + | A| XX|2001| 4| 9.0| 0.5| + | A| XX|2002| 7| 5.0| 1.699673171197595| + | A| XX|2003| 6| 8.0|1.5811388300841895| + | A| YY|2000| 9| 3.0| 0.0| + | A| YY|2001| 5| 4.0| 2.0| + | A| YY|2002| 10| 2.0| 2.160246899469287| + | A| YY|2003| 5| 7.0| 2.277608394786075| + +----+----+----+----+----+------------------+ + * */ + List> actual = + ((Dataset) engine.getContext().getAttribute("res")).getDataAsMap(); + + assertThat(actual) + .containsExactly( + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 2000L, + "Me_1", + 3L, + "Me_2", + 1.0D, + "stddev_pop_Me_1", + 0.0D), + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 2001L, + "Me_1", + 4L, + "Me_2", + 9.0D, + "stddev_pop_Me_1", + 0.5D), + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 2002L, + "Me_1", + 7L, + "Me_2", + 5.0D, + "stddev_pop_Me_1", + 1.699673171197595D), + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 2003L, + "Me_1", + 6L, + "Me_2", + 8.0D, + "stddev_pop_Me_1", + 1.5811388300841895D), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2000L, + "Me_1", + 9L, + "Me_2", + 3.0D, + "stddev_pop_Me_1", + 0.0D), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2001L, + "Me_1", + 5L, + "Me_2", + 4.0D, + "stddev_pop_Me_1", + 2.0D), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2002L, + "Me_1", + 10L, + "Me_2", + 2.0D, + "stddev_pop_Me_1", + 2.160246899469287D), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2003L, + "Me_1", + 5L, + "Me_2", + 7.0D, + "stddev_pop_Me_1", + 2.277608394786075D)); + } + + @Test + public void testAnStdPopWithPartitionClause() throws ScriptException { + + // Analytical function Test case 1 : stddev_pop on window with partition + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", ds1, ScriptContext.ENGINE_SCOPE); + + engine.eval("res := stddev_pop ( ds1 over ( partition by Id_1, Id_2 ) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * + + +----+----+----+----+----+------------------+------------------+ + |Id_1|Id_2|Year|Me_1|Me_2| std_pop_Me_1| std_pop_Me_2| + +----+----+----+----+----+------------------+------------------+ + | A| XX|2000| 3| 1.0|1.5811388300841895| 3.112474899497183| + | A| XX|2001| 4| 9.0|1.5811388300841895| 3.112474899497183| + | A| XX|2002| 7| 5.0|1.5811388300841895| 3.112474899497183| + | A| XX|2003| 6| 8.0|1.5811388300841895| 3.112474899497183| + | A| YY|2000| 9| 3.0| 2.277608394786075|1.8708286933869707| + | A| YY|2001| 5| 4.0| 2.277608394786075|1.8708286933869707| + | A| YY|2002| 10| 2.0| 2.277608394786075|1.8708286933869707| + | A| YY|2003| 5| 7.0| 2.277608394786075|1.8708286933869707| + +----+----+----+----+----+------------------+------------------+ + * */ + List> res = + AnalyticTest.roundDecimalInDataset( + (Dataset) engine.getContext().getAttribute("res"), AnalyticTest.DEFAULT_PRECISION); + assertThat(res) + .containsExactly( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 1.58D, "Me_2", 3.11D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 1.58D, "Me_2", 3.11D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 1.58D, "Me_2", 3.11D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 1.58D, "Me_2", 3.11D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 2.28D, "Me_2", 1.87D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 2.28D, "Me_2", 1.87D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 2.28D, "Me_2", 1.87D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 2.28D, "Me_2", 1.87D)); + } + + @Test + public void testAnStdPopWithPartitionOrderByClause() throws ScriptException { + + // Analytical function Test case 2 : stddev_pop on window with partition and order by + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", ds1, ScriptContext.ENGINE_SCOPE); + + engine.eval("res := stddev_pop ( ds1 over ( partition by Id_1, Id_2 order by Year) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + +----+----+----+----+----+------------------+------------------+ + |Id_1|Id_2|Year|Me_1|Me_2| std_pop_Me_1| std_pop_Me_2| + +----+----+----+----+----+------------------+------------------+ + | A| XX|2000| 3| 1.0| 0.0| 0.0| + | A| XX|2001| 4| 9.0| 0.5| 4.0| + | A| XX|2002| 7| 5.0| 1.699673171197595| 3.265986323710904| + | A| XX|2003| 6| 8.0|1.5811388300841895| 3.112474899497183| + | A| YY|2000| 9| 3.0| 0.0| 0.0| + | A| YY|2001| 5| 4.0| 2.0| 0.5| + | A| YY|2002| 10| 2.0| 2.160246899469287| 0.816496580927726| + | A| YY|2003| 5| 7.0| 2.277608394786075|1.8708286933869707| + +----+----+----+----+----+------------------+------------------+ + + * */ + List> res = + AnalyticTest.roundDecimalInDataset( + (Dataset) engine.getContext().getAttribute("res"), AnalyticTest.DEFAULT_PRECISION); + assertThat(res) + .containsExactly( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 0.0D, "Me_2", 0.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 0.5D, "Me_2", 4.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 1.70D, "Me_2", 3.27D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 1.58D, "Me_2", 3.11D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 0.0D, "Me_2", 0.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 2.0D, "Me_2", 0.5D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 2.16D, "Me_2", 0.82D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 2.28D, "Me_2", 1.87D)); + } + + @Test + public void testAnStdPopWithPartitionOrderByDPClause() throws ScriptException { + + // Analytical function count test case 3 : stddev_pop on window with partition, orderBy and data + // points + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", ds1, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := stddev_pop ( ds1 over ( partition by Id_1 order by Id_2 data points between 2 preceding and 2 following) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * The result data frame + * + +----+----+----+----+----+------------------+------------------+ + |Id_1|Id_2|Year|Me_1|Me_2| std_pop_Me_1| std_pop_Me_2| + +----+----+----+----+----+------------------+------------------+ + | A| XX|2000| 3| 1.0| 1.699673171197595| 3.265986323710904| + | A| XX|2001| 4| 9.0|1.5811388300841895| 3.112474899497183| + | A| XX|2002| 7| 5.0|2.1354156504062622| 2.993325909419153| + | A| XX|2003| 6| 8.0|1.7204650534085253|2.3151673805580453| + | A| YY|2000| 9| 3.0|1.8547236990991407| 2.0591260281974| + | A| YY|2001| 5| 4.0|2.0976176963403033|2.3151673805580453| + | A| YY|2002| 10| 2.0| 2.277608394786075|1.8708286933869707| + | A| YY|2003| 5| 7.0| 2.357022603955158|2.0548046676563256| + +----+----+----+----+----+------------------+------------------+ + + * */ + List> res = + AnalyticTest.roundDecimalInDataset( + (Dataset) engine.getContext().getAttribute("res"), AnalyticTest.DEFAULT_PRECISION); + assertThat(res) + .containsExactly( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 1.70D, "Me_2", 3.27D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 1.58D, "Me_2", 3.11D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 2.14D, "Me_2", 2.99D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 1.72D, "Me_2", 2.32D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 1.85D, "Me_2", 2.06D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 2.1D, "Me_2", 2.32D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 2.28D, "Me_2", 1.87D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 2.36D, "Me_2", 2.05D)); + } + + @Test + public void testAnStdPopWithPartitionOrderByRangeClause() throws ScriptException { + + // Analytical function count test case 4 : stddev_pop on window with partition, orderBy and + // range + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", ds1, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := stddev_pop ( ds1 over ( partition by Id_1 order by Year range between 1 preceding and 1 following) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * The result data frame + * + +----+----+----+----+----+------------------+------------------+ + |Id_1|Id_2|Year|Me_1|Me_2| std_pop_Me_1| std_pop_Me_2| + +----+----+----+----+----+------------------+------------------+ + | A| XX|2000| 3| 1.0| 2.277608394786075| 2.947456530637899| + | A| YY|2000| 9| 3.0| 2.277608394786075| 2.947456530637899| + | A| XX|2001| 4| 9.0| 2.560381915956203|2.5819888974716116| + | A| YY|2001| 5| 4.0| 2.560381915956203|2.5819888974716116| + | A| XX|2002| 7| 5.0|1.9507833184532708|2.4094720491334933| + | A| YY|2002| 10| 2.0|1.9507833184532708|2.4094720491334933| + | A| XX|2003| 6| 8.0|1.8708286933869707| 2.29128784747792| + | A| YY|2003| 5| 7.0|1.8708286933869707| 2.29128784747792| + +----+----+----+----+----+------------------+------------------+ + + * */ + List> res = + AnalyticTest.roundDecimalInDataset( + (Dataset) engine.getContext().getAttribute("res"), AnalyticTest.DEFAULT_PRECISION); + assertThat(res) + .containsExactly( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 2.28D, "Me_2", 2.95D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 2.28D, "Me_2", 2.95D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 2.56D, "Me_2", 2.58D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 2.56D, "Me_2", 2.58D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 1.95D, "Me_2", 2.41D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 1.95D, "Me_2", 2.41D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 1.87D, "Me_2", 2.29D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 1.87D, "Me_2", 2.29D)); + } +} diff --git a/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticStdSampTest.java b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticStdSampTest.java new file mode 100644 index 000000000..0e6bda56f --- /dev/null +++ b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticStdSampTest.java @@ -0,0 +1,403 @@ +package fr.insee.vtl.spark.processing.engine.analytic; + +import static org.assertj.core.api.Assertions.assertThat; + +import fr.insee.vtl.model.Dataset; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import javax.script.ScriptContext; +import javax.script.ScriptException; +import org.junit.jupiter.api.Test; + +public class AnalyticStdSampTest extends AnalyticTest { + + @Test + public void testAnStdSampWithCalcClause() throws ScriptException { + + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", ds1, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := ds1 [ calc stddev_samp_Me_1:= stddev_samp ( Me_1 over ( partition by Id_1,Id_2 order by Year) )];"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * + +----+----+----+----+----+------------------+ + |Id_1|Id_2|Year|Me_1|Me_2| stddev_samp_Me_1| + +----+----+----+----+----+------------------+ + | A| XX|2000| 3| 1.0| null| + | A| XX|2001| 4| 9.0|0.7071067811865476| + | A| XX|2002| 7| 5.0|2.0816659994661326| + | A| XX|2003| 6| 8.0|1.8257418583505536| + | A| YY|2000| 9| 3.0| null| + | A| YY|2001| 5| 4.0|2.8284271247461903| + | A| YY|2002| 10| 2.0|2.6457513110645907| + | A| YY|2003| 5| 7.0|2.6299556396765835| + +----+----+----+----+----+------------------+ + * */ + List> actualWithNull = + ((Dataset) engine.getContext().getAttribute("res")).getDataAsMap(); + + List> actual = new ArrayList<>(); + for (Map map : actualWithNull) { + actual.add(replaceNullValues(map, DEFAULT_NULL_STR)); + } + + assertThat(actual) + .containsExactly( + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 2000L, + "Me_1", + 3L, + "Me_2", + 1.0D, + "stddev_samp_Me_1", + "null"), + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 2001L, + "Me_1", + 4L, + "Me_2", + 9.0D, + "stddev_samp_Me_1", + 0.7071067811865476D), + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 2002L, + "Me_1", + 7L, + "Me_2", + 5.0D, + "stddev_samp_Me_1", + 2.0816659994661326D), + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 2003L, + "Me_1", + 6L, + "Me_2", + 8.0D, + "stddev_samp_Me_1", + 1.8257418583505536D), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2000L, + "Me_1", + 9L, + "Me_2", + 3.0D, + "stddev_samp_Me_1", + "null"), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2001L, + "Me_1", + 5L, + "Me_2", + 4.0D, + "stddev_samp_Me_1", + 2.8284271247461903D), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2002L, + "Me_1", + 10L, + "Me_2", + 2.0D, + "stddev_samp_Me_1", + 2.6457513110645907D), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2003L, + "Me_1", + 5L, + "Me_2", + 7.0D, + "stddev_samp_Me_1", + 2.6299556396765835D)); + } + + @Test + public void testAnStdSampWithPartitionClause() throws ScriptException { + + // Analytical function Test case 1 : stddev_samp on window with partition + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", ds1, ScriptContext.ENGINE_SCOPE); + + engine.eval("res := stddev_samp ( ds1 over ( partition by Id_1, Id_2 ) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * + + +----+----+----+----+----+------------------+-----------------+ + |Id_1|Id_2|Year|Me_1|Me_2| std_samp_Me_1| std_samp_Me_2| + +----+----+----+----+----+------------------+-----------------+ + | A| XX|2000| 3| 1.0|1.8257418583505536|3.593976442141304| + | A| XX|2001| 4| 9.0|1.8257418583505536|3.593976442141304| + | A| XX|2002| 7| 5.0|1.8257418583505536|3.593976442141304| + | A| XX|2003| 6| 8.0|1.8257418583505536|3.593976442141304| + | A| YY|2000| 9| 3.0|2.6299556396765835|2.160246899469287| + | A| YY|2001| 5| 4.0|2.6299556396765835|2.160246899469287| + | A| YY|2002| 10| 2.0|2.6299556396765835|2.160246899469287| + | A| YY|2003| 5| 7.0|2.6299556396765835|2.160246899469287| + +----+----+----+----+----+------------------+-----------------+ + * */ + List> res = + AnalyticTest.roundDecimalInDataset( + (Dataset) engine.getContext().getAttribute("res"), AnalyticTest.DEFAULT_PRECISION); + assertThat(res) + .containsExactly( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 1.83D, "Me_2", 3.59D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 1.83D, "Me_2", 3.59D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 1.83D, "Me_2", 3.59D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 1.83D, "Me_2", 3.59D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 2.63D, "Me_2", 2.16D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 2.63D, "Me_2", 2.16D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 2.63D, "Me_2", 2.16D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 2.63D, "Me_2", 2.16D)); + } + + @Test + public void testAnStdSampWithPartitionOrderByClause() throws ScriptException { + + // Analytical function Test case 2 : stddev_samp on window with partition and order by + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", ds1, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := stddev_samp ( ds1 over ( partition by Id_1, Id_2 order by Year) );" + + "res1 := res[calc Me_1 := round(Me_1, 2), Me_2 := round(Me_2, 2)];"); + assertThat(engine.getContext().getAttribute("res1")).isInstanceOf(Dataset.class); + + /* + +----+----+----+----+----+------------------+------------------+ + |Id_1|Id_2|Year|Me_1|Me_2| std_samp_Me_1| std_samp_Me_2| + +----+----+----+----+----+------------------+------------------+ + | A| XX|2000| 3| 1.0| null| null| + | A| XX|2001| 4| 9.0|0.7071067811865476| 5.656854249492381| + | A| XX|2002| 7| 5.0|2.0816659994661326| 4.0| + | A| XX|2003| 6| 8.0|1.8257418583505536| 3.593976442141304| + | A| YY|2000| 9| 3.0| null| null| + | A| YY|2001| 5| 4.0|2.8284271247461903|0.7071067811865476| + | A| YY|2002| 10| 2.0|2.6457513110645907| 1.0| + | A| YY|2003| 5| 7.0|2.6299556396765835| 2.160246899469287| + +----+----+----+----+----+------------------+------------------+ + + * */ + + var actual = + ((Dataset) engine.getContext().getAttribute("res1")) + .getDataAsMap().stream() + .map(map -> replaceNullValues(map, DEFAULT_NULL_STR)) + .collect(Collectors.toList()); + assertThat(actual) + .containsExactly( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", "null", "Me_2", "null"), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 0.71D, "Me_2", 5.66D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 2.08D, "Me_2", 4.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 1.83D, "Me_2", 3.59D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", "null", "Me_2", "null"), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 2.83D, "Me_2", 0.71D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 2.65D, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 2.63D, "Me_2", 2.16D)); + } + + @Test + public void testAnStdSampWithPartitionOrderByDPClause() throws ScriptException { + + // Analytical function count test case 3 : stddev_samp on window with partition, orderBy and + // data points + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", ds1, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := stddev_samp ( ds1 over ( partition by Id_1 order by Id_2 data points between 2 preceding and 2 following) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * The result data frame + * + +----+----+----+----+----+------------------+------------------+ + |Id_1|Id_2|Year|Me_1|Me_2| std_samp_Me_1| std_samp_Me_2| + +----+----+----+----+----+------------------+------------------+ + | A| XX|2000| 3| 1.0|2.0816659994661326| 4.0| + | A| XX|2001| 4| 9.0|1.8257418583505536| 3.593976442141304| + | A| XX|2002| 7| 5.0|2.3874672772626644|3.3466401061363023| + | A| XX|2003| 6| 8.0|1.9235384061671346| 2.588435821108957| + | A| YY|2000| 9| 3.0| 2.073644135332772|2.3021728866442674| + | A| YY|2001| 5| 4.0| 2.345207879911715| 2.588435821108957| + | A| YY|2002| 10| 2.0|2.6299556396765835| 2.160246899469287| + | A| YY|2003| 5| 7.0|2.8867513459481287|2.5166114784235836| + +----+----+----+----+----+------------------+------------------+ + + + * */ + + List> res = + AnalyticTest.roundDecimalInDataset( + (Dataset) engine.getContext().getAttribute("res"), AnalyticTest.DEFAULT_PRECISION); + + assertThat(res) + .contains( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 2.08D, "Me_2", 4.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 1.83D, "Me_2", 3.59D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 2.39D, "Me_2", 3.35D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 1.92D, "Me_2", 2.59D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 2.07D, "Me_2", 2.30D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 2.35D, "Me_2", 2.59D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 2.63D, "Me_2", 2.16D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 2.89D, "Me_2", 2.52D)); + } + + @Test + public void testAnStdSampWithPartitionOrderByRangeClause() throws ScriptException { + + // Analytical function count test case 4 : stddev_samp on window with partition, orderBy and + // range + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", ds1, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := stddev_samp ( ds1 over ( partition by Id_1 order by Year range between 1 preceding and 1 following) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * The result data frame + * + +----+----+----+----+----+------------------+------------------+ + |Id_1|Id_2|Year|Me_1|Me_2| std_samp_Me_1| std_samp_Me_2| + +----+----+----+----+----+------------------+------------------+ + | A| XX|2000| 3| 1.0|2.6299556396765835|3.4034296427770228| + | A| YY|2000| 9| 3.0|2.6299556396765835|3.4034296427770228| + | A| XX|2001| 4| 9.0|2.8047578623950176|2.8284271247461903| + | A| YY|2001| 5| 4.0|2.8047578623950176|2.8284271247461903| + | A| XX|2002| 7| 5.0| 2.136976056643281|2.6394443859772205| + | A| YY|2002| 10| 2.0| 2.136976056643281|2.6394443859772205| + | A| XX|2003| 6| 8.0| 2.160246899469287|2.6457513110645907| + | A| YY|2003| 5| 7.0| 2.160246899469287|2.6457513110645907| + +----+----+----+----+----+------------------+------------------+ + + * */ + List> res = + AnalyticTest.roundDecimalInDataset( + (Dataset) engine.getContext().getAttribute("res"), AnalyticTest.DEFAULT_PRECISION); + assertThat(res) + .contains( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 2.63D, "Me_2", 3.40D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 2.63D, "Me_2", 3.40D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 2.80D, "Me_2", 2.83D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 2.80D, "Me_2", 2.83D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 2.14D, "Me_2", 2.64D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 2.14D, "Me_2", 2.64D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 2.16D, "Me_2", 2.65D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 2.16D, "Me_2", 2.65D)); + } +} diff --git a/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticSumTest.java b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticSumTest.java new file mode 100644 index 000000000..b45a5c190 --- /dev/null +++ b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticSumTest.java @@ -0,0 +1,458 @@ +package fr.insee.vtl.spark.processing.engine.analytic; + +import static org.assertj.core.api.Assertions.assertThat; + +import fr.insee.vtl.engine.VtlScriptEngine; +import fr.insee.vtl.model.Dataset; +import fr.insee.vtl.model.InMemoryDataset; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import javax.script.ScriptContext; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import org.apache.spark.sql.SparkSession; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class AnalyticSumTest { + + private final InMemoryDataset anCountDS1 = + new InMemoryDataset( + List.of( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 3L, "Me_2", 1D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 4L, "Me_2", 9D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 7L, "Me_2", 5D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 6L, "Me_2", 8D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 9L, "Me_2", 3D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 5L, "Me_2", 4D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 10L, "Me_2", 2D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 5L, "Me_2", 7D)), + Map.of( + "Id_1", + String.class, + "Id_2", + String.class, + "Year", + Long.class, + "Me_1", + Long.class, + "Me_2", + Double.class), + Map.of( + "Id_1", + Dataset.Role.IDENTIFIER, + "Id_2", + Dataset.Role.IDENTIFIER, + "Year", + Dataset.Role.IDENTIFIER, + "Me_1", + Dataset.Role.MEASURE, + "Me_2", + Dataset.Role.MEASURE)); + + private static SparkSession spark; + private static ScriptEngine engine; + + @BeforeEach + public void setUp() { + + ScriptEngineManager mgr = new ScriptEngineManager(); + engine = mgr.getEngineByExtension("vtl"); + + spark = SparkSession.builder().appName("test").master("local").getOrCreate(); + SparkSession.setActiveSession(spark); + + engine.put(VtlScriptEngine.PROCESSING_ENGINE_NAMES, "spark4"); + } + + @AfterAll + public static void tearDown() throws IOException { + if (spark != null) spark.close(); + } + + @Test + public void testAnSumWithCalcClause() throws ScriptException { + + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", anCountDS1, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := ds1 [ calc sum_Me_1:= sum ( Me_1 over ( partition by Id_1,Id_2 order by Year) )];"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * + +----+----+----+----+----+--------+ + |Id_1|Id_2|Year|Me_1|Me_2|sum_Me_1| + +----+----+----+----+----+--------+ + | A| XX|2000| 3| 1.0| 3| + | A| XX|2001| 4| 9.0| 7| + | A| XX|2002| 7| 5.0| 14| + | A| XX|2003| 6| 8.0| 20| + | A| YY|2000| 9| 3.0| 9| + | A| YY|2001| 5| 4.0| 14| + | A| YY|2002| 10| 2.0| 24| + | A| YY|2003| 5| 7.0| 29| + +----+----+----+----+----+--------+ + * */ + List> actual = + ((Dataset) engine.getContext().getAttribute("res")).getDataAsMap(); + + assertThat(actual) + .containsExactly( + Map.of( + "Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 3L, "Me_2", 1.0D, "sum_Me_1", 3L), + Map.of( + "Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 4L, "Me_2", 9.0D, "sum_Me_1", 7L), + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 2002L, + "Me_1", + 7L, + "Me_2", + 5.0D, + "sum_Me_1", + 14L), + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 2003L, + "Me_1", + 6L, + "Me_2", + 8.0D, + "sum_Me_1", + 20L), + Map.of( + "Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 9L, "Me_2", 3.0D, "sum_Me_1", 9L), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2001L, + "Me_1", + 5L, + "Me_2", + 4.0D, + "sum_Me_1", + 14L), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2002L, + "Me_1", + 10L, + "Me_2", + 2.0D, + "sum_Me_1", + 24L), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2003L, + "Me_1", + 5L, + "Me_2", + 7.0D, + "sum_Me_1", + 29L)); + } + + @Test + public void testAnSumWithPartitionClause() throws ScriptException { + + // Analytical function Test case 1 : sum on window with partition + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", anCountDS1, ScriptContext.ENGINE_SCOPE); + + engine.eval("res := sum ( ds1 over ( partition by Id_1, Id_2 ) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * The result data frame need to check mutable or not mutable on Mesaument column + * + * +----+----+----+----+----+----------+----------+ + |Id_1|Id_2|Year|Me_1|Me_2|sum_Me_1|sum_Me_2| + +----+----+----+----+----+--------+--------+ + | A| XX|2000| 3| 1.0| 20| 23.0| + | A| XX|2001| 4| 9.0| 20| 23.0| + | A| XX|2002| 7| 5.0| 20| 23.0| + | A| XX|2003| 6| 8.0| 20| 23.0| + | A| YY|2000| 9| 3.0| 29| 16.0| + | A| YY|2001| 5| 4.0| 29| 16.0| + | A| YY|2002| 10| 2.0| 29| 16.0| + | A| YY|2003| 5| 7.0| 29| 16.0| + +----+----+----+----+----+--------+--------+ + * */ + assertThat(((Dataset) engine.getContext().getAttribute("res")).getDataAsMap()) + .containsExactly( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 20L, "Me_2", 23.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 20L, "Me_2", 23.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 20L, "Me_2", 23.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 20L, "Me_2", 23.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 29L, "Me_2", 16.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 29L, "Me_2", 16.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 29L, "Me_2", 16.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 29L, "Me_2", 16.0D)); + } + + @Test + public void testAnSumWithPartitionOrderByClause() throws ScriptException { + + // Analytical function Test case 2 : sum on window with partition and order by + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", anCountDS1, ScriptContext.ENGINE_SCOPE); + + engine.eval("res := sum ( ds1 over ( partition by Id_1 order by Id_2) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * The result data frame need to check mutable or not mutable on Mesaument column + * + * +----+----+----+----+----+----------+----------+ + |Id_1|Id_2|Year|Me_1|Me_2|sum_Me_1|sum_Me_2| + +----+----+----+----+----+--------+--------+ + | A| XX|2000| 3| 1.0| 20| 23.0| + | A| XX|2001| 4| 9.0| 20| 23.0| + | A| XX|2002| 7| 5.0| 20| 23.0| + | A| XX|2003| 6| 8.0| 20| 23.0| + | A| YY|2000| 9| 3.0| 49| 39.0| + | A| YY|2001| 5| 4.0| 49| 39.0| + | A| YY|2002| 10| 2.0| 49| 39.0| + | A| YY|2003| 5| 7.0| 49| 39.0| + +----+----+----+----+----+--------+--------+ + + * */ + assertThat(((Dataset) engine.getContext().getAttribute("res")).getDataAsMap()) + .containsExactly( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 20L, "Me_2", 23.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 20L, "Me_2", 23.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 20L, "Me_2", 23.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 20L, "Me_2", 23.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 49L, "Me_2", 39.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 49L, "Me_2", 39.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 49L, "Me_2", 39.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 49L, "Me_2", 39.0D)); + } + + @Test + public void testAnSumWithOrderByClause() throws ScriptException { + + // Analytical function Test case 3 : sum on window with only order by without partition + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", anCountDS1, ScriptContext.ENGINE_SCOPE); + + engine.eval("res := sum ( ds1 over ( order by Id_1, Id_2, Year ) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * The result data frame need to check mutable or not mutable on Mesaument column + * + * +----+----+----+----+----+----------+----------+ + |Id_1|Id_2|Year|Me_1|Me_2|sum_Me_1|sum_Me_2| + +----+----+----+----+----+--------+--------+ + | A| XX|2000| 3| 1.0| 3| 1.0| + | A| XX|2001| 4| 9.0| 7| 10.0| + | A| XX|2002| 7| 5.0| 14| 15.0| + | A| XX|2003| 6| 8.0| 20| 23.0| + | A| YY|2000| 9| 3.0| 29| 26.0| + | A| YY|2001| 5| 4.0| 34| 30.0| + | A| YY|2002| 10| 2.0| 44| 32.0| + | A| YY|2003| 5| 7.0| 49| 39.0| + +----+----+----+----+----+--------+--------+ + + * */ + assertThat(((Dataset) engine.getContext().getAttribute("res")).getDataAsMap()) + .containsExactly( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 3L, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 7L, "Me_2", 10.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 14L, "Me_2", 15.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 20L, "Me_2", 23.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 29L, "Me_2", 26.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 34L, "Me_2", 30.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 44L, "Me_2", 32.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 49L, "Me_2", 39.0D)); + } + + @Test + public void testAnSumWithPartitionOrderByDPClause() throws ScriptException { + + // Analytical function count test case 4 : sum on window with partition, orderBy and data points + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", anCountDS1, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := sum ( ds1 over ( partition by Id_1 order by Id_2 data points between 2 preceding and 2 following) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * The result data frame + * + * +----+----+----+----+----+----------+----------+ + |Id_1|Id_2|Year|Me_1|Me_2|sum_Me_1|sum_Me_2| + +----+----+----+----+----+--------+--------+ + | A| XX|2000| 3| 1.0| 14| 15.0| + | A| XX|2001| 4| 9.0| 20| 23.0| + | A| XX|2002| 7| 5.0| 29| 26.0| + | A| XX|2003| 6| 8.0| 31| 29.0| + | A| YY|2000| 9| 3.0| 37| 22.0| + | A| YY|2001| 5| 4.0| 35| 24.0| + | A| YY|2002| 10| 2.0| 29| 16.0| + | A| YY|2003| 5| 7.0| 20| 13.0| + +----+----+----+----+----+--------+--------+ + + * */ + assertThat(((Dataset) engine.getContext().getAttribute("res")).getDataAsMap()) + .containsExactly( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 14L, "Me_2", 15.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 20L, "Me_2", 23.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 29L, "Me_2", 26.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 31L, "Me_2", 29.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 37L, "Me_2", 22.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 35L, "Me_2", 24.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 29L, "Me_2", 16.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 20L, "Me_2", 13.0D)); + } + + @Test + public void testAnSumWithPartitionOrderByRangeClause() throws ScriptException { + + // Analytical function count test case 5 : sum on window with partition, orderBy and range + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", anCountDS1, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := sum ( ds1 over ( partition by Id_1 order by Year range between 1 preceding and 1 following) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * The result data frame + * + * +----+----+----+----+----+----------+----------+ + |Id_1|Id_2|Year|Me_1|Me_2|sum_Me_1|sum_Me_2| + +----+----+----+----+----+--------+--------+ + | A| XX|2000| 3| 1.0| 21| 17.0| + | A| YY|2000| 9| 3.0| 21| 17.0| + | A| XX|2001| 4| 9.0| 38| 24.0| + | A| YY|2001| 5| 4.0| 38| 24.0| + | A| XX|2002| 7| 5.0| 37| 35.0| + | A| YY|2002| 10| 2.0| 37| 35.0| + | A| XX|2003| 6| 8.0| 28| 22.0| + | A| YY|2003| 5| 7.0| 28| 22.0| + +----+----+----+----+----+--------+--------+ + + * */ + assertThat(((Dataset) engine.getContext().getAttribute("res")).getDataAsMap()) + .containsExactly( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 21L, "Me_2", 17.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 21L, "Me_2", 17.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 38L, "Me_2", 24.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 38L, "Me_2", 24.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 37L, "Me_2", 35.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 37L, "Me_2", 35.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 28L, "Me_2", 22.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 28L, "Me_2", 22.0D)); + } +} diff --git a/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticTest.java b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticTest.java new file mode 100644 index 000000000..927079111 --- /dev/null +++ b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticTest.java @@ -0,0 +1,207 @@ +package fr.insee.vtl.spark.processing.engine.analytic; + +import fr.insee.vtl.engine.VtlScriptEngine; +import fr.insee.vtl.model.Dataset; +import fr.insee.vtl.model.Dataset.Role; +import fr.insee.vtl.model.InMemoryDataset; +import java.io.IOException; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import org.apache.spark.sql.SparkSession; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; + +public abstract class AnalyticTest { + + public final InMemoryDataset ds1 = + new InMemoryDataset( + List.of( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 3L, "Me_2", 1D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 4L, "Me_2", 9D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 7L, "Me_2", 5D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 6L, "Me_2", 8D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 9L, "Me_2", 3D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 5L, "Me_2", 4D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 10L, "Me_2", 2D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 5L, "Me_2", 7D)), + Map.of( + "Id_1", + String.class, + "Id_2", + String.class, + "Year", + Long.class, + "Me_1", + Long.class, + "Me_2", + Double.class), + Map.of( + "Id_1", + Role.IDENTIFIER, + "Id_2", + Role.IDENTIFIER, + "Year", + Role.IDENTIFIER, + "Me_1", + Role.MEASURE, + "Me_2", + Role.MEASURE)); + + public final InMemoryDataset ds2 = + new InMemoryDataset( + List.of( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1993L, "Me_1", 3L, "Me_2", 1D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1994L, "Me_1", 4L, "Me_2", 9D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1995L, "Me_1", 7L, "Me_2", 5D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 1996L, "Me_1", 6L, "Me_2", 8D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1993L, "Me_1", 9L, "Me_2", 3D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1994L, "Me_1", 5L, "Me_2", 4D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1995L, "Me_1", 10L, "Me_2", 2D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 1996L, "Me_1", 2L, "Me_2", 7D)), + Map.of( + "Id_1", + String.class, + "Id_2", + String.class, + "Year", + Long.class, + "Me_1", + Long.class, + "Me_2", + Double.class), + Map.of( + "Id_1", + Dataset.Role.IDENTIFIER, + "Id_2", + Dataset.Role.IDENTIFIER, + "Year", + Dataset.Role.IDENTIFIER, + "Me_1", + Dataset.Role.MEASURE, + "Me_2", + Dataset.Role.MEASURE)); + + public static final int DEFAULT_PRECISION = 2; + + public static Map replaceNullValues(Map map, T defaultValue) { + + // Replace the null value + map = + map.entrySet().stream() + .map( + entry -> { + if (entry.getValue() == null) entry.setValue(defaultValue); + return entry; + }) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + return map; + } + + /** + * This method round the double or float column of the given dataset with the given precision. + * This method should be used only for unit test, do not apply it on large dataset, it will have + * performance issues. + * + * @param dataset The given dataset which need to be transformed + * @param precision The given precision of the decimal value after , + * @return The result dataset in a list of map. + */ + public static List> roundDecimalInDataset(Dataset dataset, int precision) { + List> res = dataset.getDataAsMap(); + for (Map map : res) { + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() instanceof Double || entry.getValue() instanceof Float) { + double value = ((Number) entry.getValue()).doubleValue(); + BigDecimal roundedValue = + BigDecimal.valueOf(value).setScale(precision, RoundingMode.HALF_UP); + map.put(entry.getKey(), roundedValue.doubleValue()); + } + } + } + return res; + } + + public final String DEFAULT_NULL_STR = "null"; + + public static SparkSession spark; + public static ScriptEngine engine; + + @BeforeEach + public void setUp() { + + ScriptEngineManager mgr = new ScriptEngineManager(); + engine = mgr.getEngineByExtension("vtl"); + + spark = SparkSession.builder().appName("test").master("local").getOrCreate(); + SparkSession.setActiveSession(spark); + + engine.put(VtlScriptEngine.PROCESSING_ENGINE_NAMES, "spark4"); + } + + @AfterAll + public static void tearDown() throws IOException { + if (spark != null) spark.close(); + } + + /* + * End test with calc statement*/ + + /***********************************Unit test for implicit analytic function *************************************/ + + /* + * Test case for analytic function Avg + * + * */ + + /* + * End of Avg test case */ + + /* + * Test case for analytic function Median + * + * */ + + /* + * End of Median test case */ + + /* + * Test case for analytic function stddev_pop + * + * */ + + /* + * End of stddev_samp test case */ + + /* + * End of var_pop test case */ + + /* + * End of rank test case */ + + /* + * Test case for analytic function first + * + * */ + + /* + * End of first test case */ + + /* + * End of last test case */ + + /* + * End of lead test case */ + + /* + * End of lag test case */ + + /* + * End of ratio_to_report test case */ + +} diff --git a/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticVarPopTest.java b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticVarPopTest.java new file mode 100644 index 000000000..6df54c09d --- /dev/null +++ b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticVarPopTest.java @@ -0,0 +1,456 @@ +package fr.insee.vtl.spark.processing.engine.analytic; + +import static org.assertj.core.api.Assertions.assertThat; + +import fr.insee.vtl.engine.VtlScriptEngine; +import fr.insee.vtl.model.Dataset; +import fr.insee.vtl.model.InMemoryDataset; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import javax.script.ScriptContext; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import org.apache.spark.sql.SparkSession; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class AnalyticVarPopTest { + + private final InMemoryDataset anCountDS1 = + new InMemoryDataset( + List.of( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 3L, "Me_2", 1D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 4L, "Me_2", 9D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 7L, "Me_2", 5D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 6L, "Me_2", 8D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 9L, "Me_2", 3D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 5L, "Me_2", 4D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 10L, "Me_2", 2D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 5L, "Me_2", 7D)), + Map.of( + "Id_1", + String.class, + "Id_2", + String.class, + "Year", + Long.class, + "Me_1", + Long.class, + "Me_2", + Double.class), + Map.of( + "Id_1", + Dataset.Role.IDENTIFIER, + "Id_2", + Dataset.Role.IDENTIFIER, + "Year", + Dataset.Role.IDENTIFIER, + "Me_1", + Dataset.Role.MEASURE, + "Me_2", + Dataset.Role.MEASURE)); + + private static SparkSession spark; + private static ScriptEngine engine; + + @BeforeEach + public void setUp() { + + ScriptEngineManager mgr = new ScriptEngineManager(); + engine = mgr.getEngineByExtension("vtl"); + + spark = SparkSession.builder().appName("test").master("local").getOrCreate(); + SparkSession.setActiveSession(spark); + + engine.put(VtlScriptEngine.PROCESSING_ENGINE_NAMES, "spark4"); + } + + @AfterAll + public static void tearDown() throws IOException { + if (spark != null) spark.close(); + } + + @Test + public void testAnVarPopWithCalcClause() throws ScriptException { + + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", anCountDS1, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := ds1 [ calc var_pop_Me_1:= var_pop ( Me_1 over ( partition by Id_1,Id_2 order by Year) )];"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * + +----+----+----+----+----+------------------+ + |Id_1|Id_2|Year|Me_1|Me_2| var_pop_Me_1| + +----+----+----+----+----+------------------+ + | A| XX|2000| 3| 1.0| 0.0| + | A| XX|2001| 4| 9.0| 0.25| + | A| XX|2002| 7| 5.0| 2.888888888888889| + | A| XX|2003| 6| 8.0|2.4999999999999996| + | A| YY|2000| 9| 3.0| 0.0| + | A| YY|2001| 5| 4.0| 4.0| + | A| YY|2002| 10| 2.0| 4.666666666666667| + | A| YY|2003| 5| 7.0| 5.1875| + +----+----+----+----+----+------------------+ + * */ + List> actual = + ((Dataset) engine.getContext().getAttribute("res")).getDataAsMap(); + + assertThat(actual) + .containsExactly( + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 2000L, + "Me_1", + 3L, + "Me_2", + 1.0D, + "var_pop_Me_1", + 0.0D), + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 2001L, + "Me_1", + 4L, + "Me_2", + 9.0D, + "var_pop_Me_1", + 0.25D), + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 2002L, + "Me_1", + 7L, + "Me_2", + 5.0D, + "var_pop_Me_1", + 2.888888888888889D), + Map.of( + "Id_1", + "A", + "Id_2", + "XX", + "Year", + 2003L, + "Me_1", + 6L, + "Me_2", + 8.0D, + "var_pop_Me_1", + 2.4999999999999996D), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2000L, + "Me_1", + 9L, + "Me_2", + 3.0D, + "var_pop_Me_1", + 0.0D), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2001L, + "Me_1", + 5L, + "Me_2", + 4.0D, + "var_pop_Me_1", + 4.0D), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2002L, + "Me_1", + 10L, + "Me_2", + 2.0D, + "var_pop_Me_1", + 4.666666666666667D), + Map.of( + "Id_1", + "A", + "Id_2", + "YY", + "Year", + 2003L, + "Me_1", + 5L, + "Me_2", + 7.0D, + "var_pop_Me_1", + 5.1875D)); + } + + /* + * Test case for analytic function var_pop + * + * */ + @Test + public void testAnVarPopWithPartitionClause() throws ScriptException { + + // Analytical function Test case 1 : var_pop on window with partition + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", anCountDS1, ScriptContext.ENGINE_SCOPE); + + engine.eval("res := var_pop ( ds1 over ( partition by Id_1, Id_2 ) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * + + +----+----+----+----+----+------------------+------------+ + |Id_1|Id_2|Year|Me_1|Me_2| var_pop_Me_1|var_pop_Me_2| + +----+----+----+----+----+------------------+------------+ + | A| XX|2000| 3| 1.0|2.4999999999999996| 9.6875| + | A| XX|2001| 4| 9.0|2.4999999999999996| 9.6875| + | A| XX|2002| 7| 5.0|2.4999999999999996| 9.6875| + | A| XX|2003| 6| 8.0|2.4999999999999996| 9.6875| + | A| YY|2000| 9| 3.0| 5.1875| 3.5| + | A| YY|2001| 5| 4.0| 5.1875| 3.5| + | A| YY|2002| 10| 2.0| 5.1875| 3.5| + | A| YY|2003| 5| 7.0| 5.1875| 3.5| + +----+----+----+----+----+------------------+------------+ + * */ + List> res = + AnalyticTest.roundDecimalInDataset( + (Dataset) engine.getContext().getAttribute("res"), AnalyticTest.DEFAULT_PRECISION); + assertThat(res) + .containsExactly( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 2.50D, "Me_2", 9.69D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 2.50D, "Me_2", 9.69D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 2.50D, "Me_2", 9.69D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 2.50D, "Me_2", 9.69D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 5.19D, "Me_2", 3.5D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 5.19D, "Me_2", 3.5D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 5.19D, "Me_2", 3.5D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 5.19D, "Me_2", 3.5D)); + } + + @Test + public void testAnVarPopWithPartitionOrderByClause() throws ScriptException { + + // Analytical function Test case 2 : var_pop on window with partition and order by + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", anCountDS1, ScriptContext.ENGINE_SCOPE); + + engine.eval("res := var_pop ( ds1 over ( partition by Id_1, Id_2 order by Year) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + +----+----+----+----+----+------------------+------------------+ + |Id_1|Id_2|Year|Me_1|Me_2| var_pop_Me_1| var_pop_Me_2| + +----+----+----+----+----+------------------+------------------+ + | A| XX|2000| 3| 1.0| 0.0| 0.0| + | A| XX|2001| 4| 9.0| 0.25| 16.0| + | A| XX|2002| 7| 5.0| 2.888888888888889|10.666666666666666| + | A| XX|2003| 6| 8.0|2.4999999999999996| 9.6875| + | A| YY|2000| 9| 3.0| 0.0| 0.0| + | A| YY|2001| 5| 4.0| 4.0| 0.25| + | A| YY|2002| 10| 2.0| 4.666666666666667|0.6666666666666666| + | A| YY|2003| 5| 7.0| 5.1875| 3.5| + +----+----+----+----+----+------------------+------------------+ + + * */ + + List> res = + AnalyticTest.roundDecimalInDataset( + (Dataset) engine.getContext().getAttribute("res"), AnalyticTest.DEFAULT_PRECISION); + + assertThat(res) + .contains( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 0.0D, "Me_2", 0.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 0.25D, "Me_2", 16.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 2.89D, "Me_2", 10.67D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 2.50D, "Me_2", 9.69D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 0.0D, "Me_2", 0.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 4.0D, "Me_2", 0.25D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 4.67D, "Me_2", 0.67D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 5.19D, "Me_2", 3.5D)); + } + + @Test + public void testAnVarPopWithPartitionOrderByDPClause() throws ScriptException { + + // Analytical function count test case 3 : var_pop on window with partition, orderBy and data + // points + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", anCountDS1, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := var_pop ( ds1 over ( partition by Id_1 order by Id_2 data points between 2 preceding and 2 following) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * The result data frame + * + +----+----+----+----+----+------------------+------------------+ + |Id_1|Id_2|Year|Me_1|Me_2| var_pop_Me_1| var_pop_Me_2| + +----+----+----+----+----+------------------+------------------+ + | A| XX|2000| 3| 1.0| 2.888888888888889|10.666666666666666| + | A| XX|2001| 4| 9.0|2.4999999999999996| 9.6875| + | A| XX|2002| 7| 5.0| 4.56| 8.959999999999999| + | A| XX|2003| 6| 8.0| 2.96| 5.36| + | A| YY|2000| 9| 3.0| 3.44| 4.239999999999999| + | A| YY|2001| 5| 4.0| 4.4| 5.36| + | A| YY|2002| 10| 2.0| 5.1875| 3.5| + | A| YY|2003| 5| 7.0|5.5555555555555545| 4.222222222222222| + +----+----+----+----+----+------------------+------------------+ + + + * */ + List> res = + AnalyticTest.roundDecimalInDataset( + (Dataset) engine.getContext().getAttribute("res"), AnalyticTest.DEFAULT_PRECISION); + assertThat(res) + .containsExactly( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 2.89D, "Me_2", 10.67D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 2.5D, "Me_2", 9.69D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 4.56D, "Me_2", 8.96D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 2.96D, "Me_2", 5.36D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 3.44D, "Me_2", 4.24D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 4.4D, "Me_2", 5.36D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 5.19D, "Me_2", 3.5D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 5.56D, "Me_2", 4.22D)); + } + + @Test + public void testAnVarPopWithPartitionOrderByRangeClause() throws ScriptException { + + // Analytical function count test case 4 : var_pop on window with partition, orderBy and range + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", anCountDS1, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := var_pop ( ds1 over ( partition by Id_1 order by Year range between 1 preceding and 1 following) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * The result data frame + * + +----+----+----+----+----+------------------+-----------------+ + |Id_1|Id_2|Year|Me_1|Me_2| var_pop_Me_1| var_pop_Me_2| + +----+----+----+----+----+------------------+-----------------+ + | A| XX|2000| 3| 1.0| 5.1875| 8.6875| + | A| YY|2000| 9| 3.0| 5.1875| 8.6875| + | A| XX|2001| 4| 9.0| 6.555555555555556|6.666666666666668| + | A| YY|2001| 5| 4.0| 6.555555555555556|6.666666666666668| + | A| XX|2002| 7| 5.0|3.8055555555555554|5.805555555555556| + | A| YY|2002| 10| 2.0|3.8055555555555554|5.805555555555556| + | A| XX|2003| 6| 8.0| 3.5| 5.25| + | A| YY|2003| 5| 7.0| 3.5| 5.25| + +----+----+----+----+----+------------------+-----------------+ + + * */ + List> res = + AnalyticTest.roundDecimalInDataset( + (Dataset) engine.getContext().getAttribute("res"), AnalyticTest.DEFAULT_PRECISION); + assertThat(res) + .containsExactlyInAnyOrder( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 5.19D, "Me_2", 8.69D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 5.19D, "Me_2", 8.69D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 6.56D, "Me_2", 6.67D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 6.56D, "Me_2", 6.67D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 3.81D, "Me_2", 5.81D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 3.81D, "Me_2", 5.81D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 3.5D, "Me_2", 5.25D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 3.5D, "Me_2", 5.25D)); + } +} diff --git a/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticVarSampTest.java b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticVarSampTest.java new file mode 100644 index 000000000..1d2d4ae02 --- /dev/null +++ b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/processing.engine/analytic/AnalyticVarSampTest.java @@ -0,0 +1,244 @@ +package fr.insee.vtl.spark.processing.engine.analytic; + +import static org.assertj.core.api.Assertions.assertThat; + +import fr.insee.vtl.model.Dataset; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import javax.script.ScriptContext; +import javax.script.ScriptException; +import org.junit.jupiter.api.Test; + +public class AnalyticVarSampTest extends AnalyticTest { + + @Test + public void testAnVarSampWithPartitionClause() throws ScriptException { + + // Analytical function Test case 1 : var_samp on window with partition + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", ds1, ScriptContext.ENGINE_SCOPE); + + engine.eval("res := var_samp ( ds1 over ( partition by Id_1, Id_2 ) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * + + +----+----+----+----+----+------------------+------------------+ + |Id_1|Id_2|Year|Me_1|Me_2| var_samp_Me_1| var_samp_Me_2| + +----+----+----+----+----+------------------+------------------+ + | A| XX|2000| 3| 1.0|3.3333333333333326|12.916666666666666| + | A| XX|2001| 4| 9.0|3.3333333333333326|12.916666666666666| + | A| XX|2002| 7| 5.0|3.3333333333333326|12.916666666666666| + | A| XX|2003| 6| 8.0|3.3333333333333326|12.916666666666666| + | A| YY|2000| 9| 3.0| 6.916666666666667| 4.666666666666667| + | A| YY|2001| 5| 4.0| 6.916666666666667| 4.666666666666667| + | A| YY|2002| 10| 2.0| 6.916666666666667| 4.666666666666667| + | A| YY|2003| 5| 7.0| 6.916666666666667| 4.666666666666667| + +----+----+----+----+----+------------------+------------------+ + * */ + List> res = + AnalyticTest.roundDecimalInDataset( + (Dataset) engine.getContext().getAttribute("res"), AnalyticTest.DEFAULT_PRECISION); + assertThat(res) + .contains( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 3.33D, "Me_2", 12.92D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 3.33D, "Me_2", 12.92D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 3.33D, "Me_2", 12.92D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 3.33D, "Me_2", 12.92D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 6.92D, "Me_2", 4.67D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 6.92D, "Me_2", 4.67D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 6.92D, "Me_2", 4.67D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 6.92D, "Me_2", 4.67D)); + } + + @Test + public void testAnVarSampWithPartitionOrderByClause() throws ScriptException { + + // Analytical function Test case 2 : var_samp on window with partition and order by + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", ds1, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := var_samp ( ds1 over ( partition by Id_1, Id_2 order by Year) );" + + "res1 := res [calc Me_1 := round(Me_1, 2), Me_2 := round(Me_2, 2)];"); + assertThat(engine.getContext().getAttribute("res1")).isInstanceOf(Dataset.class); + + /* + +----+----+----+----+----+------------------+------------------+ + |Id_1|Id_2|Year|Me_1|Me_2| var_samp_Me_1| var_samp_Me_2| + +----+----+----+----+----+------------------+------------------+ + | A| XX|2000| 3| 1.0| null| null| + | A| XX|2001| 4| 9.0| 0.5| 32.0| + | A| XX|2002| 7| 5.0| 4.333333333333333| 16.0| + | A| XX|2003| 6| 8.0|3.3333333333333326|12.916666666666666| + | A| YY|2000| 9| 3.0| null| null| + | A| YY|2001| 5| 4.0| 8.0| 0.5| + | A| YY|2002| 10| 2.0| 7.0| 1.0| + | A| YY|2003| 5| 7.0| 6.916666666666667| 4.666666666666667| + +----+----+----+----+----+------------------+------------------+ + * */ + + var actual = + ((Dataset) engine.getContext().getAttribute("res1")) + .getDataAsMap().stream() + .map(map -> replaceNullValues(map, DEFAULT_NULL_STR)) + .collect(Collectors.toList()); + assertThat(actual) + .containsExactly( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", "null", "Me_2", "null"), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 0.5D, "Me_2", 32.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 4.33D, "Me_2", 16.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 3.33D, "Me_2", 12.92D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", "null", "Me_2", "null"), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 8.0D, "Me_2", 0.5D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 7.0D, "Me_2", 1.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 6.92D, "Me_2", 4.67D)); + } + + @Test + public void testAnVarSampWithPartitionOrderByDPClause() throws ScriptException { + + // Analytical function count test case 3 : var_samp on window with partition, orderBy and data + // points + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", ds1, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := var_samp ( ds1 over ( partition by Id_1 order by Id_2 data points between 2 preceding and 2 following) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * The result data frame + * + +----+----+----+----+----+------------------+------------------+ + |Id_1|Id_2|Year|Me_1|Me_2| var_samp_Me_1| var_samp_Me_2| + +----+----+----+----+----+------------------+------------------+ + | A| XX|2000| 3| 1.0| 4.333333333333333| 16.0| + | A| XX|2001| 4| 9.0|3.3333333333333326|12.916666666666666| + | A| XX|2002| 7| 5.0| 5.699999999999999| 11.2| + | A| XX|2003| 6| 8.0| 3.7| 6.7| + | A| YY|2000| 9| 3.0| 4.3| 5.299999999999999| + | A| YY|2001| 5| 4.0| 5.5| 6.7| + | A| YY|2002| 10| 2.0| 6.916666666666667| 4.666666666666667| + | A| YY|2003| 5| 7.0| 8.333333333333332| 6.333333333333334| + +----+----+----+----+----+------------------+------------------+ + + + + * */ + List> res = + AnalyticTest.roundDecimalInDataset( + (Dataset) engine.getContext().getAttribute("res"), AnalyticTest.DEFAULT_PRECISION); + assertThat(res) + .containsExactly( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 4.33D, "Me_2", 16.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 3.33D, "Me_2", 12.92D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 5.70D, "Me_2", 11.2D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 3.7D, "Me_2", 6.7D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 4.3D, "Me_2", 5.3D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 5.5D, "Me_2", 6.7D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 6.92D, "Me_2", 4.67D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 8.33D, "Me_2", 6.33D)); + } + + @Test + public void testAnVarSampWithPartitionOrderByRangeClause() throws ScriptException { + + // Analytical function count test case 4 : var_samp on window with partition, orderBy and range + /* Input dataset + * +----+----+----+----+----+ + |Id_1|Id_2|Year|Me_1|Me_2| + +----+----+----+----+----+ + | A| XX|2000| 3| 1.0| + | A| XX|2001| 4| 9.0| + | A| XX|2002| 7| 5.0| + | A| XX|2003| 6| 8.0| + | A| YY|2000| 9| 3.0| + | A| YY|2001| 5| 4.0| + | A| YY|2002| 10| 2.0| + | A| YY|2003| 5| 7.0| + +----+----+----+----+----+ + * */ + ScriptContext context = engine.getContext(); + context.setAttribute("ds1", ds1, ScriptContext.ENGINE_SCOPE); + + engine.eval( + "res := var_samp ( ds1 over ( partition by Id_1 order by Year range between 1 preceding and 1 following) );"); + assertThat(engine.getContext().getAttribute("res")).isInstanceOf(Dataset.class); + + /* + * The result data frame + * + +----+----+----+----+----+-----------------+------------------+ + |Id_1|Id_2|Year|Me_1|Me_2| var_samp_Me_1| var_samp_Me_2| + +----+----+----+----+----+-----------------+------------------+ + | A| XX|2000| 3| 1.0|6.916666666666667|11.583333333333334| + | A| YY|2000| 9| 3.0|6.916666666666667|11.583333333333334| + | A| XX|2001| 4| 9.0|7.866666666666667| 8.000000000000002| + | A| YY|2001| 5| 4.0|7.866666666666667| 8.000000000000002| + | A| XX|2002| 7| 5.0|4.566666666666666| 6.966666666666667| + | A| YY|2002| 10| 2.0|4.566666666666666| 6.966666666666667| + | A| XX|2003| 6| 8.0|4.666666666666667| 7.0| + | A| YY|2003| 5| 7.0|4.666666666666667| 7.0| + +----+----+----+----+----+-----------------+------------------+ + + * */ + List> res = + AnalyticTest.roundDecimalInDataset( + (Dataset) engine.getContext().getAttribute("res"), AnalyticTest.DEFAULT_PRECISION); + assertThat(res) + .containsExactlyInAnyOrder( + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2000L, "Me_1", 6.92D, "Me_2", 11.58D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2000L, "Me_1", 6.92D, "Me_2", 11.58D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2001L, "Me_1", 7.87D, "Me_2", 8.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2001L, "Me_1", 7.87D, "Me_2", 8.0D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2002L, "Me_1", 4.57D, "Me_2", 6.97D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2002L, "Me_1", 4.57D, "Me_2", 6.97D), + Map.of("Id_1", "A", "Id_2", "XX", "Year", 2003L, "Me_1", 4.67D, "Me_2", 7.0D), + Map.of("Id_1", "A", "Id_2", "YY", "Year", 2003L, "Me_1", 4.67D, "Me_2", 7.0D)); + } +} diff --git a/vtl-spark4/src/test/java/fr/insee/vtl/spark4/samples/DatasetSamples.java b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/samples/DatasetSamples.java new file mode 100644 index 000000000..9a88a8ab6 --- /dev/null +++ b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/samples/DatasetSamples.java @@ -0,0 +1,39 @@ +package fr.insee.vtl.spark.samples; + +import fr.insee.vtl.model.Dataset; +import fr.insee.vtl.model.InMemoryDataset; +import fr.insee.vtl.model.Structured; +import java.util.Arrays; +import java.util.List; + +public class DatasetSamples { + + public static InMemoryDataset ds1 = + new InMemoryDataset( + List.of( + new Structured.Component("id", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("long1", Long.class, Dataset.Role.MEASURE), + new Structured.Component("long2", Long.class, Dataset.Role.MEASURE), + new Structured.Component("double1", Double.class, Dataset.Role.MEASURE), + new Structured.Component("double2", Double.class, Dataset.Role.MEASURE), + new Structured.Component("bool1", Boolean.class, Dataset.Role.MEASURE), + new Structured.Component("bool2", Boolean.class, Dataset.Role.MEASURE), + new Structured.Component("string1", String.class, Dataset.Role.MEASURE), + new Structured.Component("string2", String.class, Dataset.Role.MEASURE)), + Arrays.asList("Toto", 30L, 300L, 12.2D, 1.22D, true, false, "toto", "t"), + Arrays.asList("Hadrien", 10L, 1L, 1.1D, 10.11D, true, true, "hadrien", "k"), + Arrays.asList("Nico", 20L, 250L, 12.2D, 21.1D, false, true, "nico", "l"), + Arrays.asList("Franck", 100L, 2L, 1.21D, 100.9D, false, false, "franck", "c")); + + public static InMemoryDataset ds2 = + new InMemoryDataset( + List.of( + new Structured.Component("id", String.class, Dataset.Role.IDENTIFIER), + new Structured.Component("long1", Long.class, Dataset.Role.MEASURE), + new Structured.Component("double1", Double.class, Dataset.Role.MEASURE), + new Structured.Component("bool1", Boolean.class, Dataset.Role.MEASURE), + new Structured.Component("string1", String.class, Dataset.Role.MEASURE)), + Arrays.asList("Hadrien", 150L, 1.1D, true, "hadrien"), + Arrays.asList("Nico", 20L, 2.2D, true, "nico"), + Arrays.asList("Franck", 100L, -1.21D, false, "franck")); +} diff --git a/vtl-spark4/src/test/java/fr/insee/vtl/spark4/samples/RegisterMethodTest.java b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/samples/RegisterMethodTest.java new file mode 100644 index 000000000..7654f9ebc --- /dev/null +++ b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/samples/RegisterMethodTest.java @@ -0,0 +1,99 @@ +package fr.insee.vtl.spark.samples; + +import fr.insee.vtl.engine.VtlScriptEngine; +import fr.insee.vtl.model.Structured; +import fr.insee.vtl.spark.SparkDataset; +import java.io.IOException; +import java.util.Collection; +import java.util.Map; +import java.util.stream.Collectors; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Row; +import org.apache.spark.sql.SparkSession; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class RegisterMethodTest { + + private static SparkSession spark; + private VtlScriptEngine engine; + + @BeforeEach + public void setUp() { + spark = SparkSession.builder().appName("test").master("local").getOrCreate(); + + SparkSession.setActiveSession(spark); + + ScriptEngineManager mgr = new ScriptEngineManager(); + engine = (VtlScriptEngine) mgr.getEngineByExtension("vtl"); + engine.put(VtlScriptEngine.PROCESSING_ENGINE_NAMES, "spark4"); + } + + @AfterEach + public void tearDown() throws IOException { + if (spark != null) spark.close(); + } + + @Test + public void registerTest() throws ScriptException, NoSuchMethodException { + engine.registerGlobalMethod("loadCSV", HandleDs.class.getMethod("loadCSV", String.class)); + engine.registerGlobalMethod( + "writeCSV", + HandleDs.class.getMethod("writeCSV", String.class, fr.insee.vtl.model.Dataset.class)); + engine.registerGlobalMethod( + "getSize", HandleDs.class.getMethod("getSize", fr.insee.vtl.model.Dataset.class)); + engine.eval( + "a := loadCSV(\"src/main/resources/ds1.csv\"); " + + "b := a[calc toto := \"test\"]; " + + "c := writeCSV(\"src/main/resources/ds1_out.csv\", b);" + + "d := getSize(b);"); + fr.insee.vtl.model.Dataset a = + (fr.insee.vtl.model.Dataset) engine.getContext().getAttribute("a"); + Structured.DataStructure dataStructure = a.getDataStructure(); + String d = (String) engine.getContext().getAttribute("d"); + } + + public static class HandleDs { + public static SparkDataset loadCSV(String path) throws Exception { + Dataset ds; + try { + ds = spark.read().option("sep", ";").option("header", "true").csv(path); + } catch (Exception e) { + throw new Exception(e); + } + return new SparkDataset(ds); + } + + public static void writeCSV(String location, fr.insee.vtl.model.Dataset dataset) { + org.apache.spark.sql.Dataset sparkDataset = asSparkDataset(dataset).getSparkDataset(); + // Commented to avoid git issues, but function is found at high level + // sparkDataset.write().mode(SaveMode.Overwrite).csv(location); + } + + public static String getSize(fr.insee.vtl.model.Dataset dataset) { + return "Dataset size: " + dataset.getDataPoints().size(); + } + + private static SparkDataset asSparkDataset(fr.insee.vtl.model.Dataset dataset) { + if (dataset instanceof SparkDataset sparkDataset) { + return sparkDataset; + } else { + return new SparkDataset(dataset, getRoleMap(dataset), spark); + } + } + + private static Map getRoleMap( + Collection components) { + return components.stream() + .collect(Collectors.toMap(Structured.Component::getName, Structured.Component::getRole)); + } + + private static Map getRoleMap( + fr.insee.vtl.model.Dataset dataset) { + return getRoleMap(dataset.getDataStructure().values()); + } + } +} From 02b23b7394905a17230430778175694aa0c72583 Mon Sep 17 00:00:00 2001 From: Sebastian Heupts Date: Mon, 23 Mar 2026 13:16:20 +0100 Subject: [PATCH 03/16] Try adding shaded classifier --- vtl-engine/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/vtl-engine/pom.xml b/vtl-engine/pom.xml index bffc6474b..506569b30 100644 --- a/vtl-engine/pom.xml +++ b/vtl-engine/pom.xml @@ -27,6 +27,7 @@ vtl-parser 2.3.0-SNAPSHOT compile + shaded fr.insee.trevas From 390acf80e5aa1293b7227024d60fd50f1d24123a Mon Sep 17 00:00:00 2001 From: Sebastian Heupts Date: Mon, 23 Mar 2026 13:23:36 +0100 Subject: [PATCH 04/16] Package vtl-parser frist, then test the rest in pipeline --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c018e5021..68a1c769a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,6 +40,8 @@ jobs: with: java-version: 17 distribution: "adopt" + - name: Package vtl-parser + run: mvn clean package -pl vtl-parser # Also exclude vtl-prov because of vtl-sdmx scope test dependency - name: Test run: mvn test -pl '!vtl-sdmx,!vtl-prov' @@ -70,6 +72,8 @@ jobs: "username": "${{ secrets.GH_PACKAGES_USERNAME }}", "password": "${{ secrets.GH_PACKAGES_PASSWORD }}" }] + - name: Package vtl-parser + run: mvn clean package -pl vtl-parser - name: Test run: mvn test package: From 1956c85b9b40b3a5ef7fd99863a70e44553bb572 Mon Sep 17 00:00:00 2001 From: Sebastian Heupts Date: Mon, 23 Mar 2026 14:11:51 +0100 Subject: [PATCH 05/16] Install vtl-parser frist, then test the rest in pipeline --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 68a1c769a..75f41be4f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,7 +41,7 @@ jobs: java-version: 17 distribution: "adopt" - name: Package vtl-parser - run: mvn clean package -pl vtl-parser + run: mvn clean install -pl vtl-parser # Also exclude vtl-prov because of vtl-sdmx scope test dependency - name: Test run: mvn test -pl '!vtl-sdmx,!vtl-prov' @@ -73,7 +73,7 @@ jobs: "password": "${{ secrets.GH_PACKAGES_PASSWORD }}" }] - name: Package vtl-parser - run: mvn clean package -pl vtl-parser + run: mvn clean install -pl vtl-parser - name: Test run: mvn test package: From 82fbae7f2b567108714fa78fe91b549080c3dcfe Mon Sep 17 00:00:00 2001 From: Sebastian Heupts Date: Mon, 23 Mar 2026 14:31:25 +0100 Subject: [PATCH 06/16] In one step, install vtl-parser first, then test the rest in pipeline --- .github/workflows/ci.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 75f41be4f..9f5c5a5c1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,11 +40,9 @@ jobs: with: java-version: 17 distribution: "adopt" - - name: Package vtl-parser - run: mvn clean install -pl vtl-parser # Also exclude vtl-prov because of vtl-sdmx scope test dependency - name: Test - run: mvn test -pl '!vtl-sdmx,!vtl-prov' + run: mvn install -pl vtl-parser && mvn test -pl '!vtl-sdmx,!vtl-prov' test: name: Run Trevas tests if: (github.repository != 'InseeFr/Trevas' && @@ -72,10 +70,8 @@ jobs: "username": "${{ secrets.GH_PACKAGES_USERNAME }}", "password": "${{ secrets.GH_PACKAGES_PASSWORD }}" }] - - name: Package vtl-parser - run: mvn clean install -pl vtl-parser - name: Test - run: mvn test + run: mvn install -pl vtl-parser && mvn test package: name: Package Trevas modules # Filter thanks to test job From cb730307422e76e4b81f84073723b477d7e97c56 Mon Sep 17 00:00:00 2001 From: Nicolas Laval Date: Wed, 6 May 2026 18:36:03 +0200 Subject: [PATCH 07/16] Continue with shading for ANTLR4 --- .github/workflows/ci.yml | 52 +- pom.xml | 15 + vtl-engine/pom.xml | 12 +- .../fr/insee/vtl/engine/VtlScriptEngine.java | 8 +- .../engine/visitors/AssignmentVisitor.java | 702 +++++++++--------- vtl-engine/src/main/java/module-info.java | 7 +- vtl-parser/pom.xml | 56 +- .../fr/insee/vtl/parser/package-info.java | 2 - vtl-parser/src/main/java/module-info.java | 10 + vtl-prov/pom.xml | 20 + .../fr/insee/vtl/prov/ProvenanceListener.java | 6 +- .../main/java/fr/insee/vtl/prov/Variable.java | 6 +- .../insee/vtl/prov/VariableGraphListener.java | 2 +- .../fr/insee/vtl/prov/utils/AntlrUtils.java | 4 +- .../vtl/prov/VariableGraphListenerTest.java | 8 +- vtl-spark/pom.xml | 3 +- vtl-spark4/pom.xml | 11 +- 17 files changed, 528 insertions(+), 396 deletions(-) delete mode 100644 vtl-parser/src/main/java/fr/insee/vtl/parser/package-info.java create mode 100644 vtl-parser/src/main/java/module-info.java diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9f5c5a5c1..65cbf38c3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,11 +72,59 @@ jobs: }] - name: Test run: mvn install -pl vtl-parser && mvn test + + spark-integration: + name: Spark ${{ matrix.spark-label }} + runs-on: ubuntu-latest + needs: format + strategy: + fail-fast: false + matrix: + include: + - module: vtl-spark + spark-label: "3.x (Scala 2.12)" + extra-args: "" + - module: vtl-spark4 + spark-label: "4.x (Scala 2.13)" + extra-args: "" + - module: vtl-prov + spark-label: "vtl-prov + Spark 4 (tests)" + extra-args: "-Pspark4-tests" + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: 17 + distribution: temurin + - name: Cache Maven dependencies + uses: actions/cache@v4 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - uses: s4u/maven-settings-action@v3.0.0 + with: + githubServer: false + servers: | + [{ + "id": "Github", + "username": "${{ secrets.GH_PACKAGES_USERNAME }}", + "password": "${{ secrets.GH_PACKAGES_PASSWORD }}" + }] + # Install shaded vtl-parser first, then exclude it from the reactor so dependent + # modules resolve the installed jar (JPMS fr.insee.vtl.parser.shaded), not target/classes. + - name: Test ${{ matrix.module }} (and upstream modules) + run: mvn install -pl vtl-parser -DskipTests && mvn -B test -pl ${{ matrix.module }} -am --projects '!vtl-parser' ${{ matrix.extra-args }} + package: name: Package Trevas modules # Filter thanks to test job runs-on: ubuntu-latest - needs: test + needs: [ test, spark-integration ] steps: - uses: actions/checkout@v4 with: @@ -134,7 +182,7 @@ jobs: name: Publish Trevas modules on Maven if: startsWith(github.event.ref, 'refs/tags/v') # Temp update because of sonar token issue - needs: test + needs: [ test, spark-integration ] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/pom.xml b/pom.xml index 6c05c6366..0b2fa10ef 100644 --- a/pom.xml +++ b/pom.xml @@ -76,6 +76,11 @@ ${project.basedir}/coverage/target/site/jacoco-aggregate/jacoco.xml + + 3.5.8 + 2.12 + 4.0.0 + 2.13 @@ -106,6 +111,16 @@ pom import + + org.apache.spark + spark-sql_2.12 + ${spark3.version} + + + org.apache.spark + spark-sql_2.13 + ${spark4.version} + diff --git a/vtl-engine/pom.xml b/vtl-engine/pom.xml index a9a7a5624..504d77673 100644 --- a/vtl-engine/pom.xml +++ b/vtl-engine/pom.xml @@ -27,7 +27,6 @@ vtl-parser 2.4.0-SNAPSHOT compile - shaded fr.insee.trevas @@ -77,6 +76,17 @@ + + org.apache.maven.plugins + maven-surefire-plugin + + + false + + diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/VtlScriptEngine.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/VtlScriptEngine.java index f3ea9c1bf..034b92f7b 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/VtlScriptEngine.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/VtlScriptEngine.java @@ -2,10 +2,6 @@ import static fr.insee.vtl.engine.VtlNativeMethods.NATIVE_METHODS; -import fr.insee.trevas.antlr.shaded.v4.runtime.*; -import fr.insee.trevas.antlr.shaded.v4.runtime.misc.Interval; -import fr.insee.trevas.antlr.shaded.v4.runtime.tree.ParseTree; -import fr.insee.trevas.antlr.shaded.v4.runtime.tree.TerminalNode; import fr.insee.vtl.engine.exceptions.VtlRuntimeException; import fr.insee.vtl.engine.exceptions.VtlSyntaxException; import fr.insee.vtl.engine.visitors.AssignmentVisitor; @@ -13,6 +9,10 @@ import fr.insee.vtl.model.exceptions.VtlScriptException; import fr.insee.vtl.parser.VtlLexer; import fr.insee.vtl.parser.VtlParser; +import fr.insee.vtl.parser.antlr4.runtime.*; +import fr.insee.vtl.parser.antlr4.runtime.misc.Interval; +import fr.insee.vtl.parser.antlr4.runtime.tree.ParseTree; +import fr.insee.vtl.parser.antlr4.runtime.tree.TerminalNode; import java.io.IOException; import java.io.Reader; import java.lang.reflect.Method; diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/AssignmentVisitor.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/AssignmentVisitor.java index ac2be2c4c..479ccf3e6 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/AssignmentVisitor.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/AssignmentVisitor.java @@ -1,8 +1,5 @@ package fr.insee.vtl.engine.visitors; -import static fr.insee.vtl.engine.VtlScriptEngine.fromContext; - -import fr.insee.trevas.antlr.shaded.v4.runtime.tree.TerminalNode; import fr.insee.vtl.engine.VtlScriptEngine; import fr.insee.vtl.engine.exceptions.InvalidArgumentException; import fr.insee.vtl.engine.exceptions.VtlRuntimeException; @@ -12,382 +9,387 @@ import fr.insee.vtl.model.exceptions.VtlScriptException; import fr.insee.vtl.parser.VtlBaseVisitor; import fr.insee.vtl.parser.VtlParser; -import java.util.*; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Collectors; + import javax.script.Bindings; import javax.script.ScriptContext; import javax.script.ScriptException; import javax.script.SimpleBindings; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; -/** AssignmentVisitor is the visitor for VTL assignment expressions. */ -public class AssignmentVisitor extends VtlBaseVisitor { - - private final VtlScriptEngine engine; - private final ProcessingEngine processingEngine; - private final ExpressionVisitor expressionVisitor; - - /** - * Constructor taking a scripting engine and a processing engine. - * - * @param engine The scripting engine. - * @param processingEngine The processing engine. - */ - public AssignmentVisitor(VtlScriptEngine engine, ProcessingEngine processingEngine) { - this.engine = Objects.requireNonNull(engine); - this.processingEngine = Objects.requireNonNull(processingEngine); - expressionVisitor = - new ExpressionVisitor( - engine.getBindings(ScriptContext.ENGINE_SCOPE), processingEngine, engine); - } +import static fr.insee.vtl.engine.VtlScriptEngine.fromContext; - private Object visitAssignment(VtlParser.ExprContext expr) { - ResolvableExpression resolvableExpression = expressionVisitor.visit(expr); - return resolvableExpression.resolve(engine.getBindings(ScriptContext.ENGINE_SCOPE)); - } +/** + * AssignmentVisitor is the visitor for VTL assignment expressions. + */ +public class AssignmentVisitor extends VtlBaseVisitor { - @Override - public Object visitTemporaryAssignment(VtlParser.TemporaryAssignmentContext ctx) { - var result = visitAssignment(ctx.expr()); - Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); - String variableIdentifier = ctx.varID().getText(); - bindings.put(variableIdentifier, result); - return result; - } + private final VtlScriptEngine engine; + private final ProcessingEngine processingEngine; + private final ExpressionVisitor expressionVisitor; - @Override - public Object visitPersistAssignment(VtlParser.PersistAssignmentContext ctx) { - var result = visitAssignment(ctx.expr()); - if (result instanceof Dataset resultDataset) { - result = new PersistentDataset(resultDataset); - Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); - String variableIdentifier = ctx.varID().getText(); - bindings.put(variableIdentifier, result); - return result; + /** + * Constructor taking a scripting engine and a processing engine. + * + * @param engine The scripting engine. + * @param processingEngine The processing engine. + */ + public AssignmentVisitor(VtlScriptEngine engine, ProcessingEngine processingEngine) { + this.engine = Objects.requireNonNull(engine); + this.processingEngine = Objects.requireNonNull(processingEngine); + expressionVisitor = + new ExpressionVisitor( + engine.getBindings(ScriptContext.ENGINE_SCOPE), processingEngine, engine); } - throw new VtlRuntimeException( - new InvalidTypeException(Dataset.class, result.getClass(), fromContext(ctx))); - } - @Override - public Object visitDefDatapointRuleset(VtlParser.DefDatapointRulesetContext ctx) { - var pos = fromContext(ctx); - String rulesetName = ctx.rulesetID().getText(); - List signature = ctx.rulesetSignature().signature(); - List variables = - ctx.rulesetSignature().VARIABLE() != null - ? signature.stream().map(s -> s.varID().getText()).collect(Collectors.toList()) - : List.of(); - List valuedomains = - ctx.rulesetSignature().VALUE_DOMAIN() != null - ? signature.stream().map(s -> s.varID().getText()).collect(Collectors.toList()) - : List.of(); - Map alias = - signature.stream() - .filter(s -> null != s.alias()) - .collect(Collectors.toMap(k -> k.varID().getText(), v -> v.alias().getText())); + private Object visitAssignment(VtlParser.ExprContext expr) { + ResolvableExpression resolvableExpression = expressionVisitor.visit(expr); + return resolvableExpression.resolve(engine.getBindings(ScriptContext.ENGINE_SCOPE)); + } - Set erCodeTypes = - ctx.ruleClauseDatapoint().ruleItemDatapoint().stream() - .map( - c -> { - VtlParser.ErCodeContext erCodeContext = c.erCode(); - if (null == erCodeContext) return Object.class; - return expressionVisitor.visit(c.erCode()).getType(); - }) - .collect(Collectors.toSet()); - List filteredErCodeTypes = - erCodeTypes.stream().filter(t -> !t.equals(Object.class)).collect(Collectors.toList()); - if (filteredErCodeTypes.size() > 1) { - throw new VtlRuntimeException( - new InvalidArgumentException("Error codes of rules have different types", pos)); + @Override + public Object visitTemporaryAssignment(VtlParser.TemporaryAssignmentContext ctx) { + var result = visitAssignment(ctx.expr()); + Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); + String variableIdentifier = ctx.varID().getText(); + bindings.put(variableIdentifier, result); + return result; } - Class erCodeType = - filteredErCodeTypes.isEmpty() ? String.class : filteredErCodeTypes.iterator().next(); - Set erLevelTypes = - ctx.ruleClauseDatapoint().ruleItemDatapoint().stream() - .map( - c -> { - VtlParser.ErLevelContext erLevelContext = c.erLevel(); - if (null == erLevelContext) return Object.class; - return expressionVisitor.visit(c.erLevel()).getType(); - }) - .collect(Collectors.toSet()); - List filteredErLevelTypes = - erLevelTypes.stream().filter(t -> !t.equals(Object.class)).collect(Collectors.toList()); - if (filteredErLevelTypes.size() > 1) { - throw new VtlRuntimeException( - new InvalidArgumentException("Error levels of rules have different types", pos)); + @Override + public Object visitPersistAssignment(VtlParser.PersistAssignmentContext ctx) { + var result = visitAssignment(ctx.expr()); + if (result instanceof Dataset resultDataset) { + result = new PersistentDataset(resultDataset); + Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); + String variableIdentifier = ctx.varID().getText(); + bindings.put(variableIdentifier, result); + return result; + } + throw new VtlRuntimeException( + new InvalidTypeException(Dataset.class, result.getClass(), fromContext(ctx))); } - Class erLevelType = - filteredErLevelTypes.isEmpty() ? Long.class : filteredErLevelTypes.iterator().next(); - AtomicInteger index = new AtomicInteger(); - List rules = - ctx.ruleClauseDatapoint().ruleItemDatapoint().stream() - .map( - c -> { - TerminalNode identifier = c.IDENTIFIER(); - int i = index.getAndIncrement() + 1; - String name = null != identifier ? identifier.getText() : rulesetName + "_" + i; + @Override + public Object visitDefDatapointRuleset(VtlParser.DefDatapointRulesetContext ctx) { + var pos = fromContext(ctx); + String rulesetName = ctx.rulesetID().getText(); + List signature = ctx.rulesetSignature().signature(); + List variables = + ctx.rulesetSignature().VARIABLE() != null + ? signature.stream().map(s -> s.varID().getText()).collect(Collectors.toList()) + : List.of(); + List valuedomains = + ctx.rulesetSignature().VALUE_DOMAIN() != null + ? signature.stream().map(s -> s.varID().getText()).collect(Collectors.toList()) + : List.of(); + Map alias = + signature.stream() + .filter(s -> null != s.alias()) + .collect(Collectors.toMap(k -> k.varID().getText(), v -> v.alias().getText())); - VtlParser.ExprContext antecedentContiditonContext = c.antecedentContiditon; - VtlParser.ExprContext consequentConditionContext = c.consequentCondition; + Set erCodeTypes = + ctx.ruleClauseDatapoint().ruleItemDatapoint().stream() + .map( + c -> { + VtlParser.ErCodeContext erCodeContext = c.erCode(); + if (null == erCodeContext) return Object.class; + return expressionVisitor.visit(c.erCode()).getType(); + }) + .collect(Collectors.toSet()); + List filteredErCodeTypes = + erCodeTypes.stream().filter(t -> !t.equals(Object.class)).collect(Collectors.toList()); + if (filteredErCodeTypes.size() > 1) { + throw new VtlRuntimeException( + new InvalidArgumentException("Error codes of rules have different types", pos)); + } + Class erCodeType = + filteredErCodeTypes.isEmpty() ? String.class : filteredErCodeTypes.iterator().next(); - ResolvableExpression errorCodeExpression = - null != c.erCode() ? expressionVisitor.visit(c.erCode()) : null; - ResolvableExpression errorLevelExpression = - null != c.erLevel() ? expressionVisitor.visit(c.erLevel()) : null; - return new DataPointRule( - name, - dataStructure -> { - if (antecedentContiditonContext != null) { - Map componentMap = - dataStructure.values().stream() - .collect( - Collectors.toMap( - Dataset.Component::getName, component -> component)); - return new ExpressionVisitor(componentMap, processingEngine, engine) - .visit(antecedentContiditonContext); - } else { - return ResolvableExpression.withType(Boolean.class) - .withPosition(pos) - .using(cc -> Boolean.TRUE); - } - }, - dataStructure -> { - Map componentMap = - dataStructure.values().stream() - .collect( - Collectors.toMap( - Dataset.Component::getName, component -> component)); - return new ExpressionVisitor(componentMap, processingEngine, engine) - .visit(consequentConditionContext); - }, - errorCodeExpression, - errorLevelExpression); - }) - .collect(Collectors.toList()); - DataPointRuleset dataPointRuleset = - new DataPointRuleset( - rulesetName, rules, variables, valuedomains, alias, erCodeType, erLevelType); - Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); - bindings.put(rulesetName, dataPointRuleset); - return dataPointRuleset; - } + Set erLevelTypes = + ctx.ruleClauseDatapoint().ruleItemDatapoint().stream() + .map( + c -> { + VtlParser.ErLevelContext erLevelContext = c.erLevel(); + if (null == erLevelContext) return Object.class; + return expressionVisitor.visit(c.erLevel()).getType(); + }) + .collect(Collectors.toSet()); + List filteredErLevelTypes = + erLevelTypes.stream().filter(t -> !t.equals(Object.class)).collect(Collectors.toList()); + if (filteredErLevelTypes.size() > 1) { + throw new VtlRuntimeException( + new InvalidArgumentException("Error levels of rules have different types", pos)); + } + Class erLevelType = + filteredErLevelTypes.isEmpty() ? Long.class : filteredErLevelTypes.iterator().next(); - // TODO: handle when clause (expr ctx) - @Override - public Object visitDefHierarchical(VtlParser.DefHierarchicalContext ctx) { - var pos = fromContext(ctx); - String rulesetName = ctx.rulesetID().getText(); + AtomicInteger index = new AtomicInteger(); + List rules = + ctx.ruleClauseDatapoint().ruleItemDatapoint().stream() + .map( + c -> { + TerminalNode identifier = c.IDENTIFIER(); + int i = index.getAndIncrement() + 1; + String name = null != identifier ? identifier.getText() : rulesetName + "_" + i; - // Mix variables and valuedomain. Information useless for now, find use case to do so - String variable = ctx.hierRuleSignature().IDENTIFIER().getText(); + VtlParser.ExprContext antecedentContiditonContext = c.antecedentContiditon; + VtlParser.ExprContext consequentConditionContext = c.consequentCondition; - Set> erCodeTypes = - ctx.ruleClauseHierarchical().ruleItemHierarchical().stream() - .map( - c -> { - VtlParser.ErCodeContext erCodeContext = c.erCode(); - if (null == erCodeContext) return Object.class; - return expressionVisitor.visit(c.erCode()).getType(); - }) - .collect(Collectors.toSet()); - List> filteredErCodeTypes = - erCodeTypes.stream().filter(t -> !t.equals(Object.class)).collect(Collectors.toList()); - if (filteredErCodeTypes.size() > 1) { - throw new VtlRuntimeException( - new InvalidArgumentException("Error codes of rules have different types", pos)); + ResolvableExpression errorCodeExpression = + null != c.erCode() ? expressionVisitor.visit(c.erCode()) : null; + ResolvableExpression errorLevelExpression = + null != c.erLevel() ? expressionVisitor.visit(c.erLevel()) : null; + return new DataPointRule( + name, + dataStructure -> { + if (antecedentContiditonContext != null) { + Map componentMap = + dataStructure.values().stream() + .collect( + Collectors.toMap( + Dataset.Component::getName, component -> component)); + return new ExpressionVisitor(componentMap, processingEngine, engine) + .visit(antecedentContiditonContext); + } else { + return ResolvableExpression.withType(Boolean.class) + .withPosition(pos) + .using(cc -> Boolean.TRUE); + } + }, + dataStructure -> { + Map componentMap = + dataStructure.values().stream() + .collect( + Collectors.toMap( + Dataset.Component::getName, component -> component)); + return new ExpressionVisitor(componentMap, processingEngine, engine) + .visit(consequentConditionContext); + }, + errorCodeExpression, + errorLevelExpression); + }) + .collect(Collectors.toList()); + DataPointRuleset dataPointRuleset = + new DataPointRuleset( + rulesetName, rules, variables, valuedomains, alias, erCodeType, erLevelType); + Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); + bindings.put(rulesetName, dataPointRuleset); + return dataPointRuleset; } - Class erCodeType = - filteredErCodeTypes.isEmpty() ? String.class : filteredErCodeTypes.iterator().next(); - Set> erLevelTypes = - ctx.ruleClauseHierarchical().ruleItemHierarchical().stream() - .map( - c -> { - VtlParser.ErLevelContext erLevelContext = c.erLevel(); - if (null == erLevelContext) return Object.class; - return expressionVisitor.visit(c.erLevel()).getType(); - }) - .collect(Collectors.toSet()); - List> filteredErLevelTypes = - erLevelTypes.stream().filter(t -> !t.equals(Object.class)).collect(Collectors.toList()); - if (filteredErLevelTypes.size() > 1) { - throw new VtlRuntimeException( - new InvalidArgumentException("Error levels of rules have different types", pos)); - } - Class erLevelType = - filteredErLevelTypes.isEmpty() ? Long.class : filteredErLevelTypes.iterator().next(); + // TODO: handle when clause (expr ctx) + @Override + public Object visitDefHierarchical(VtlParser.DefHierarchicalContext ctx) { + var pos = fromContext(ctx); + String rulesetName = ctx.rulesetID().getText(); + + // Mix variables and valuedomain. Information useless for now, find use case to do so + String variable = ctx.hierRuleSignature().IDENTIFIER().getText(); + + Set> erCodeTypes = + ctx.ruleClauseHierarchical().ruleItemHierarchical().stream() + .map( + c -> { + VtlParser.ErCodeContext erCodeContext = c.erCode(); + if (null == erCodeContext) return Object.class; + return expressionVisitor.visit(c.erCode()).getType(); + }) + .collect(Collectors.toSet()); + List> filteredErCodeTypes = + erCodeTypes.stream().filter(t -> !t.equals(Object.class)).collect(Collectors.toList()); + if (filteredErCodeTypes.size() > 1) { + throw new VtlRuntimeException( + new InvalidArgumentException("Error codes of rules have different types", pos)); + } + Class erCodeType = + filteredErCodeTypes.isEmpty() ? String.class : filteredErCodeTypes.iterator().next(); - AtomicInteger index = new AtomicInteger(); - List rules = - ctx.ruleClauseHierarchical().ruleItemHierarchical().stream() - .map( - r -> { - TerminalNode identifier = r.IDENTIFIER(); - int i = index.getAndIncrement() + 1; - String ruleName = - null != identifier ? identifier.getText() : rulesetName + "_" + i; + Set> erLevelTypes = + ctx.ruleClauseHierarchical().ruleItemHierarchical().stream() + .map( + c -> { + VtlParser.ErLevelContext erLevelContext = c.erLevel(); + if (null == erLevelContext) return Object.class; + return expressionVisitor.visit(c.erLevel()).getType(); + }) + .collect(Collectors.toSet()); + List> filteredErLevelTypes = + erLevelTypes.stream().filter(t -> !t.equals(Object.class)).collect(Collectors.toList()); + if (filteredErLevelTypes.size() > 1) { + throw new VtlRuntimeException( + new InvalidArgumentException("Error levels of rules have different types", pos)); + } + Class erLevelType = + filteredErLevelTypes.isEmpty() ? Long.class : filteredErLevelTypes.iterator().next(); - List codeItems = new ArrayList<>(); - VtlParser.CodeItemRelationContext codeItemRelationContext = r.codeItemRelation(); - String valueDomainValue = - codeItemRelationContext.valueDomainValue().IDENTIFIER().getText(); - codeItems.add(valueDomainValue); + AtomicInteger index = new AtomicInteger(); + List rules = + ctx.ruleClauseHierarchical().ruleItemHierarchical().stream() + .map( + r -> { + TerminalNode identifier = r.IDENTIFIER(); + int i = index.getAndIncrement() + 1; + String ruleName = + null != identifier ? identifier.getText() : rulesetName + "_" + i; - VtlParser.ComparisonOperandContext comparisonOperandContext = - codeItemRelationContext.comparisonOperand(); + List codeItems = new ArrayList<>(); + VtlParser.CodeItemRelationContext codeItemRelationContext = r.codeItemRelation(); + String valueDomainValue = + codeItemRelationContext.valueDomainValue().IDENTIFIER().getText(); + codeItems.add(valueDomainValue); - StringBuilder codeItemExpressionBuilder = new StringBuilder(); - codeItemRelationContext - .codeItemRelationClause() - .forEach( - circ -> { - TerminalNode minus = circ.MINUS(); - String rightCodeItem = circ.rightCodeItem.getText(); - codeItems.add(rightCodeItem); - if (minus != null) - codeItemExpressionBuilder.append(" -").append(rightCodeItem); - // plus value or plus null & minus null mean plus - codeItemExpressionBuilder.append(" +").append(rightCodeItem); - }); + VtlParser.ComparisonOperandContext comparisonOperandContext = + codeItemRelationContext.comparisonOperand(); - String rightExpressionToEval = codeItemExpressionBuilder.toString(); - String expressionToEval = - "bool_var := " - + valueDomainValue - + " " - + comparisonOperandContext.getText() - + " " - + rightExpressionToEval - + ";"; + StringBuilder codeItemExpressionBuilder = new StringBuilder(); + codeItemRelationContext + .codeItemRelationClause() + .forEach( + circ -> { + TerminalNode minus = circ.MINUS(); + String rightCodeItem = circ.rightCodeItem.getText(); + codeItems.add(rightCodeItem); + if (minus != null) + codeItemExpressionBuilder.append(" -").append(rightCodeItem); + // plus value or plus null & minus null mean plus + codeItemExpressionBuilder.append(" +").append(rightCodeItem); + }); - ResolvableExpression leftExpression = - ResolvableExpression.withType(Double.class) - .withPosition(pos) - .using( - context -> { - Bindings bindings = new SimpleBindings(context); - bindings.forEach( - (k, v) -> - engine - .getContext() - .setAttribute(k, v, ScriptContext.ENGINE_SCOPE)); - try { - engine.eval("left := " + valueDomainValue + ";"); - Object left = engine.getContext().getAttribute("left"); - engine - .getContext() - .removeAttribute("left", ScriptContext.ENGINE_SCOPE); - bindings - .keySet() - .forEach( - k -> - engine - .getContext() - .removeAttribute(k, ScriptContext.ENGINE_SCOPE)); - if (left.getClass().isAssignableFrom(Double.class)) { - return (Double) left; - } - return ((Long) left).doubleValue(); - } catch (ScriptException e) { - throw new VtlRuntimeException( - new VtlScriptException( - "right hierarchical rule has to return long or double", - pos)); - } - }); + String rightExpressionToEval = codeItemExpressionBuilder.toString(); + String expressionToEval = + "bool_var := " + + valueDomainValue + + " " + + comparisonOperandContext.getText() + + " " + + rightExpressionToEval + + ";"; - ResolvableExpression rightExpression = - ResolvableExpression.withType(Double.class) - .withPosition(pos) - .using( - context -> { - Bindings bindings = new SimpleBindings(context); - bindings.forEach( - (k, v) -> - engine - .getContext() - .setAttribute(k, v, ScriptContext.ENGINE_SCOPE)); - try { - engine.eval("right := " + rightExpressionToEval + ";"); - Object right = engine.getContext().getAttribute("right"); - engine - .getContext() - .removeAttribute("right", ScriptContext.ENGINE_SCOPE); - bindings - .keySet() - .forEach( - k -> - engine - .getContext() - .removeAttribute(k, ScriptContext.ENGINE_SCOPE)); - if (right.getClass().isAssignableFrom(Double.class)) { - return (Double) right; - } - return ((Long) right).doubleValue(); - } catch (ScriptException e) { - throw new VtlRuntimeException( - new VtlScriptException( - "right hierarchical rule has to return long or double", - pos)); - } - }); + ResolvableExpression leftExpression = + ResolvableExpression.withType(Double.class) + .withPosition(pos) + .using( + context -> { + Bindings bindings = new SimpleBindings(context); + bindings.forEach( + (k, v) -> + engine + .getContext() + .setAttribute(k, v, ScriptContext.ENGINE_SCOPE)); + try { + engine.eval("left := " + valueDomainValue + ";"); + Object left = engine.getContext().getAttribute("left"); + engine + .getContext() + .removeAttribute("left", ScriptContext.ENGINE_SCOPE); + bindings + .keySet() + .forEach( + k -> + engine + .getContext() + .removeAttribute(k, ScriptContext.ENGINE_SCOPE)); + if (left.getClass().isAssignableFrom(Double.class)) { + return (Double) left; + } + return ((Long) left).doubleValue(); + } catch (ScriptException e) { + throw new VtlRuntimeException( + new VtlScriptException( + "right hierarchical rule has to return long or double", + pos)); + } + }); - ResolvableExpression expression = - ResolvableExpression.withType(Boolean.class) - .withPosition(pos) - .using( - context -> { - Bindings bindings = new SimpleBindings(context); - bindings.forEach( - (k, v) -> - engine - .getContext() - .setAttribute(k, v, ScriptContext.ENGINE_SCOPE)); - try { - engine.eval(expressionToEval); - Boolean boolVar = - (Boolean) engine.getContext().getAttribute("bool_var"); - engine - .getContext() - .removeAttribute("bool_var", ScriptContext.ENGINE_SCOPE); - bindings - .keySet() - .forEach( - k -> - engine - .getContext() - .removeAttribute(k, ScriptContext.ENGINE_SCOPE)); - return boolVar; - } catch (ScriptException e) { - throw new VtlRuntimeException( - new VtlScriptException( - "hierarchical rule has to return boolean", pos)); - } - }); + ResolvableExpression rightExpression = + ResolvableExpression.withType(Double.class) + .withPosition(pos) + .using( + context -> { + Bindings bindings = new SimpleBindings(context); + bindings.forEach( + (k, v) -> + engine + .getContext() + .setAttribute(k, v, ScriptContext.ENGINE_SCOPE)); + try { + engine.eval("right := " + rightExpressionToEval + ";"); + Object right = engine.getContext().getAttribute("right"); + engine + .getContext() + .removeAttribute("right", ScriptContext.ENGINE_SCOPE); + bindings + .keySet() + .forEach( + k -> + engine + .getContext() + .removeAttribute(k, ScriptContext.ENGINE_SCOPE)); + if (right.getClass().isAssignableFrom(Double.class)) { + return (Double) right; + } + return ((Long) right).doubleValue(); + } catch (ScriptException e) { + throw new VtlRuntimeException( + new VtlScriptException( + "right hierarchical rule has to return long or double", + pos)); + } + }); - ResolvableExpression errorCodeExpression = - null != r.erCode() ? expressionVisitor.visit(r.erCode()) : null; - ResolvableExpression errorLevelExpression = - null != r.erLevel() ? expressionVisitor.visit(r.erLevel()) : null; - return new HierarchicalRule( - ruleName, - valueDomainValue, - expression, - leftExpression, - rightExpression, - codeItems, - errorCodeExpression, - errorLevelExpression); - }) - .collect(Collectors.toList()); - HierarchicalRuleset hr = new HierarchicalRuleset(rules, variable, erCodeType, erLevelType); - Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); - bindings.put(rulesetName, hr); - return hr; - } + ResolvableExpression expression = + ResolvableExpression.withType(Boolean.class) + .withPosition(pos) + .using( + context -> { + Bindings bindings = new SimpleBindings(context); + bindings.forEach( + (k, v) -> + engine + .getContext() + .setAttribute(k, v, ScriptContext.ENGINE_SCOPE)); + try { + engine.eval(expressionToEval); + Boolean boolVar = + (Boolean) engine.getContext().getAttribute("bool_var"); + engine + .getContext() + .removeAttribute("bool_var", ScriptContext.ENGINE_SCOPE); + bindings + .keySet() + .forEach( + k -> + engine + .getContext() + .removeAttribute(k, ScriptContext.ENGINE_SCOPE)); + return boolVar; + } catch (ScriptException e) { + throw new VtlRuntimeException( + new VtlScriptException( + "hierarchical rule has to return boolean", pos)); + } + }); + + ResolvableExpression errorCodeExpression = + null != r.erCode() ? expressionVisitor.visit(r.erCode()) : null; + ResolvableExpression errorLevelExpression = + null != r.erLevel() ? expressionVisitor.visit(r.erLevel()) : null; + return new HierarchicalRule( + ruleName, + valueDomainValue, + expression, + leftExpression, + rightExpression, + codeItems, + errorCodeExpression, + errorLevelExpression); + }) + .collect(Collectors.toList()); + HierarchicalRuleset hr = new HierarchicalRuleset(rules, variable, erCodeType, erLevelType); + Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); + bindings.put(rulesetName, hr); + return hr; + } } diff --git a/vtl-engine/src/main/java/module-info.java b/vtl-engine/src/main/java/module-info.java index a99486236..969458409 100644 --- a/vtl-engine/src/main/java/module-info.java +++ b/vtl-engine/src/main/java/module-info.java @@ -1,17 +1,12 @@ import fr.insee.vtl.engine.VtlScriptEngineFactory; import fr.insee.vtl.engine.functions.LevenshteinProvider; -import fr.insee.vtl.engine.processors.InMemoryProcessingEngine; -import fr.insee.vtl.model.FunctionProvider; -import fr.insee.vtl.model.ProcessingEngine; -import fr.insee.vtl.model.ProcessingEngineFactory; -import javax.script.ScriptEngineFactory; /** This module contains the actual VTL engine. */ module fr.insee.vtl.engine { exports fr.insee.vtl.engine.exceptions; requires transitive java.scripting; - requires transitive fr.insee.vtl.parser.shaded; + requires transitive fr.insee.vtl.parser; requires transitive fr.insee.vtl.model; uses ProcessingEngine; diff --git a/vtl-parser/pom.xml b/vtl-parser/pom.xml index 9ad705b9b..661c255c5 100644 --- a/vtl-parser/pom.xml +++ b/vtl-parser/pom.xml @@ -28,6 +28,7 @@ org.antlr antlr4-runtime ${antlr4.version} + compile @@ -51,6 +52,23 @@ + + org.apache.maven.plugins + maven-jar-plugin + 3.5.0 + + + early-jar + process-classes + + jar + + + pre-shade + + + + org.apache.maven.plugins maven-shade-plugin @@ -58,12 +76,14 @@ shade-antlr - package + process-classes shade + false + false @@ -78,18 +98,34 @@ org.antlr.v4 - fr.insee.trevas.antlr.shaded.v4 + fr.insee.vtl.parser.antlr4 - - - - fr.insee.vtl.parser.shaded - - - - + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + 3.1.0 + + + merge-shaded-jar-into-classes + process-classes + + run + + + + + diff --git a/vtl-parser/src/main/java/fr/insee/vtl/parser/package-info.java b/vtl-parser/src/main/java/fr/insee/vtl/parser/package-info.java deleted file mode 100644 index 6d79e9fe0..000000000 --- a/vtl-parser/src/main/java/fr/insee/vtl/parser/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** This package exposes the constructs generated by Antlr from the VTL grammar files. */ -package fr.insee.vtl.parser; diff --git a/vtl-parser/src/main/java/module-info.java b/vtl-parser/src/main/java/module-info.java new file mode 100644 index 000000000..ac06c7379 --- /dev/null +++ b/vtl-parser/src/main/java/module-info.java @@ -0,0 +1,10 @@ +/** + * This module exposes the constructs generated by Antlr from the VTL grammar files. + */ +module fr.insee.vtl.parser { + requires org.antlr.antlr4.runtime; + + exports fr.insee.vtl.parser; + + opens fr.insee.vtl.parser; +} diff --git a/vtl-prov/pom.xml b/vtl-prov/pom.xml index 653070e44..7b08e5534 100644 --- a/vtl-prov/pom.xml +++ b/vtl-prov/pom.xml @@ -20,6 +20,8 @@ ${project.basedir}/../coverage/target/site/jacoco-aggregate/jacoco.xml + + vtl-spark @@ -64,8 +66,26 @@ pom + + + fr.insee.trevas + ${trevas.spark.integration.test.module} + ${project.version} + test + + + + + spark4-tests + + vtl-spark4 + + + + diff --git a/vtl-prov/src/main/java/fr/insee/vtl/prov/ProvenanceListener.java b/vtl-prov/src/main/java/fr/insee/vtl/prov/ProvenanceListener.java index d80e37444..fb62a1520 100644 --- a/vtl-prov/src/main/java/fr/insee/vtl/prov/ProvenanceListener.java +++ b/vtl-prov/src/main/java/fr/insee/vtl/prov/ProvenanceListener.java @@ -1,5 +1,8 @@ package fr.insee.vtl.prov; +import fr.insee.trevas.antlr.shaded.v4.runtime.*; +import fr.insee.trevas.antlr.shaded.v4.runtime.misc.Interval; +import fr.insee.trevas.antlr.shaded.v4.runtime.tree.ParseTreeWalker; import fr.insee.vtl.model.Dataset; import fr.insee.vtl.parser.VtlBaseListener; import fr.insee.vtl.parser.VtlLexer; @@ -15,9 +18,6 @@ import java.util.Set; import javax.script.ScriptEngine; import javax.script.ScriptException; -import org.antlr.v4.runtime.*; -import org.antlr.v4.runtime.misc.Interval; -import org.antlr.v4.runtime.tree.ParseTreeWalker; /** ANTLR Listener that create provenance objects. */ public class ProvenanceListener extends VtlBaseListener { diff --git a/vtl-prov/src/main/java/fr/insee/vtl/prov/Variable.java b/vtl-prov/src/main/java/fr/insee/vtl/prov/Variable.java index 84d7343f2..30f4f06a9 100644 --- a/vtl-prov/src/main/java/fr/insee/vtl/prov/Variable.java +++ b/vtl-prov/src/main/java/fr/insee/vtl/prov/Variable.java @@ -1,9 +1,9 @@ package fr.insee.vtl.prov; +import fr.insee.trevas.antlr.shaded.v4.runtime.ParserRuleContext; +import fr.insee.trevas.antlr.shaded.v4.runtime.Token; +import fr.insee.trevas.antlr.shaded.v4.runtime.misc.Interval; import java.util.Optional; -import org.antlr.v4.runtime.ParserRuleContext; -import org.antlr.v4.runtime.Token; -import org.antlr.v4.runtime.misc.Interval; public class Variable { diff --git a/vtl-prov/src/main/java/fr/insee/vtl/prov/VariableGraphListener.java b/vtl-prov/src/main/java/fr/insee/vtl/prov/VariableGraphListener.java index 3e4748431..ffc04ebdd 100644 --- a/vtl-prov/src/main/java/fr/insee/vtl/prov/VariableGraphListener.java +++ b/vtl-prov/src/main/java/fr/insee/vtl/prov/VariableGraphListener.java @@ -1,12 +1,12 @@ package fr.insee.vtl.prov; +import fr.insee.trevas.antlr.shaded.v4.runtime.ParserRuleContext; import fr.insee.vtl.parser.VtlBaseListener; import fr.insee.vtl.parser.VtlParser; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; -import org.antlr.v4.runtime.ParserRuleContext; import org.jgrapht.graph.DefaultDirectedGraph; import org.jgrapht.graph.DefaultEdge; diff --git a/vtl-prov/src/main/java/fr/insee/vtl/prov/utils/AntlrUtils.java b/vtl-prov/src/main/java/fr/insee/vtl/prov/utils/AntlrUtils.java index d63257f5c..5217f230f 100644 --- a/vtl-prov/src/main/java/fr/insee/vtl/prov/utils/AntlrUtils.java +++ b/vtl-prov/src/main/java/fr/insee/vtl/prov/utils/AntlrUtils.java @@ -1,11 +1,11 @@ package fr.insee.vtl.prov.utils; +import fr.insee.trevas.antlr.shaded.v4.runtime.*; +import fr.insee.trevas.antlr.shaded.v4.runtime.misc.Interval; import fr.insee.vtl.parser.VtlLexer; import fr.insee.vtl.parser.VtlParser; import java.util.HashMap; import java.util.Map; -import org.antlr.v4.runtime.*; -import org.antlr.v4.runtime.misc.Interval; public class AntlrUtils { diff --git a/vtl-prov/src/test/java/fr/insee/vtl/prov/VariableGraphListenerTest.java b/vtl-prov/src/test/java/fr/insee/vtl/prov/VariableGraphListenerTest.java index 9ce99a699..0b72860a9 100644 --- a/vtl-prov/src/test/java/fr/insee/vtl/prov/VariableGraphListenerTest.java +++ b/vtl-prov/src/test/java/fr/insee/vtl/prov/VariableGraphListenerTest.java @@ -2,13 +2,13 @@ import static org.assertj.core.api.Assertions.assertThat; +import fr.insee.trevas.antlr.shaded.v4.runtime.CharStreams; +import fr.insee.trevas.antlr.shaded.v4.runtime.CodePointCharStream; +import fr.insee.trevas.antlr.shaded.v4.runtime.CommonTokenStream; +import fr.insee.trevas.antlr.shaded.v4.runtime.tree.ParseTreeWalker; import fr.insee.vtl.parser.VtlLexer; import fr.insee.vtl.parser.VtlParser; import java.util.Set; -import org.antlr.v4.runtime.CharStreams; -import org.antlr.v4.runtime.CodePointCharStream; -import org.antlr.v4.runtime.CommonTokenStream; -import org.antlr.v4.runtime.tree.ParseTreeWalker; import org.jgrapht.Graph; import org.jgrapht.graph.DefaultDirectedGraph; import org.jgrapht.graph.DefaultEdge; diff --git a/vtl-spark/pom.xml b/vtl-spark/pom.xml index d80045813..fca333427 100644 --- a/vtl-spark/pom.xml +++ b/vtl-spark/pom.xml @@ -36,8 +36,7 @@ org.apache.spark - spark-sql_2.12 - 3.5.8 + spark-sql_${spark3.scala.binary} diff --git a/vtl-spark4/pom.xml b/vtl-spark4/pom.xml index 0ca251d1e..b5eb9784b 100644 --- a/vtl-spark4/pom.xml +++ b/vtl-spark4/pom.xml @@ -7,13 +7,13 @@ fr.insee.trevas trevas-parent - 2.3.0-SNAPSHOT + 2.4.0-SNAPSHOT vtl-spark4 VTL Spark4 Trevas engine for Apache Spark 4 - 2.3.0-SNAPSHOT + 2.4.0-SNAPSHOT @@ -25,19 +25,18 @@ fr.insee.trevas vtl-model - 2.3.0-SNAPSHOT + 2.4.0-SNAPSHOT compile fr.insee.trevas vtl-engine - 2.3.0-SNAPSHOT + 2.4.0-SNAPSHOT compile org.apache.spark - spark-sql_2.13 - 4.0.0 + spark-sql_${spark4.scala.binary} From 021fab0a893bc2cd859582defff0ab5b858c618a Mon Sep 17 00:00:00 2001 From: Hadrien Kohl Date: Wed, 6 May 2026 19:08:41 +0200 Subject: [PATCH 08/16] Working parser and engine --- .../vtl/engine/VtlSyntaxPreprocessor.java | 2 +- .../insee/vtl/engine/utils/TypeChecking.java | 2 +- .../vtl/engine/utils/dag/DAGStatement.java | 8 +- .../vtl/engine/visitors/AnalyticsVisitor.java | 4 +- .../engine/visitors/AssignmentVisitor.java | 702 +++++++++--------- .../vtl/engine/visitors/ClauseVisitor.java | 4 +- .../engine/visitors/DAGBuildingVisitor.java | 8 +- .../expression/ComparisonVisitor.java | 4 +- .../functions/GenericFunctionsVisitor.java | 4 +- .../functions/SetFunctionsVisitor.java | 2 +- .../functions/TimeFunctionsVisitor.java | 2 +- vtl-engine/src/main/java/module-info.java | 31 +- .../utils/dag/DagDefineStatementsTest.java | 12 +- vtl-parser/pom.xml | 73 +- vtl-parser/src/main/java/module-info.java | 10 +- 15 files changed, 428 insertions(+), 440 deletions(-) diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/VtlSyntaxPreprocessor.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/VtlSyntaxPreprocessor.java index a691c8d08..d2e36eebf 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/VtlSyntaxPreprocessor.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/VtlSyntaxPreprocessor.java @@ -1,12 +1,12 @@ package fr.insee.vtl.engine; -import fr.insee.trevas.antlr.shaded.v4.runtime.ParserRuleContext; import fr.insee.vtl.engine.utils.dag.DAGBuilder; import fr.insee.vtl.engine.utils.dag.DAGStatement; import fr.insee.vtl.engine.visitors.DAGBuildingVisitor; import fr.insee.vtl.model.exceptions.VtlMultiErrorScriptException; import fr.insee.vtl.model.exceptions.VtlScriptException; import fr.insee.vtl.parser.VtlParser; +import fr.insee.vtl.parser.antlr4.runtime.ParserRuleContext; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/utils/TypeChecking.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/utils/TypeChecking.java index 033cbce84..3d2ea296e 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/utils/TypeChecking.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/utils/TypeChecking.java @@ -2,12 +2,12 @@ import static fr.insee.vtl.engine.VtlScriptEngine.fromContext; -import fr.insee.trevas.antlr.shaded.v4.runtime.tree.ParseTree; import fr.insee.vtl.engine.exceptions.VtlRuntimeException; import fr.insee.vtl.model.Dataset; import fr.insee.vtl.model.ResolvableExpression; import fr.insee.vtl.model.TypedExpression; import fr.insee.vtl.model.exceptions.InvalidTypeException; +import fr.insee.vtl.parser.antlr4.runtime.tree.ParseTree; import java.time.Instant; import java.util.List; import java.util.Objects; diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/utils/dag/DAGStatement.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/utils/dag/DAGStatement.java index 6fd256a69..e639690af 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/utils/dag/DAGStatement.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/utils/dag/DAGStatement.java @@ -1,13 +1,13 @@ package fr.insee.vtl.engine.utils.dag; -import fr.insee.trevas.antlr.shaded.v4.runtime.ParserRuleContext; -import fr.insee.trevas.antlr.shaded.v4.runtime.tree.ParseTree; -import fr.insee.trevas.antlr.shaded.v4.runtime.tree.RuleNode; -import fr.insee.trevas.antlr.shaded.v4.runtime.tree.TerminalNode; import fr.insee.vtl.engine.VtlScriptEngine; import fr.insee.vtl.model.Positioned; import fr.insee.vtl.model.exceptions.VtlMultiStatementScriptException; import fr.insee.vtl.parser.VtlParser; +import fr.insee.vtl.parser.antlr4.runtime.ParserRuleContext; +import fr.insee.vtl.parser.antlr4.runtime.tree.ParseTree; +import fr.insee.vtl.parser.antlr4.runtime.tree.RuleNode; +import fr.insee.vtl.parser.antlr4.runtime.tree.TerminalNode; import java.util.Collection; import java.util.Comparator; import java.util.HashSet; diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/AnalyticsVisitor.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/AnalyticsVisitor.java index 0cd33de19..e03db76cd 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/AnalyticsVisitor.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/AnalyticsVisitor.java @@ -2,8 +2,6 @@ import static fr.insee.vtl.engine.VtlScriptEngine.fromContext; -import fr.insee.trevas.antlr.shaded.v4.runtime.Token; -import fr.insee.trevas.antlr.shaded.v4.runtime.tree.ParseTree; import fr.insee.vtl.engine.exceptions.InvalidArgumentException; import fr.insee.vtl.engine.exceptions.VtlRuntimeException; import fr.insee.vtl.model.Analytics; @@ -12,6 +10,8 @@ import fr.insee.vtl.model.exceptions.VtlScriptException; import fr.insee.vtl.parser.VtlBaseVisitor; import fr.insee.vtl.parser.VtlParser; +import fr.insee.vtl.parser.antlr4.runtime.Token; +import fr.insee.vtl.parser.antlr4.runtime.tree.ParseTree; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/AssignmentVisitor.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/AssignmentVisitor.java index 479ccf3e6..41c1a7188 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/AssignmentVisitor.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/AssignmentVisitor.java @@ -1,5 +1,7 @@ package fr.insee.vtl.engine.visitors; +import static fr.insee.vtl.engine.VtlScriptEngine.fromContext; + import fr.insee.vtl.engine.VtlScriptEngine; import fr.insee.vtl.engine.exceptions.InvalidArgumentException; import fr.insee.vtl.engine.exceptions.VtlRuntimeException; @@ -9,387 +11,383 @@ import fr.insee.vtl.model.exceptions.VtlScriptException; import fr.insee.vtl.parser.VtlBaseVisitor; import fr.insee.vtl.parser.VtlParser; - +import fr.insee.vtl.parser.antlr4.runtime.tree.TerminalNode; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; import javax.script.Bindings; import javax.script.ScriptContext; import javax.script.ScriptException; import javax.script.SimpleBindings; -import java.util.*; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Collectors; - -import static fr.insee.vtl.engine.VtlScriptEngine.fromContext; -/** - * AssignmentVisitor is the visitor for VTL assignment expressions. - */ +/** AssignmentVisitor is the visitor for VTL assignment expressions. */ public class AssignmentVisitor extends VtlBaseVisitor { - private final VtlScriptEngine engine; - private final ProcessingEngine processingEngine; - private final ExpressionVisitor expressionVisitor; + private final VtlScriptEngine engine; + private final ProcessingEngine processingEngine; + private final ExpressionVisitor expressionVisitor; - /** - * Constructor taking a scripting engine and a processing engine. - * - * @param engine The scripting engine. - * @param processingEngine The processing engine. - */ - public AssignmentVisitor(VtlScriptEngine engine, ProcessingEngine processingEngine) { - this.engine = Objects.requireNonNull(engine); - this.processingEngine = Objects.requireNonNull(processingEngine); - expressionVisitor = - new ExpressionVisitor( - engine.getBindings(ScriptContext.ENGINE_SCOPE), processingEngine, engine); - } + /** + * Constructor taking a scripting engine and a processing engine. + * + * @param engine The scripting engine. + * @param processingEngine The processing engine. + */ + public AssignmentVisitor(VtlScriptEngine engine, ProcessingEngine processingEngine) { + this.engine = Objects.requireNonNull(engine); + this.processingEngine = Objects.requireNonNull(processingEngine); + expressionVisitor = + new ExpressionVisitor( + engine.getBindings(ScriptContext.ENGINE_SCOPE), processingEngine, engine); + } - private Object visitAssignment(VtlParser.ExprContext expr) { - ResolvableExpression resolvableExpression = expressionVisitor.visit(expr); - return resolvableExpression.resolve(engine.getBindings(ScriptContext.ENGINE_SCOPE)); - } + private Object visitAssignment(VtlParser.ExprContext expr) { + ResolvableExpression resolvableExpression = expressionVisitor.visit(expr); + return resolvableExpression.resolve(engine.getBindings(ScriptContext.ENGINE_SCOPE)); + } - @Override - public Object visitTemporaryAssignment(VtlParser.TemporaryAssignmentContext ctx) { - var result = visitAssignment(ctx.expr()); - Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); - String variableIdentifier = ctx.varID().getText(); - bindings.put(variableIdentifier, result); - return result; - } + @Override + public Object visitTemporaryAssignment(VtlParser.TemporaryAssignmentContext ctx) { + var result = visitAssignment(ctx.expr()); + Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); + String variableIdentifier = ctx.varID().getText(); + bindings.put(variableIdentifier, result); + return result; + } - @Override - public Object visitPersistAssignment(VtlParser.PersistAssignmentContext ctx) { - var result = visitAssignment(ctx.expr()); - if (result instanceof Dataset resultDataset) { - result = new PersistentDataset(resultDataset); - Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); - String variableIdentifier = ctx.varID().getText(); - bindings.put(variableIdentifier, result); - return result; - } - throw new VtlRuntimeException( - new InvalidTypeException(Dataset.class, result.getClass(), fromContext(ctx))); + @Override + public Object visitPersistAssignment(VtlParser.PersistAssignmentContext ctx) { + var result = visitAssignment(ctx.expr()); + if (result instanceof Dataset resultDataset) { + result = new PersistentDataset(resultDataset); + Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); + String variableIdentifier = ctx.varID().getText(); + bindings.put(variableIdentifier, result); + return result; } + throw new VtlRuntimeException( + new InvalidTypeException(Dataset.class, result.getClass(), fromContext(ctx))); + } - @Override - public Object visitDefDatapointRuleset(VtlParser.DefDatapointRulesetContext ctx) { - var pos = fromContext(ctx); - String rulesetName = ctx.rulesetID().getText(); - List signature = ctx.rulesetSignature().signature(); - List variables = - ctx.rulesetSignature().VARIABLE() != null - ? signature.stream().map(s -> s.varID().getText()).collect(Collectors.toList()) - : List.of(); - List valuedomains = - ctx.rulesetSignature().VALUE_DOMAIN() != null - ? signature.stream().map(s -> s.varID().getText()).collect(Collectors.toList()) - : List.of(); - Map alias = - signature.stream() - .filter(s -> null != s.alias()) - .collect(Collectors.toMap(k -> k.varID().getText(), v -> v.alias().getText())); + @Override + public Object visitDefDatapointRuleset(VtlParser.DefDatapointRulesetContext ctx) { + var pos = fromContext(ctx); + String rulesetName = ctx.rulesetID().getText(); + List signature = ctx.rulesetSignature().signature(); + List variables = + ctx.rulesetSignature().VARIABLE() != null + ? signature.stream().map(s -> s.varID().getText()).collect(Collectors.toList()) + : List.of(); + List valuedomains = + ctx.rulesetSignature().VALUE_DOMAIN() != null + ? signature.stream().map(s -> s.varID().getText()).collect(Collectors.toList()) + : List.of(); + Map alias = + signature.stream() + .filter(s -> null != s.alias()) + .collect(Collectors.toMap(k -> k.varID().getText(), v -> v.alias().getText())); - Set erCodeTypes = - ctx.ruleClauseDatapoint().ruleItemDatapoint().stream() - .map( - c -> { - VtlParser.ErCodeContext erCodeContext = c.erCode(); - if (null == erCodeContext) return Object.class; - return expressionVisitor.visit(c.erCode()).getType(); - }) - .collect(Collectors.toSet()); - List filteredErCodeTypes = - erCodeTypes.stream().filter(t -> !t.equals(Object.class)).collect(Collectors.toList()); - if (filteredErCodeTypes.size() > 1) { - throw new VtlRuntimeException( - new InvalidArgumentException("Error codes of rules have different types", pos)); - } - Class erCodeType = - filteredErCodeTypes.isEmpty() ? String.class : filteredErCodeTypes.iterator().next(); + Set erCodeTypes = + ctx.ruleClauseDatapoint().ruleItemDatapoint().stream() + .map( + c -> { + VtlParser.ErCodeContext erCodeContext = c.erCode(); + if (null == erCodeContext) return Object.class; + return expressionVisitor.visit(c.erCode()).getType(); + }) + .collect(Collectors.toSet()); + List filteredErCodeTypes = + erCodeTypes.stream().filter(t -> !t.equals(Object.class)).collect(Collectors.toList()); + if (filteredErCodeTypes.size() > 1) { + throw new VtlRuntimeException( + new InvalidArgumentException("Error codes of rules have different types", pos)); + } + Class erCodeType = + filteredErCodeTypes.isEmpty() ? String.class : filteredErCodeTypes.iterator().next(); - Set erLevelTypes = - ctx.ruleClauseDatapoint().ruleItemDatapoint().stream() - .map( - c -> { - VtlParser.ErLevelContext erLevelContext = c.erLevel(); - if (null == erLevelContext) return Object.class; - return expressionVisitor.visit(c.erLevel()).getType(); - }) - .collect(Collectors.toSet()); - List filteredErLevelTypes = - erLevelTypes.stream().filter(t -> !t.equals(Object.class)).collect(Collectors.toList()); - if (filteredErLevelTypes.size() > 1) { - throw new VtlRuntimeException( - new InvalidArgumentException("Error levels of rules have different types", pos)); - } - Class erLevelType = - filteredErLevelTypes.isEmpty() ? Long.class : filteredErLevelTypes.iterator().next(); + Set erLevelTypes = + ctx.ruleClauseDatapoint().ruleItemDatapoint().stream() + .map( + c -> { + VtlParser.ErLevelContext erLevelContext = c.erLevel(); + if (null == erLevelContext) return Object.class; + return expressionVisitor.visit(c.erLevel()).getType(); + }) + .collect(Collectors.toSet()); + List filteredErLevelTypes = + erLevelTypes.stream().filter(t -> !t.equals(Object.class)).collect(Collectors.toList()); + if (filteredErLevelTypes.size() > 1) { + throw new VtlRuntimeException( + new InvalidArgumentException("Error levels of rules have different types", pos)); + } + Class erLevelType = + filteredErLevelTypes.isEmpty() ? Long.class : filteredErLevelTypes.iterator().next(); - AtomicInteger index = new AtomicInteger(); - List rules = - ctx.ruleClauseDatapoint().ruleItemDatapoint().stream() - .map( - c -> { - TerminalNode identifier = c.IDENTIFIER(); - int i = index.getAndIncrement() + 1; - String name = null != identifier ? identifier.getText() : rulesetName + "_" + i; + AtomicInteger index = new AtomicInteger(); + List rules = + ctx.ruleClauseDatapoint().ruleItemDatapoint().stream() + .map( + c -> { + TerminalNode identifier = c.IDENTIFIER(); + int i = index.getAndIncrement() + 1; + String name = null != identifier ? identifier.getText() : rulesetName + "_" + i; - VtlParser.ExprContext antecedentContiditonContext = c.antecedentContiditon; - VtlParser.ExprContext consequentConditionContext = c.consequentCondition; + VtlParser.ExprContext antecedentContiditonContext = c.antecedentContiditon; + VtlParser.ExprContext consequentConditionContext = c.consequentCondition; - ResolvableExpression errorCodeExpression = - null != c.erCode() ? expressionVisitor.visit(c.erCode()) : null; - ResolvableExpression errorLevelExpression = - null != c.erLevel() ? expressionVisitor.visit(c.erLevel()) : null; - return new DataPointRule( - name, - dataStructure -> { - if (antecedentContiditonContext != null) { - Map componentMap = - dataStructure.values().stream() - .collect( - Collectors.toMap( - Dataset.Component::getName, component -> component)); - return new ExpressionVisitor(componentMap, processingEngine, engine) - .visit(antecedentContiditonContext); - } else { - return ResolvableExpression.withType(Boolean.class) - .withPosition(pos) - .using(cc -> Boolean.TRUE); - } - }, - dataStructure -> { - Map componentMap = - dataStructure.values().stream() - .collect( - Collectors.toMap( - Dataset.Component::getName, component -> component)); - return new ExpressionVisitor(componentMap, processingEngine, engine) - .visit(consequentConditionContext); - }, - errorCodeExpression, - errorLevelExpression); - }) - .collect(Collectors.toList()); - DataPointRuleset dataPointRuleset = - new DataPointRuleset( - rulesetName, rules, variables, valuedomains, alias, erCodeType, erLevelType); - Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); - bindings.put(rulesetName, dataPointRuleset); - return dataPointRuleset; - } + ResolvableExpression errorCodeExpression = + null != c.erCode() ? expressionVisitor.visit(c.erCode()) : null; + ResolvableExpression errorLevelExpression = + null != c.erLevel() ? expressionVisitor.visit(c.erLevel()) : null; + return new DataPointRule( + name, + dataStructure -> { + if (antecedentContiditonContext != null) { + Map componentMap = + dataStructure.values().stream() + .collect( + Collectors.toMap( + Dataset.Component::getName, component -> component)); + return new ExpressionVisitor(componentMap, processingEngine, engine) + .visit(antecedentContiditonContext); + } else { + return ResolvableExpression.withType(Boolean.class) + .withPosition(pos) + .using(cc -> Boolean.TRUE); + } + }, + dataStructure -> { + Map componentMap = + dataStructure.values().stream() + .collect( + Collectors.toMap( + Dataset.Component::getName, component -> component)); + return new ExpressionVisitor(componentMap, processingEngine, engine) + .visit(consequentConditionContext); + }, + errorCodeExpression, + errorLevelExpression); + }) + .collect(Collectors.toList()); + DataPointRuleset dataPointRuleset = + new DataPointRuleset( + rulesetName, rules, variables, valuedomains, alias, erCodeType, erLevelType); + Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); + bindings.put(rulesetName, dataPointRuleset); + return dataPointRuleset; + } - // TODO: handle when clause (expr ctx) - @Override - public Object visitDefHierarchical(VtlParser.DefHierarchicalContext ctx) { - var pos = fromContext(ctx); - String rulesetName = ctx.rulesetID().getText(); + // TODO: handle when clause (expr ctx) + @Override + public Object visitDefHierarchical(VtlParser.DefHierarchicalContext ctx) { + var pos = fromContext(ctx); + String rulesetName = ctx.rulesetID().getText(); - // Mix variables and valuedomain. Information useless for now, find use case to do so - String variable = ctx.hierRuleSignature().IDENTIFIER().getText(); + // Mix variables and valuedomain. Information useless for now, find use case to do so + String variable = ctx.hierRuleSignature().IDENTIFIER().getText(); - Set> erCodeTypes = - ctx.ruleClauseHierarchical().ruleItemHierarchical().stream() - .map( - c -> { - VtlParser.ErCodeContext erCodeContext = c.erCode(); - if (null == erCodeContext) return Object.class; - return expressionVisitor.visit(c.erCode()).getType(); - }) - .collect(Collectors.toSet()); - List> filteredErCodeTypes = - erCodeTypes.stream().filter(t -> !t.equals(Object.class)).collect(Collectors.toList()); - if (filteredErCodeTypes.size() > 1) { - throw new VtlRuntimeException( - new InvalidArgumentException("Error codes of rules have different types", pos)); - } - Class erCodeType = - filteredErCodeTypes.isEmpty() ? String.class : filteredErCodeTypes.iterator().next(); + Set> erCodeTypes = + ctx.ruleClauseHierarchical().ruleItemHierarchical().stream() + .map( + c -> { + VtlParser.ErCodeContext erCodeContext = c.erCode(); + if (null == erCodeContext) return Object.class; + return expressionVisitor.visit(c.erCode()).getType(); + }) + .collect(Collectors.toSet()); + List> filteredErCodeTypes = + erCodeTypes.stream().filter(t -> !t.equals(Object.class)).collect(Collectors.toList()); + if (filteredErCodeTypes.size() > 1) { + throw new VtlRuntimeException( + new InvalidArgumentException("Error codes of rules have different types", pos)); + } + Class erCodeType = + filteredErCodeTypes.isEmpty() ? String.class : filteredErCodeTypes.iterator().next(); - Set> erLevelTypes = - ctx.ruleClauseHierarchical().ruleItemHierarchical().stream() - .map( - c -> { - VtlParser.ErLevelContext erLevelContext = c.erLevel(); - if (null == erLevelContext) return Object.class; - return expressionVisitor.visit(c.erLevel()).getType(); - }) - .collect(Collectors.toSet()); - List> filteredErLevelTypes = - erLevelTypes.stream().filter(t -> !t.equals(Object.class)).collect(Collectors.toList()); - if (filteredErLevelTypes.size() > 1) { - throw new VtlRuntimeException( - new InvalidArgumentException("Error levels of rules have different types", pos)); - } - Class erLevelType = - filteredErLevelTypes.isEmpty() ? Long.class : filteredErLevelTypes.iterator().next(); + Set> erLevelTypes = + ctx.ruleClauseHierarchical().ruleItemHierarchical().stream() + .map( + c -> { + VtlParser.ErLevelContext erLevelContext = c.erLevel(); + if (null == erLevelContext) return Object.class; + return expressionVisitor.visit(c.erLevel()).getType(); + }) + .collect(Collectors.toSet()); + List> filteredErLevelTypes = + erLevelTypes.stream().filter(t -> !t.equals(Object.class)).collect(Collectors.toList()); + if (filteredErLevelTypes.size() > 1) { + throw new VtlRuntimeException( + new InvalidArgumentException("Error levels of rules have different types", pos)); + } + Class erLevelType = + filteredErLevelTypes.isEmpty() ? Long.class : filteredErLevelTypes.iterator().next(); - AtomicInteger index = new AtomicInteger(); - List rules = - ctx.ruleClauseHierarchical().ruleItemHierarchical().stream() - .map( - r -> { - TerminalNode identifier = r.IDENTIFIER(); - int i = index.getAndIncrement() + 1; - String ruleName = - null != identifier ? identifier.getText() : rulesetName + "_" + i; + AtomicInteger index = new AtomicInteger(); + List rules = + ctx.ruleClauseHierarchical().ruleItemHierarchical().stream() + .map( + r -> { + TerminalNode identifier = r.IDENTIFIER(); + int i = index.getAndIncrement() + 1; + String ruleName = + null != identifier ? identifier.getText() : rulesetName + "_" + i; - List codeItems = new ArrayList<>(); - VtlParser.CodeItemRelationContext codeItemRelationContext = r.codeItemRelation(); - String valueDomainValue = - codeItemRelationContext.valueDomainValue().IDENTIFIER().getText(); - codeItems.add(valueDomainValue); + List codeItems = new ArrayList<>(); + VtlParser.CodeItemRelationContext codeItemRelationContext = r.codeItemRelation(); + String valueDomainValue = + codeItemRelationContext.valueDomainValue().IDENTIFIER().getText(); + codeItems.add(valueDomainValue); - VtlParser.ComparisonOperandContext comparisonOperandContext = - codeItemRelationContext.comparisonOperand(); + VtlParser.ComparisonOperandContext comparisonOperandContext = + codeItemRelationContext.comparisonOperand(); - StringBuilder codeItemExpressionBuilder = new StringBuilder(); - codeItemRelationContext - .codeItemRelationClause() - .forEach( - circ -> { - TerminalNode minus = circ.MINUS(); - String rightCodeItem = circ.rightCodeItem.getText(); - codeItems.add(rightCodeItem); - if (minus != null) - codeItemExpressionBuilder.append(" -").append(rightCodeItem); - // plus value or plus null & minus null mean plus - codeItemExpressionBuilder.append(" +").append(rightCodeItem); - }); + StringBuilder codeItemExpressionBuilder = new StringBuilder(); + codeItemRelationContext + .codeItemRelationClause() + .forEach( + circ -> { + TerminalNode minus = circ.MINUS(); + String rightCodeItem = circ.rightCodeItem.getText(); + codeItems.add(rightCodeItem); + if (minus != null) + codeItemExpressionBuilder.append(" -").append(rightCodeItem); + // plus value or plus null & minus null mean plus + codeItemExpressionBuilder.append(" +").append(rightCodeItem); + }); - String rightExpressionToEval = codeItemExpressionBuilder.toString(); - String expressionToEval = - "bool_var := " - + valueDomainValue - + " " - + comparisonOperandContext.getText() - + " " - + rightExpressionToEval - + ";"; + String rightExpressionToEval = codeItemExpressionBuilder.toString(); + String expressionToEval = + "bool_var := " + + valueDomainValue + + " " + + comparisonOperandContext.getText() + + " " + + rightExpressionToEval + + ";"; - ResolvableExpression leftExpression = - ResolvableExpression.withType(Double.class) - .withPosition(pos) - .using( - context -> { - Bindings bindings = new SimpleBindings(context); - bindings.forEach( - (k, v) -> - engine - .getContext() - .setAttribute(k, v, ScriptContext.ENGINE_SCOPE)); - try { - engine.eval("left := " + valueDomainValue + ";"); - Object left = engine.getContext().getAttribute("left"); - engine - .getContext() - .removeAttribute("left", ScriptContext.ENGINE_SCOPE); - bindings - .keySet() - .forEach( - k -> - engine - .getContext() - .removeAttribute(k, ScriptContext.ENGINE_SCOPE)); - if (left.getClass().isAssignableFrom(Double.class)) { - return (Double) left; - } - return ((Long) left).doubleValue(); - } catch (ScriptException e) { - throw new VtlRuntimeException( - new VtlScriptException( - "right hierarchical rule has to return long or double", - pos)); - } - }); + ResolvableExpression leftExpression = + ResolvableExpression.withType(Double.class) + .withPosition(pos) + .using( + context -> { + Bindings bindings = new SimpleBindings(context); + bindings.forEach( + (k, v) -> + engine + .getContext() + .setAttribute(k, v, ScriptContext.ENGINE_SCOPE)); + try { + engine.eval("left := " + valueDomainValue + ";"); + Object left = engine.getContext().getAttribute("left"); + engine + .getContext() + .removeAttribute("left", ScriptContext.ENGINE_SCOPE); + bindings + .keySet() + .forEach( + k -> + engine + .getContext() + .removeAttribute(k, ScriptContext.ENGINE_SCOPE)); + if (left.getClass().isAssignableFrom(Double.class)) { + return (Double) left; + } + return ((Long) left).doubleValue(); + } catch (ScriptException e) { + throw new VtlRuntimeException( + new VtlScriptException( + "right hierarchical rule has to return long or double", + pos)); + } + }); - ResolvableExpression rightExpression = - ResolvableExpression.withType(Double.class) - .withPosition(pos) - .using( - context -> { - Bindings bindings = new SimpleBindings(context); - bindings.forEach( - (k, v) -> - engine - .getContext() - .setAttribute(k, v, ScriptContext.ENGINE_SCOPE)); - try { - engine.eval("right := " + rightExpressionToEval + ";"); - Object right = engine.getContext().getAttribute("right"); - engine - .getContext() - .removeAttribute("right", ScriptContext.ENGINE_SCOPE); - bindings - .keySet() - .forEach( - k -> - engine - .getContext() - .removeAttribute(k, ScriptContext.ENGINE_SCOPE)); - if (right.getClass().isAssignableFrom(Double.class)) { - return (Double) right; - } - return ((Long) right).doubleValue(); - } catch (ScriptException e) { - throw new VtlRuntimeException( - new VtlScriptException( - "right hierarchical rule has to return long or double", - pos)); - } - }); + ResolvableExpression rightExpression = + ResolvableExpression.withType(Double.class) + .withPosition(pos) + .using( + context -> { + Bindings bindings = new SimpleBindings(context); + bindings.forEach( + (k, v) -> + engine + .getContext() + .setAttribute(k, v, ScriptContext.ENGINE_SCOPE)); + try { + engine.eval("right := " + rightExpressionToEval + ";"); + Object right = engine.getContext().getAttribute("right"); + engine + .getContext() + .removeAttribute("right", ScriptContext.ENGINE_SCOPE); + bindings + .keySet() + .forEach( + k -> + engine + .getContext() + .removeAttribute(k, ScriptContext.ENGINE_SCOPE)); + if (right.getClass().isAssignableFrom(Double.class)) { + return (Double) right; + } + return ((Long) right).doubleValue(); + } catch (ScriptException e) { + throw new VtlRuntimeException( + new VtlScriptException( + "right hierarchical rule has to return long or double", + pos)); + } + }); - ResolvableExpression expression = - ResolvableExpression.withType(Boolean.class) - .withPosition(pos) - .using( - context -> { - Bindings bindings = new SimpleBindings(context); - bindings.forEach( - (k, v) -> - engine - .getContext() - .setAttribute(k, v, ScriptContext.ENGINE_SCOPE)); - try { - engine.eval(expressionToEval); - Boolean boolVar = - (Boolean) engine.getContext().getAttribute("bool_var"); - engine - .getContext() - .removeAttribute("bool_var", ScriptContext.ENGINE_SCOPE); - bindings - .keySet() - .forEach( - k -> - engine - .getContext() - .removeAttribute(k, ScriptContext.ENGINE_SCOPE)); - return boolVar; - } catch (ScriptException e) { - throw new VtlRuntimeException( - new VtlScriptException( - "hierarchical rule has to return boolean", pos)); - } - }); + ResolvableExpression expression = + ResolvableExpression.withType(Boolean.class) + .withPosition(pos) + .using( + context -> { + Bindings bindings = new SimpleBindings(context); + bindings.forEach( + (k, v) -> + engine + .getContext() + .setAttribute(k, v, ScriptContext.ENGINE_SCOPE)); + try { + engine.eval(expressionToEval); + Boolean boolVar = + (Boolean) engine.getContext().getAttribute("bool_var"); + engine + .getContext() + .removeAttribute("bool_var", ScriptContext.ENGINE_SCOPE); + bindings + .keySet() + .forEach( + k -> + engine + .getContext() + .removeAttribute(k, ScriptContext.ENGINE_SCOPE)); + return boolVar; + } catch (ScriptException e) { + throw new VtlRuntimeException( + new VtlScriptException( + "hierarchical rule has to return boolean", pos)); + } + }); - ResolvableExpression errorCodeExpression = - null != r.erCode() ? expressionVisitor.visit(r.erCode()) : null; - ResolvableExpression errorLevelExpression = - null != r.erLevel() ? expressionVisitor.visit(r.erLevel()) : null; - return new HierarchicalRule( - ruleName, - valueDomainValue, - expression, - leftExpression, - rightExpression, - codeItems, - errorCodeExpression, - errorLevelExpression); - }) - .collect(Collectors.toList()); - HierarchicalRuleset hr = new HierarchicalRuleset(rules, variable, erCodeType, erLevelType); - Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); - bindings.put(rulesetName, hr); - return hr; - } + ResolvableExpression errorCodeExpression = + null != r.erCode() ? expressionVisitor.visit(r.erCode()) : null; + ResolvableExpression errorLevelExpression = + null != r.erLevel() ? expressionVisitor.visit(r.erLevel()) : null; + return new HierarchicalRule( + ruleName, + valueDomainValue, + expression, + leftExpression, + rightExpression, + codeItems, + errorCodeExpression, + errorLevelExpression); + }) + .collect(Collectors.toList()); + HierarchicalRuleset hr = new HierarchicalRuleset(rules, variable, erCodeType, erLevelType); + Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); + bindings.put(rulesetName, hr); + return hr; + } } diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/ClauseVisitor.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/ClauseVisitor.java index 5654b216a..3fb9400e5 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/ClauseVisitor.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/ClauseVisitor.java @@ -5,8 +5,6 @@ import static fr.insee.vtl.engine.utils.TypeChecking.assertBasicScalarType; import static fr.insee.vtl.engine.utils.TypeChecking.assertNumber; -import fr.insee.trevas.antlr.shaded.v4.runtime.ParserRuleContext; -import fr.insee.trevas.antlr.shaded.v4.runtime.misc.Interval; import fr.insee.vtl.engine.VtlScriptEngine; import fr.insee.vtl.engine.exceptions.AlreadyDefinedException; import fr.insee.vtl.engine.exceptions.InvalidArgumentException; @@ -17,6 +15,8 @@ import fr.insee.vtl.model.exceptions.VtlScriptException; import fr.insee.vtl.parser.VtlBaseVisitor; import fr.insee.vtl.parser.VtlParser; +import fr.insee.vtl.parser.antlr4.runtime.ParserRuleContext; +import fr.insee.vtl.parser.antlr4.runtime.misc.Interval; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/DAGBuildingVisitor.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/DAGBuildingVisitor.java index 7472a31eb..b2f96e0b0 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/DAGBuildingVisitor.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/DAGBuildingVisitor.java @@ -1,12 +1,12 @@ package fr.insee.vtl.engine.visitors; -import fr.insee.trevas.antlr.shaded.v4.runtime.RuleContext; -import fr.insee.trevas.antlr.shaded.v4.runtime.Token; -import fr.insee.trevas.antlr.shaded.v4.runtime.tree.RuleNode; -import fr.insee.trevas.antlr.shaded.v4.runtime.tree.TerminalNode; import fr.insee.vtl.engine.utils.dag.DAGStatement; import fr.insee.vtl.parser.VtlBaseVisitor; import fr.insee.vtl.parser.VtlParser; +import fr.insee.vtl.parser.antlr4.runtime.RuleContext; +import fr.insee.vtl.parser.antlr4.runtime.Token; +import fr.insee.vtl.parser.antlr4.runtime.tree.RuleNode; +import fr.insee.vtl.parser.antlr4.runtime.tree.TerminalNode; import java.util.Collection; import java.util.List; import java.util.Optional; diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/ComparisonVisitor.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/ComparisonVisitor.java index fd217e705..f3e8a2e47 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/ComparisonVisitor.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/ComparisonVisitor.java @@ -2,8 +2,6 @@ import static fr.insee.vtl.engine.VtlScriptEngine.fromContext; -import fr.insee.trevas.antlr.shaded.v4.runtime.Token; -import fr.insee.trevas.antlr.shaded.v4.runtime.tree.TerminalNode; import fr.insee.vtl.engine.exceptions.ConflictingTypesException; import fr.insee.vtl.engine.exceptions.VtlRuntimeException; import fr.insee.vtl.engine.utils.TypeChecking; @@ -15,6 +13,8 @@ import fr.insee.vtl.model.exceptions.VtlScriptException; import fr.insee.vtl.parser.VtlBaseVisitor; import fr.insee.vtl.parser.VtlParser; +import fr.insee.vtl.parser.antlr4.runtime.Token; +import fr.insee.vtl.parser.antlr4.runtime.tree.TerminalNode; import java.util.*; import java.util.stream.Collectors; diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/functions/GenericFunctionsVisitor.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/functions/GenericFunctionsVisitor.java index ba4876988..e9a5dacaa 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/functions/GenericFunctionsVisitor.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/functions/GenericFunctionsVisitor.java @@ -2,8 +2,6 @@ import static fr.insee.vtl.engine.VtlScriptEngine.fromContext; -import fr.insee.trevas.antlr.shaded.v4.runtime.Token; -import fr.insee.trevas.antlr.shaded.v4.runtime.tree.TerminalNode; import fr.insee.vtl.engine.VtlScriptEngine; import fr.insee.vtl.engine.exceptions.FunctionNotFoundException; import fr.insee.vtl.engine.exceptions.InvalidArgumentException; @@ -22,6 +20,8 @@ import fr.insee.vtl.model.exceptions.VtlScriptException; import fr.insee.vtl.parser.VtlBaseVisitor; import fr.insee.vtl.parser.VtlParser; +import fr.insee.vtl.parser.antlr4.runtime.Token; +import fr.insee.vtl.parser.antlr4.runtime.tree.TerminalNode; import java.time.Instant; import java.util.HashMap; import java.util.HashSet; diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/functions/SetFunctionsVisitor.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/functions/SetFunctionsVisitor.java index ee85baa22..0c8523ffe 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/functions/SetFunctionsVisitor.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/functions/SetFunctionsVisitor.java @@ -3,7 +3,6 @@ import static fr.insee.vtl.engine.VtlScriptEngine.fromContext; import static fr.insee.vtl.engine.utils.TypeChecking.assertTypeExpression; -import fr.insee.trevas.antlr.shaded.v4.runtime.RuleContext; import fr.insee.vtl.engine.exceptions.InvalidArgumentException; import fr.insee.vtl.engine.exceptions.VtlRuntimeException; import fr.insee.vtl.engine.visitors.expression.ExpressionVisitor; @@ -14,6 +13,7 @@ import fr.insee.vtl.model.Structured; import fr.insee.vtl.parser.VtlBaseVisitor; import fr.insee.vtl.parser.VtlParser; +import fr.insee.vtl.parser.antlr4.runtime.RuleContext; import java.util.ArrayList; import java.util.List; import java.util.Objects; diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/functions/TimeFunctionsVisitor.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/functions/TimeFunctionsVisitor.java index e10d006cf..f1e6e78d0 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/functions/TimeFunctionsVisitor.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/functions/TimeFunctionsVisitor.java @@ -2,7 +2,6 @@ import static fr.insee.vtl.engine.VtlScriptEngine.fromContext; -import fr.insee.trevas.antlr.shaded.v4.runtime.tree.ParseTree; import fr.insee.vtl.engine.exceptions.InvalidArgumentException; import fr.insee.vtl.engine.exceptions.VtlRuntimeException; import fr.insee.vtl.engine.expressions.ComponentExpression; @@ -11,6 +10,7 @@ import fr.insee.vtl.model.exceptions.VtlScriptException; import fr.insee.vtl.parser.VtlBaseVisitor; import fr.insee.vtl.parser.VtlParser; +import fr.insee.vtl.parser.antlr4.runtime.tree.ParseTree; import java.time.Instant; import java.time.OffsetDateTime; import java.time.ZonedDateTime; diff --git a/vtl-engine/src/main/java/module-info.java b/vtl-engine/src/main/java/module-info.java index 969458409..7c938e2a0 100644 --- a/vtl-engine/src/main/java/module-info.java +++ b/vtl-engine/src/main/java/module-info.java @@ -1,38 +1,37 @@ import fr.insee.vtl.engine.VtlScriptEngineFactory; import fr.insee.vtl.engine.functions.LevenshteinProvider; +import fr.insee.vtl.engine.processors.InMemoryProcessingEngine; +import fr.insee.vtl.model.FunctionProvider; +import fr.insee.vtl.model.ProcessingEngine; +import fr.insee.vtl.model.ProcessingEngineFactory; +import javax.script.ScriptEngineFactory; /** This module contains the actual VTL engine. */ module fr.insee.vtl.engine { exports fr.insee.vtl.engine.exceptions; + exports fr.insee.vtl.engine.processors; requires transitive java.scripting; requires transitive fr.insee.vtl.parser; requires transitive fr.insee.vtl.model; + // TODO: Consider removing these. + requires org.apache.commons.lang3; + requires org.apache.commons.text; + requires safety.mirror; + requires org.threeten.extra; + requires org.jgrapht.core; + uses ProcessingEngine; uses ProcessingEngineFactory; uses FunctionProvider; - // exports fr.insee.vtl.engine.functions; provides FunctionProvider with LevenshteinProvider; - - exports fr.insee.vtl.engine.processors; - provides ProcessingEngineFactory with InMemoryProcessingEngine.Factory; - - opens fr.insee.vtl.engine; - - requires org.antlr.antlr4.runtime; - - // TODO: Consider removing these. - requires org.apache.commons.lang3; - requires org.apache.commons.text; - requires safety.mirror; - requires org.threeten.extra; - requires org.jgrapht.core; - provides ScriptEngineFactory with VtlScriptEngineFactory; + + opens fr.insee.vtl.engine; } diff --git a/vtl-engine/src/test/java/fr/insee/vtl/engine/utils/dag/DagDefineStatementsTest.java b/vtl-engine/src/test/java/fr/insee/vtl/engine/utils/dag/DagDefineStatementsTest.java index d8c96d5ab..a572c79b6 100644 --- a/vtl-engine/src/test/java/fr/insee/vtl/engine/utils/dag/DagDefineStatementsTest.java +++ b/vtl-engine/src/test/java/fr/insee/vtl/engine/utils/dag/DagDefineStatementsTest.java @@ -2,16 +2,16 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import fr.insee.trevas.antlr.shaded.v4.runtime.CharStreams; -import fr.insee.trevas.antlr.shaded.v4.runtime.CodePointCharStream; -import fr.insee.trevas.antlr.shaded.v4.runtime.CommonTokenStream; -import fr.insee.trevas.antlr.shaded.v4.runtime.tree.ParseTree; -import fr.insee.trevas.antlr.shaded.v4.runtime.tree.RuleNode; -import fr.insee.trevas.antlr.shaded.v4.runtime.tree.TerminalNode; import fr.insee.vtl.engine.VtlSyntaxPreprocessor; import fr.insee.vtl.model.exceptions.VtlScriptException; import fr.insee.vtl.parser.VtlLexer; import fr.insee.vtl.parser.VtlParser; +import fr.insee.vtl.parser.antlr4.runtime.CharStreams; +import fr.insee.vtl.parser.antlr4.runtime.CodePointCharStream; +import fr.insee.vtl.parser.antlr4.runtime.CommonTokenStream; +import fr.insee.vtl.parser.antlr4.runtime.tree.ParseTree; +import fr.insee.vtl.parser.antlr4.runtime.tree.RuleNode; +import fr.insee.vtl.parser.antlr4.runtime.tree.TerminalNode; import java.util.Set; import java.util.stream.Stream; import javax.script.ScriptException; diff --git a/vtl-parser/pom.xml b/vtl-parser/pom.xml index 661c255c5..24d508e94 100644 --- a/vtl-parser/pom.xml +++ b/vtl-parser/pom.xml @@ -52,23 +52,6 @@ - - org.apache.maven.plugins - maven-jar-plugin - 3.5.0 - - - early-jar - process-classes - - jar - - - pre-shade - - - - org.apache.maven.plugins maven-shade-plugin @@ -76,25 +59,18 @@ shade-antlr - process-classes + package shade - - false - - false - - org.antlr:antlr4-runtime - org.antlr.v4 @@ -106,26 +82,43 @@ - - org.apache.maven.plugins - maven-antrun-plugin - 3.1.0 + + org.moditect + moditect-maven-plugin + 1.2.2.Final - merge-shaded-jar-into-classes - process-classes + add-shaded-module-info + package - run + add-module-info - - - + true + ${project.build.directory} + + + ${project.build.directory}/${project.build.finalName}.jar + + fr.insee.vtl.parser + + fr.insee.vtl.parser; + fr.insee.vtl.parser.antlr4.runtime; + fr.insee.vtl.parser.antlr4.runtime.atn; + fr.insee.vtl.parser.antlr4.runtime.dfa; + fr.insee.vtl.parser.antlr4.runtime.misc; + fr.insee.vtl.parser.antlr4.runtime.tree; + fr.insee.vtl.parser.antlr4.runtime.tree.pattern; + fr.insee.vtl.parser.antlr4.runtime.tree.xpath; + + fr.insee.vtl.parser; + + + @@ -146,4 +139,4 @@ - \ No newline at end of file + diff --git a/vtl-parser/src/main/java/module-info.java b/vtl-parser/src/main/java/module-info.java index ac06c7379..e1e2f5fda 100644 --- a/vtl-parser/src/main/java/module-info.java +++ b/vtl-parser/src/main/java/module-info.java @@ -1,10 +1,8 @@ -/** - * This module exposes the constructs generated by Antlr from the VTL grammar files. - */ +/** This module exposes the constructs generated by Antlr from the VTL grammar files. */ module fr.insee.vtl.parser { - requires org.antlr.antlr4.runtime; + requires org.antlr.antlr4.runtime; - exports fr.insee.vtl.parser; + exports fr.insee.vtl.parser; - opens fr.insee.vtl.parser; + opens fr.insee.vtl.parser; } From 81f701c74aa3f1ad8fe18f85447e3c47fdda5595 Mon Sep 17 00:00:00 2001 From: Hadrien Kohl Date: Wed, 6 May 2026 19:26:34 +0200 Subject: [PATCH 09/16] Works --- vtl-parser/pom.xml | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/vtl-parser/pom.xml b/vtl-parser/pom.xml index 24d508e94..18c6b9228 100644 --- a/vtl-parser/pom.xml +++ b/vtl-parser/pom.xml @@ -52,6 +52,26 @@ + + + org.apache.maven.plugins + maven-jar-plugin + + + default-jar + none + + + pre-shade-jar + process-classes + + jar + + + + org.apache.maven.plugins maven-shade-plugin @@ -59,7 +79,7 @@ shade-antlr - package + process-classes shade @@ -93,7 +113,7 @@ add-shaded-module-info - package + process-classes add-module-info From 1f221fb58822443ac2a0a0b641d43ddb89f1b043 Mon Sep 17 00:00:00 2001 From: Nicolas Laval Date: Thu, 7 May 2026 20:56:58 +0200 Subject: [PATCH 10/16] Fix vtl-prov to manage spark 3/4 --- vtl-prov/pom.xml | 22 ++++++++++++++----- .../fr/insee/vtl/prov/ProvenanceListener.java | 6 ++--- .../main/java/fr/insee/vtl/prov/Variable.java | 6 ++--- .../insee/vtl/prov/VariableGraphListener.java | 2 +- .../fr/insee/vtl/prov/utils/AntlrUtils.java | 4 ++-- .../vtl/prov/VariableGraphListenerTest.java | 8 +++---- .../fr/insee/vtl/spark/SparkDatasetTest.java | 9 +++++++- .../fr/insee/vtl/spark4/SparkDatasetTest.java | 9 +++++++- 8 files changed, 46 insertions(+), 20 deletions(-) diff --git a/vtl-prov/pom.xml b/vtl-prov/pom.xml index 7b08e5534..b09eb93e4 100644 --- a/vtl-prov/pom.xml +++ b/vtl-prov/pom.xml @@ -22,6 +22,7 @@ vtl-spark + 2.15.2 @@ -41,11 +42,6 @@ vtl-engine 2.4.0-SNAPSHOT - - fr.insee.trevas - vtl-spark - 2.4.0-SNAPSHOT - fr.insee.trevas vtl-model @@ -65,6 +61,21 @@ 3.17.0 pom + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + @@ -82,6 +93,7 @@ spark4-tests vtl-spark4 + 2.18.2 diff --git a/vtl-prov/src/main/java/fr/insee/vtl/prov/ProvenanceListener.java b/vtl-prov/src/main/java/fr/insee/vtl/prov/ProvenanceListener.java index fb62a1520..5cf7f08e9 100644 --- a/vtl-prov/src/main/java/fr/insee/vtl/prov/ProvenanceListener.java +++ b/vtl-prov/src/main/java/fr/insee/vtl/prov/ProvenanceListener.java @@ -1,12 +1,12 @@ package fr.insee.vtl.prov; -import fr.insee.trevas.antlr.shaded.v4.runtime.*; -import fr.insee.trevas.antlr.shaded.v4.runtime.misc.Interval; -import fr.insee.trevas.antlr.shaded.v4.runtime.tree.ParseTreeWalker; import fr.insee.vtl.model.Dataset; import fr.insee.vtl.parser.VtlBaseListener; import fr.insee.vtl.parser.VtlLexer; import fr.insee.vtl.parser.VtlParser; +import fr.insee.vtl.parser.antlr4.runtime.*; +import fr.insee.vtl.parser.antlr4.runtime.misc.Interval; +import fr.insee.vtl.parser.antlr4.runtime.tree.ParseTreeWalker; import fr.insee.vtl.prov.prov.DataframeInstance; import fr.insee.vtl.prov.prov.Program; import fr.insee.vtl.prov.prov.ProgramStep; diff --git a/vtl-prov/src/main/java/fr/insee/vtl/prov/Variable.java b/vtl-prov/src/main/java/fr/insee/vtl/prov/Variable.java index 30f4f06a9..9982d377b 100644 --- a/vtl-prov/src/main/java/fr/insee/vtl/prov/Variable.java +++ b/vtl-prov/src/main/java/fr/insee/vtl/prov/Variable.java @@ -1,8 +1,8 @@ package fr.insee.vtl.prov; -import fr.insee.trevas.antlr.shaded.v4.runtime.ParserRuleContext; -import fr.insee.trevas.antlr.shaded.v4.runtime.Token; -import fr.insee.trevas.antlr.shaded.v4.runtime.misc.Interval; +import fr.insee.vtl.parser.antlr4.runtime.ParserRuleContext; +import fr.insee.vtl.parser.antlr4.runtime.Token; +import fr.insee.vtl.parser.antlr4.runtime.misc.Interval; import java.util.Optional; public class Variable { diff --git a/vtl-prov/src/main/java/fr/insee/vtl/prov/VariableGraphListener.java b/vtl-prov/src/main/java/fr/insee/vtl/prov/VariableGraphListener.java index ffc04ebdd..4ae148649 100644 --- a/vtl-prov/src/main/java/fr/insee/vtl/prov/VariableGraphListener.java +++ b/vtl-prov/src/main/java/fr/insee/vtl/prov/VariableGraphListener.java @@ -1,8 +1,8 @@ package fr.insee.vtl.prov; -import fr.insee.trevas.antlr.shaded.v4.runtime.ParserRuleContext; import fr.insee.vtl.parser.VtlBaseListener; import fr.insee.vtl.parser.VtlParser; +import fr.insee.vtl.parser.antlr4.runtime.ParserRuleContext; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; diff --git a/vtl-prov/src/main/java/fr/insee/vtl/prov/utils/AntlrUtils.java b/vtl-prov/src/main/java/fr/insee/vtl/prov/utils/AntlrUtils.java index 5217f230f..6a0199e3a 100644 --- a/vtl-prov/src/main/java/fr/insee/vtl/prov/utils/AntlrUtils.java +++ b/vtl-prov/src/main/java/fr/insee/vtl/prov/utils/AntlrUtils.java @@ -1,9 +1,9 @@ package fr.insee.vtl.prov.utils; -import fr.insee.trevas.antlr.shaded.v4.runtime.*; -import fr.insee.trevas.antlr.shaded.v4.runtime.misc.Interval; import fr.insee.vtl.parser.VtlLexer; import fr.insee.vtl.parser.VtlParser; +import fr.insee.vtl.parser.antlr4.runtime.*; +import fr.insee.vtl.parser.antlr4.runtime.misc.Interval; import java.util.HashMap; import java.util.Map; diff --git a/vtl-prov/src/test/java/fr/insee/vtl/prov/VariableGraphListenerTest.java b/vtl-prov/src/test/java/fr/insee/vtl/prov/VariableGraphListenerTest.java index 0b72860a9..c6aff3329 100644 --- a/vtl-prov/src/test/java/fr/insee/vtl/prov/VariableGraphListenerTest.java +++ b/vtl-prov/src/test/java/fr/insee/vtl/prov/VariableGraphListenerTest.java @@ -2,12 +2,12 @@ import static org.assertj.core.api.Assertions.assertThat; -import fr.insee.trevas.antlr.shaded.v4.runtime.CharStreams; -import fr.insee.trevas.antlr.shaded.v4.runtime.CodePointCharStream; -import fr.insee.trevas.antlr.shaded.v4.runtime.CommonTokenStream; -import fr.insee.trevas.antlr.shaded.v4.runtime.tree.ParseTreeWalker; import fr.insee.vtl.parser.VtlLexer; import fr.insee.vtl.parser.VtlParser; +import fr.insee.vtl.parser.antlr4.runtime.CharStreams; +import fr.insee.vtl.parser.antlr4.runtime.CodePointCharStream; +import fr.insee.vtl.parser.antlr4.runtime.CommonTokenStream; +import fr.insee.vtl.parser.antlr4.runtime.tree.ParseTreeWalker; import java.util.Set; import org.jgrapht.Graph; import org.jgrapht.graph.DefaultDirectedGraph; diff --git a/vtl-spark/src/test/java/fr/insee/vtl/spark/SparkDatasetTest.java b/vtl-spark/src/test/java/fr/insee/vtl/spark/SparkDatasetTest.java index d0e97f7dd..fdd105926 100644 --- a/vtl-spark/src/test/java/fr/insee/vtl/spark/SparkDatasetTest.java +++ b/vtl-spark/src/test/java/fr/insee/vtl/spark/SparkDatasetTest.java @@ -29,7 +29,14 @@ public class SparkDatasetTest { @BeforeEach public void setUp() { - spark = SparkSession.builder().appName("test").master("local").getOrCreate(); + spark = + SparkSession.builder() + .appName("test") + .master("local") + .config("spark.driver.bindAddress", "127.0.0.1") + .config("spark.driver.host", "127.0.0.1") + .config("spark.ui.enabled", "false") + .getOrCreate(); ScriptEngineManager mgr = new ScriptEngineManager(); engine = mgr.getEngineByExtension("vtl"); diff --git a/vtl-spark4/src/test/java/fr/insee/vtl/spark4/SparkDatasetTest.java b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/SparkDatasetTest.java index 12f086485..ffe9f7f35 100644 --- a/vtl-spark4/src/test/java/fr/insee/vtl/spark4/SparkDatasetTest.java +++ b/vtl-spark4/src/test/java/fr/insee/vtl/spark4/SparkDatasetTest.java @@ -30,7 +30,14 @@ public class SparkDatasetTest { @BeforeEach public void setUp() { - spark = SparkSession.builder().appName("test").master("local").getOrCreate(); + spark = + SparkSession.builder() + .appName("test") + .master("local") + .config("spark.driver.bindAddress", "127.0.0.1") + .config("spark.driver.host", "127.0.0.1") + .config("spark.ui.enabled", "false") + .getOrCreate(); ScriptEngineManager mgr = new ScriptEngineManager(); engine = mgr.getEngineByExtension("vtl"); From 795c22ef1949b37d4ba6cfeac012be8b4fe8b429 Mon Sep 17 00:00:00 2001 From: Nicolas Laval Date: Fri, 8 May 2026 08:53:12 +0200 Subject: [PATCH 11/16] Fix vtl-prov tests with spark profile --- vtl-prov/pom.xml | 5 +++++ .../fr/insee/vtl/prov/ProvenanceListenerTest.java | 2 +- .../src/test/java/fr/insee/vtl/prov/RDFTest.java | 2 +- .../fr/insee/vtl/prov/TestSparkEngineConfig.java | 13 +++++++++++++ 4 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 vtl-prov/src/test/java/fr/insee/vtl/prov/TestSparkEngineConfig.java diff --git a/vtl-prov/pom.xml b/vtl-prov/pom.xml index b09eb93e4..3572ea26e 100644 --- a/vtl-prov/pom.xml +++ b/vtl-prov/pom.xml @@ -22,6 +22,7 @@ vtl-spark + spark 2.15.2 @@ -93,6 +94,7 @@ spark4-tests vtl-spark4 + spark4 2.18.2 @@ -106,6 +108,9 @@ 3.5.4 --add-exports java.base/sun.nio.ch=ALL-UNNAMED + + ${trevas.spark.processing.engine} + diff --git a/vtl-prov/src/test/java/fr/insee/vtl/prov/ProvenanceListenerTest.java b/vtl-prov/src/test/java/fr/insee/vtl/prov/ProvenanceListenerTest.java index fb2f07326..f48674756 100644 --- a/vtl-prov/src/test/java/fr/insee/vtl/prov/ProvenanceListenerTest.java +++ b/vtl-prov/src/test/java/fr/insee/vtl/prov/ProvenanceListenerTest.java @@ -30,7 +30,7 @@ public void setUp() { ScriptEngineManager mgr = new ScriptEngineManager(); engine = mgr.getEngineByExtension("vtl"); - engine.put(VtlScriptEngine.PROCESSING_ENGINE_NAMES, "spark"); + engine.put(VtlScriptEngine.PROCESSING_ENGINE_NAMES, TestSparkEngineConfig.getEngineName()); engine.put("$vtl.spark.session", spark); } diff --git a/vtl-prov/src/test/java/fr/insee/vtl/prov/RDFTest.java b/vtl-prov/src/test/java/fr/insee/vtl/prov/RDFTest.java index 8d5870e1d..a36eae0ed 100644 --- a/vtl-prov/src/test/java/fr/insee/vtl/prov/RDFTest.java +++ b/vtl-prov/src/test/java/fr/insee/vtl/prov/RDFTest.java @@ -65,7 +65,7 @@ void loadBlueprintConfig() { ScriptEngineManager mgr = new ScriptEngineManager(); engine = mgr.getEngineByExtension("vtl"); - engine.put(VtlScriptEngine.PROCESSING_ENGINE_NAMES, "spark"); + engine.put(VtlScriptEngine.PROCESSING_ENGINE_NAMES, TestSparkEngineConfig.getEngineName()); engine.put("$vtl.spark.session", spark); } diff --git a/vtl-prov/src/test/java/fr/insee/vtl/prov/TestSparkEngineConfig.java b/vtl-prov/src/test/java/fr/insee/vtl/prov/TestSparkEngineConfig.java new file mode 100644 index 000000000..192c6234d --- /dev/null +++ b/vtl-prov/src/test/java/fr/insee/vtl/prov/TestSparkEngineConfig.java @@ -0,0 +1,13 @@ +package fr.insee.vtl.prov; + +final class TestSparkEngineConfig { + + private static final String ENGINE_PROPERTY = "trevas.spark.processing.engine"; + private static final String DEFAULT_ENGINE = "spark"; + + private TestSparkEngineConfig() {} + + static String getEngineName() { + return System.getProperty(ENGINE_PROPERTY, DEFAULT_ENGINE); + } +} From c2223891779c65500eb6afbad1321234e6e58452 Mon Sep 17 00:00:00 2001 From: Nicolas Laval Date: Fri, 8 May 2026 08:57:40 +0200 Subject: [PATCH 12/16] Improve TCK messages with script --- .../java/fr/insee/vtl/coverage/TCKTest.java | 8 +++++- .../vtl/coverage/tck/TckFailureText.java | 11 +------- .../insee/vtl/coverage/tck/TckScriptText.java | 27 +++++++++++++++++++ 3 files changed, 35 insertions(+), 11 deletions(-) create mode 100644 coverage/src/test/java/fr/insee/vtl/coverage/tck/TckScriptText.java diff --git a/coverage/src/test/java/fr/insee/vtl/coverage/TCKTest.java b/coverage/src/test/java/fr/insee/vtl/coverage/TCKTest.java index 118c86e3a..3bcc1b012 100644 --- a/coverage/src/test/java/fr/insee/vtl/coverage/TCKTest.java +++ b/coverage/src/test/java/fr/insee/vtl/coverage/TCKTest.java @@ -5,6 +5,7 @@ import fr.insee.vtl.coverage.tck.TckCaseExecutor; import fr.insee.vtl.coverage.tck.TckFolders; import fr.insee.vtl.coverage.tck.TckLeafCase; +import fr.insee.vtl.coverage.tck.TckScriptText; import fr.insee.vtl.coverage.tck.TckSparkScriptEngines; import java.io.InputStream; import java.util.List; @@ -52,6 +53,7 @@ void tckCase(TckCase c) throws Exception { private static void logCaseOutcome(TckCase c, boolean success) { System.out.println((success ? "✅" : "❌") + " Test " + c.index()); + System.out.println("\tVTL script: " + c.scriptSummary()); System.out.println("\t" + c.displayPath()); } @@ -74,7 +76,11 @@ static Stream leafCases() throws Exception { private record TckCase(int index, String displayPath, Test payload) { String label() { - return "Test " + index + " — " + displayPath; + return "Test " + index + " — VTL script: " + scriptSummary(); + } + + String scriptSummary() { + return TckScriptText.summary(payload.getScript(), 120); } } } diff --git a/coverage/src/test/java/fr/insee/vtl/coverage/tck/TckFailureText.java b/coverage/src/test/java/fr/insee/vtl/coverage/tck/TckFailureText.java index 9ee748ac7..bdadb5e74 100644 --- a/coverage/src/test/java/fr/insee/vtl/coverage/tck/TckFailureText.java +++ b/coverage/src/test/java/fr/insee/vtl/coverage/tck/TckFailureText.java @@ -45,16 +45,7 @@ public static String rowDataMismatch( } private static void appendScript(StringBuilder sb, String script) { - sb.append("--- transformation.vtl ---").append(System.lineSeparator()); - if (script == null) { - sb.append("(null)"); - return; - } - String s = - script.length() > MAX_SCRIPT_CHARS - ? script.substring(0, MAX_SCRIPT_CHARS) + "\n… (truncated)" - : script; - sb.append(s); + TckScriptText.appendFull(sb, script, MAX_SCRIPT_CHARS); } private static void appendInputs(StringBuilder sb, Map inputs) { diff --git a/coverage/src/test/java/fr/insee/vtl/coverage/tck/TckScriptText.java b/coverage/src/test/java/fr/insee/vtl/coverage/tck/TckScriptText.java new file mode 100644 index 000000000..31aed3d5d --- /dev/null +++ b/coverage/src/test/java/fr/insee/vtl/coverage/tck/TckScriptText.java @@ -0,0 +1,27 @@ +package fr.insee.vtl.coverage.tck; + +/** Shared formatting for VTL script text in TCK outputs. */ +public final class TckScriptText { + + private TckScriptText() {} + + public static String summary(String script, int maxChars) { + if (script == null || script.isBlank()) { + return "(empty)"; + } + String oneLine = script.replace('\n', ' ').replace('\r', ' ').trim().replaceAll("\\s+", " "); + return oneLine.length() > maxChars ? oneLine.substring(0, maxChars) + "…" : oneLine; + } + + public static void appendFull(StringBuilder sb, String script, int maxChars) { + sb.append("VTL script: ").append(summary(script, 120)).append(System.lineSeparator()); + sb.append("--- transformation.vtl ---").append(System.lineSeparator()); + if (script == null) { + sb.append("(null)"); + return; + } + String truncated = + script.length() > maxChars ? script.substring(0, maxChars) + "\n… (truncated)" : script; + sb.append(truncated); + } +} From 4292274aea7730ebdc1c7fb3fcd8cbb15ae433f0 Mon Sep 17 00:00:00 2001 From: Nicolas Laval Date: Fri, 8 May 2026 09:44:30 +0200 Subject: [PATCH 13/16] Refine ANTLR shading for vtl-prov --- vtl-parser/pom.xml | 4 ++++ vtl-prov/pom.xml | 4 ++++ .../insee/vtl/prov/ProvenanceListenerTest.java | 2 +- .../test/java/fr/insee/vtl/prov/RDFTest.java | 2 +- .../vtl/prov/TestSparkSessionFactory.java | 18 ++++++++++++++++++ 5 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 vtl-prov/src/test/java/fr/insee/vtl/prov/TestSparkSessionFactory.java diff --git a/vtl-parser/pom.xml b/vtl-parser/pom.xml index 18c6b9228..cfe6b5699 100644 --- a/vtl-parser/pom.xml +++ b/vtl-parser/pom.xml @@ -29,6 +29,9 @@ antlr4-runtime ${antlr4.version} compile + + true @@ -58,6 +61,7 @@ overwrite the shaded jar at the package phase. --> org.apache.maven.plugins maven-jar-plugin + 3.4.2 default-jar diff --git a/vtl-prov/pom.xml b/vtl-prov/pom.xml index 3572ea26e..1bb88721e 100644 --- a/vtl-prov/pom.xml +++ b/vtl-prov/pom.xml @@ -108,6 +108,10 @@ 3.5.4 --add-exports java.base/sun.nio.ch=ALL-UNNAMED + + test + test + ${trevas.spark.processing.engine} diff --git a/vtl-prov/src/test/java/fr/insee/vtl/prov/ProvenanceListenerTest.java b/vtl-prov/src/test/java/fr/insee/vtl/prov/ProvenanceListenerTest.java index f48674756..546615bd7 100644 --- a/vtl-prov/src/test/java/fr/insee/vtl/prov/ProvenanceListenerTest.java +++ b/vtl-prov/src/test/java/fr/insee/vtl/prov/ProvenanceListenerTest.java @@ -26,7 +26,7 @@ public class ProvenanceListenerTest { @BeforeEach public void setUp() { - SparkSession spark = SparkSession.builder().appName("test").master("local").getOrCreate(); + SparkSession spark = TestSparkSessionFactory.create(); ScriptEngineManager mgr = new ScriptEngineManager(); engine = mgr.getEngineByExtension("vtl"); diff --git a/vtl-prov/src/test/java/fr/insee/vtl/prov/RDFTest.java b/vtl-prov/src/test/java/fr/insee/vtl/prov/RDFTest.java index a36eae0ed..00a2fced8 100644 --- a/vtl-prov/src/test/java/fr/insee/vtl/prov/RDFTest.java +++ b/vtl-prov/src/test/java/fr/insee/vtl/prov/RDFTest.java @@ -61,7 +61,7 @@ void loadBlueprintConfig() { RDFUtils.loadModelWithCredentials( model, sparqlEndpoint, sparqlEndpointUser, sparlqEndpointPassword); - spark = SparkSession.builder().appName("test").master("local").getOrCreate(); + spark = TestSparkSessionFactory.create(); ScriptEngineManager mgr = new ScriptEngineManager(); engine = mgr.getEngineByExtension("vtl"); diff --git a/vtl-prov/src/test/java/fr/insee/vtl/prov/TestSparkSessionFactory.java b/vtl-prov/src/test/java/fr/insee/vtl/prov/TestSparkSessionFactory.java new file mode 100644 index 000000000..d725b0581 --- /dev/null +++ b/vtl-prov/src/test/java/fr/insee/vtl/prov/TestSparkSessionFactory.java @@ -0,0 +1,18 @@ +package fr.insee.vtl.prov; + +import org.apache.spark.sql.SparkSession; + +final class TestSparkSessionFactory { + + private TestSparkSessionFactory() {} + + static SparkSession create() { + return SparkSession.builder() + .appName("test") + .master("local") + .config("spark.driver.bindAddress", "127.0.0.1") + .config("spark.driver.host", "127.0.0.1") + .config("spark.ui.enabled", "false") + .getOrCreate(); + } +} From 35c853103d20b6122c11022ad7006c3be8c45dd8 Mon Sep 17 00:00:00 2001 From: Nicolas Laval Date: Mon, 11 May 2026 12:40:04 +0200 Subject: [PATCH 14/16] feat: improve TCK reporting (#483) * Handle encoding in TCK output * Improve TckScriptText for encoding * Improve TCK output * Try to produce MD report for TCK * Improve CI actions * Try to improve TCK output * Update TCKTest * Try to retrieve all VTL scripts in TCK report * Improve render_tck_job_summary script * Try to retrieve TCK scripts in report * Update render_tck_job_summary * Try to fix TCK output * Remove duplication in TCK report * Update render_tck_job_summary script * Update render_tck_job_summary * Configure dorny action * Prettify TCK report * Fix to handle nested errors in TCK surefire report * Refine render_tck_job_summary script --- .github/workflows/ci.yml | 36 +- .github/workflows/docs.yml | 6 +- .github/workflows/tck-vtl-tf.yml | 27 +- .gitignore | 4 +- coverage/scripts/render_tck_job_summary.py | 493 ++++++++++++++++++ .../java/fr/insee/vtl/coverage/TCKTest.java | 6 +- .../insee/vtl/coverage/tck/TckScriptText.java | 45 +- 7 files changed, 584 insertions(+), 33 deletions(-) create mode 100644 coverage/scripts/render_tck_job_summary.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 65cbf38c3..3816f720a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,16 +10,16 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up JDK 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: "temurin" java-version: 17 - name: Cache Maven dependencies - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} @@ -32,11 +32,11 @@ jobs: runs-on: ubuntu-latest needs: format steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 0 - name: Set up Maven Central Repository - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 17 distribution: "adopt" @@ -53,15 +53,15 @@ jobs: runs-on: ubuntu-latest needs: format steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 0 - name: Set up Maven Central Repository - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 17 distribution: "adopt" - - uses: s4u/maven-settings-action@v3.0.0 + - uses: s4u/maven-settings-action@v4.0.0 with: githubServer: false servers: | @@ -91,22 +91,22 @@ jobs: spark-label: "vtl-prov + Spark 4 (tests)" extra-args: "-Pspark4-tests" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 0 - name: Set up JDK 17 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 17 distribution: temurin - name: Cache Maven dependencies - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} restore-keys: | ${{ runner.os }}-maven- - - uses: s4u/maven-settings-action@v3.0.0 + - uses: s4u/maven-settings-action@v4.0.0 with: githubServer: false servers: | @@ -126,15 +126,15 @@ jobs: runs-on: ubuntu-latest needs: [ test, spark-integration ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 0 - name: Set up Maven Central Repository - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 17 distribution: "adopt" - - uses: s4u/maven-settings-action@v3.0.0 + - uses: s4u/maven-settings-action@v4.0.0 with: githubServer: false servers: | @@ -185,13 +185,13 @@ jobs: needs: [ test, spark-integration ] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Import GPG Key - uses: crazy-max/ghaction-import-gpg@v1 + uses: crazy-max/ghaction-import-gpg@v7 env: GPG_PRIVATE_KEY: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} - - uses: s4u/maven-settings-action@v3.0.0 + - uses: s4u/maven-settings-action@v4.0.0 with: githubServer: false servers: | diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 26b09d597..cff15e0f8 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -10,8 +10,8 @@ jobs: if: github.repository == 'inseefr/trevas' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@v6 + - uses: actions/setup-node@v5 with: node-version: 24 - name: Install dependencies @@ -21,7 +21,7 @@ jobs: run: yarn build working-directory: docs - name: Deploy to GitHub Pages - uses: peaceiris/actions-gh-pages@v3 + uses: peaceiris/actions-gh-pages@v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} # Build output to publish to the `gh-pages` branch: diff --git a/.github/workflows/tck-vtl-tf.yml b/.github/workflows/tck-vtl-tf.yml index c740d5a48..6afe70322 100644 --- a/.github/workflows/tck-vtl-tf.yml +++ b/.github/workflows/tck-vtl-tf.yml @@ -12,7 +12,7 @@ jobs: steps: - name: Checkout main project - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 @@ -20,7 +20,7 @@ jobs: run: git clone --branch fix/tck-2.1 https://github.com/sdmx-twg/vtl.git - name: Install Python 3 - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: '3.11' @@ -34,12 +34,12 @@ jobs: mv vtl/tck/v2.1.zip coverage/src/main/resources/ - name: Set up Java - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: '17' - - uses: s4u/maven-settings-action@v3.0.0 + - uses: s4u/maven-settings-action@v4.0.0 with: githubServer: false servers: | @@ -50,7 +50,7 @@ jobs: }] - name: Cache Maven packages - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} @@ -67,11 +67,26 @@ jobs: if: always() run: python3 coverage/scripts/prettify_tck_surefire_xml.py + # only-summary: table per XML only (no per-test expansion). max-annotations: 0 avoids PR file annotations. - name: Publish JUnit test results - uses: dorny/test-reporter@v2 + uses: dorny/test-reporter@v3 if: always() with: name: JUnit Test Report path: coverage/target/surefire-reports/*.xml reporter: java-junit + only-summary: 'true' + max-annotations: 0 fail-on-error: 'false' + + - name: Generate TCK scripts report + if: always() + run: python3 coverage/scripts/render_tck_job_summary.py + + - name: Upload full TCK script report artifact + if: always() + uses: actions/upload-artifact@v7 + with: + name: tck-scripts-report + path: coverage/target/tck-scripts-report.md + if-no-files-found: ignore diff --git a/.gitignore b/.gitignore index 87c2c4a52..9ee113d25 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,6 @@ target/ **/.settings/ **/pom.xml.versionsBackup -*-dev.properties \ No newline at end of file +*-dev.properties +**/__pycache__/ +**/*.pyc \ No newline at end of file diff --git a/coverage/scripts/render_tck_job_summary.py b/coverage/scripts/render_tck_job_summary.py new file mode 100644 index 000000000..fe7d30a71 --- /dev/null +++ b/coverage/scripts/render_tck_job_summary.py @@ -0,0 +1,493 @@ +#!/usr/bin/env python3 +""" +After `mvn test` (+ optional prettify), build a plain-text-friendly Markdown report: + + TCK scripts output + Source zip: … + Total cases: … + + Per test (same shape for pass / skip / fail): + ✅ Test N + folder » with » chevrons » ex_k + +