From 85fef69dd93330973e88c66b89612ff12de6ea12 Mon Sep 17 00:00:00 2001 From: gs-jp1 <80327721+gs-jp1@users.noreply.github.com> Date: Wed, 22 Nov 2023 16:29:54 +0000 Subject: [PATCH] Legend SQL - assortment of fixes (#2473) 1. do not possibly cast all function parameters by default 2. further alias adjustments 3. handle in filter using subbselect column 4. add greatest/least --- .../toPureGraph/handlers/Handlers.java | 11 +- .../core/legend/test/handlersTest.pure | 5 + .../corefunctions/collectionExtension.pure | 30 ++ .../pure/router/routing/router_routing.pure | 6 +- .../planConventions/collectionsLibrary.pure | 7 + .../sqlQueryToString/sybaseASEExtension.pure | 2 + .../sqlQueryToString/sybaseIQExtension.pure | 2 + .../tests/testSybaseIQWithFunction.pure | 17 ++ .../pureToSQLQuery/pureToSQLQuery.pure | 14 +- .../relational/relationalExtension.pure | 20 ++ .../sqlQueryToString/dbExtension.pure | 6 +- .../sqlQueryToString/extensionDefaults.pure | 11 + .../testSuite/dynaFunctions/misc.pure | 56 ++++ .../relational/tds/tests/testTDSFilter.pure | 14 + .../fromPure/tests/testToSQLString.pure | 14 + .../binding/fromPure/fromPure.pure | 282 ++++++++++++------ .../binding/fromPure/tests/testTranspile.pure | 81 +++-- 17 files changed, 451 insertions(+), 127 deletions(-) diff --git a/legend-engine-core/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/main/java/org/finos/legend/engine/language/pure/compiler/toPureGraph/handlers/Handlers.java b/legend-engine-core/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/main/java/org/finos/legend/engine/language/pure/compiler/toPureGraph/handlers/Handlers.java index bf51c66b5fd..a8dc180137d 100644 --- a/legend-engine-core/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/main/java/org/finos/legend/engine/language/pure/compiler/toPureGraph/handlers/Handlers.java +++ b/legend-engine-core/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/main/java/org/finos/legend/engine/language/pure/compiler/toPureGraph/handlers/Handlers.java @@ -506,6 +506,11 @@ public Handlers(PureModel pureModel) register(h("meta::pure::tds::concatenate_TabularDataSet_1__TabularDataSet_1__TabularDataSet_1_", false, ps -> res("meta::pure::tds::TabularDataSet", "one"), ps -> "TabularDataSet".equals(ps.get(0)._genericType()._rawType()._name())), h("meta::pure::functions::collection::concatenate_T_MANY__T_MANY__T_MANY_", true, ps -> res(ps.get(0)._genericType(), "zeroMany"), ps -> true)); + register(m(h("meta::pure::functions::collection::greatest_X_$1_MANY$__X_1_", false, ps -> res(ps.get(0)._genericType(), "one"), ps -> matchOneMany(ps.get(0)._multiplicity())), + h("meta::pure::functions::collection::greatest_X_MANY__X_$0_1$_", false, ps -> res(ps.get(0)._genericType(), "zeroOne"), ps -> true))); + + register(m(h("meta::pure::functions::collection::least_X_$1_MANY$__X_1_", false, ps -> res(ps.get(0)._genericType(), "one"), ps -> matchOneMany(ps.get(0)._multiplicity())), + h("meta::pure::functions::collection::least_X_MANY__X_$0_1$_", false, ps -> res(ps.get(0)._genericType(), "zeroOne"), ps -> true))); register("meta::pure::functions::collection::first_T_MANY__T_$0_1$_", true, ps -> res(ps.get(0)._genericType(), "zeroOne")); register("meta::pure::functions::collection::last_T_MANY__T_$0_1$_", true, ps -> res(ps.get(0)._genericType(), "zeroOne")); @@ -1872,6 +1877,10 @@ private Map buildDispatch() map.put("meta::pure::functions::date::min_Date_MANY__Date_$0_1$_", (List ps) -> ps.size() == 1 && Sets.immutable.with("Nil", "Date", "StrictDate", "DateTime", "LatestDate").contains(ps.get(0)._genericType()._rawType()._name())); map.put("meta::pure::functions::date::min_StrictDate_1__StrictDate_1__StrictDate_1_", (List ps) -> ps.size() == 2 && isOne(ps.get(0)._multiplicity()) && ("Nil".equals(ps.get(0)._genericType()._rawType()._name()) || "StrictDate".equals(ps.get(0)._genericType()._rawType()._name())) && isOne(ps.get(1)._multiplicity()) && ("Nil".equals(ps.get(1)._genericType()._rawType()._name()) || "StrictDate".equals(ps.get(1)._genericType()._rawType()._name()))); map.put("meta::pure::functions::date::min_StrictDate_MANY__StrictDate_$0_1$_", (List ps) -> ps.size() == 1 && ("Nil".equals(ps.get(0)._genericType()._rawType()._name()) || "StrictDate".equals(ps.get(0)._genericType()._rawType()._name()))); + map.put("meta::pure::functions::collection::greatest_X_$1_MANY$__X_1_", (List ps) -> ps.size() == 1 && matchOneMany(ps.get(0)._multiplicity())); + map.put("meta::pure::functions::collection::greatest_X_MANY__X_$0_1$_", (List ps) -> ps.size() == 1); + map.put("meta::pure::functions::collection::least_X_$1_MANY$__X_1_", (List ps) -> ps.size() == 1 && matchOneMany(ps.get(0)._multiplicity())); + map.put("meta::pure::functions::collection::least_X_MANY__X_$0_1$_", (List ps) -> ps.size() == 1); map.put("meta::pure::functions::date::minute_Date_1__Integer_1_", (List ps) -> ps.size() == 1 && isOne(ps.get(0)._multiplicity()) && Sets.immutable.with("Nil", "Date", "StrictDate", "DateTime", "LatestDate").contains(ps.get(0)._genericType()._rawType()._name())); map.put("meta::pure::functions::date::monthNumber_Date_$0_1$__Integer_$0_1$_", (List ps) -> ps.size() == 1 && matchZeroOne(ps.get(0)._multiplicity()) && Sets.immutable.with("Nil", "Date", "StrictDate", "DateTime", "LatestDate").contains(ps.get(0)._genericType()._rawType()._name())); map.put("meta::pure::functions::date::monthNumber_Date_1__Integer_1_", (List ps) -> ps.size() == 1 && isOne(ps.get(0)._multiplicity()) && Sets.immutable.with("Nil", "Date", "StrictDate", "DateTime", "LatestDate").contains(ps.get(0)._genericType()._rawType()._name())); @@ -1927,8 +1936,8 @@ private Map buildDispatch() map.put("meta::pure::functions::math::log_Number_1__Float_1_", (List ps) -> ps.size() == 1 && isOne(ps.get(0)._multiplicity()) && Sets.immutable.with("Nil", "Number", "Integer", "Float", "Decimal").contains(ps.get(0)._genericType()._rawType()._name())); map.put("meta::pure::functions::math::log10_Number_1__Float_1_", (List ps) -> ps.size() == 1 && isOne(ps.get(0)._multiplicity()) && Sets.immutable.with("Nil", "Number", "Integer", "Float", "Decimal").contains(ps.get(0)._genericType()._rawType()._name())); map.put("meta::pure::functions::math::max_Float_$1_MANY$__Float_1_", (List ps) -> ps.size() == 1 && matchOneMany(ps.get(0)._multiplicity()) && ("Nil".equals(ps.get(0)._genericType()._rawType()._name()) || "Float".equals(ps.get(0)._genericType()._rawType()._name()))); - map.put("meta::pure::functions::math::max_Float_1__Float_1__Float_1_", (List ps) -> ps.size() == 2 && isOne(ps.get(0)._multiplicity()) && ("Nil".equals(ps.get(0)._genericType()._rawType()._name()) || "Float".equals(ps.get(0)._genericType()._rawType()._name())) && isOne(ps.get(1)._multiplicity()) && ("Nil".equals(ps.get(1)._genericType()._rawType()._name()) || "Float".equals(ps.get(1)._genericType()._rawType()._name()))); map.put("meta::pure::functions::math::max_Float_MANY__Float_$0_1$_", (List ps) -> ps.size() == 1 && ("Nil".equals(ps.get(0)._genericType()._rawType()._name()) || "Float".equals(ps.get(0)._genericType()._rawType()._name()))); + map.put("meta::pure::functions::math::max_Float_1__Float_1__Float_1_", (List ps) -> ps.size() == 2 && isOne(ps.get(0)._multiplicity()) && ("Nil".equals(ps.get(0)._genericType()._rawType()._name()) || "Float".equals(ps.get(0)._genericType()._rawType()._name())) && isOne(ps.get(1)._multiplicity()) && ("Nil".equals(ps.get(1)._genericType()._rawType()._name()) || "Float".equals(ps.get(1)._genericType()._rawType()._name()))); map.put("meta::pure::functions::math::max_Integer_$1_MANY$__Integer_1_", (List ps) -> ps.size() == 1 && matchOneMany(ps.get(0)._multiplicity()) && ("Nil".equals(ps.get(0)._genericType()._rawType()._name()) || "Integer".equals(ps.get(0)._genericType()._rawType()._name()))); map.put("meta::pure::functions::math::max_Integer_1__Integer_1__Integer_1_", (List ps) -> ps.size() == 2 && isOne(ps.get(0)._multiplicity()) && ("Nil".equals(ps.get(0)._genericType()._rawType()._name()) || "Integer".equals(ps.get(0)._genericType()._rawType()._name())) && isOne(ps.get(1)._multiplicity()) && ("Nil".equals(ps.get(1)._genericType()._rawType()._name()) || "Integer".equals(ps.get(1)._genericType()._rawType()._name()))); map.put("meta::pure::functions::math::max_Integer_MANY__Integer_$0_1$_", (List ps) -> ps.size() == 1 && ("Nil".equals(ps.get(0)._genericType()._rawType()._name()) || "Integer".equals(ps.get(0)._genericType()._rawType()._name()))); diff --git a/legend-engine-pure/legend-engine-pure-code/legend-engine-pure-code-compiled-core/src/main/resources/core/legend/test/handlersTest.pure b/legend-engine-pure/legend-engine-pure-code/legend-engine-pure-code-compiled-core/src/main/resources/core/legend/test/handlersTest.pure index 09e3d2fbff2..145fab5829f 100644 --- a/legend-engine-pure/legend-engine-pure-code/legend-engine-pure-code-compiled-core/src/main/resources/core/legend/test/handlersTest.pure +++ b/legend-engine-pure/legend-engine-pure-code/legend-engine-pure-code-compiled-core/src/main/resources/core/legend/test/handlersTest.pure @@ -128,6 +128,11 @@ Class meta::legend::test::handlers::model::TestMaxMin minDateTime() {$this.dateTimes->min()}:DateTime[0..1]; minTwoDateTime() {$this.dateTime->min($this.dateTime)}:DateTime[1]; + + greatest() {$this.numbers->greatest()}:Number[0..1]; + greatestMinOne() {$this.numbersMinOne->greatest()}:Number[1]; + least() {$this.numbers->least()}:Number[0..1]; + leastMinOne() {$this.numbersMinOne->least()}:Number[1]; } Class meta::legend::test::handlers::model::TestAlgebra diff --git a/legend-engine-pure/legend-engine-pure-code/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/corefunctions/collectionExtension.pure b/legend-engine-pure/legend-engine-pure-code/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/corefunctions/collectionExtension.pure index b44c31ce858..eaeca1b26c2 100644 --- a/legend-engine-pure/legend-engine-pure-code/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/corefunctions/collectionExtension.pure +++ b/legend-engine-pure/legend-engine-pure-code/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/corefunctions/collectionExtension.pure @@ -177,6 +177,36 @@ function meta::pure::functions::collection::uniqueValueOnly(values : T[*], de ); } +function meta::pure::functions::collection::greatest(values : X[*]):X[0..1] +{ + max($values); +} + +function meta::pure::functions::collection::least(values : X[*]):X[0..1] +{ + min($values); +} + +function meta::pure::functions::collection::greatest(values : X[1..*]):X[1] +{ + max($values); +} + +function meta::pure::functions::collection::least(values : X[1..*]):X[1] +{ + min($values); +} + +function meta::pure::functions::collection::max(values : X[1..*]):X[1] +{ + $values->sort()->last()->toOne(); +} + +function meta::pure::functions::collection::min(values : X[1..*]):X[1] +{ + $values->sort()->first()->toOne(); +} + function meta::pure::functions::collection::max(values : X[*]):X[0..1] { $values->sort()->last(); diff --git a/legend-engine-pure/legend-engine-pure-code/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/router/routing/router_routing.pure b/legend-engine-pure/legend-engine-pure-code/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/router/routing/router_routing.pure index 9fb4a0a45b2..678f458d172 100644 --- a/legend-engine-pure/legend-engine-pure-code/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/router/routing/router_routing.pure +++ b/legend-engine-pure/legend-engine-pure-code/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/router/routing/router_routing.pure @@ -594,7 +594,7 @@ function meta::pure::router::routing::isGetAllFunction(f:Function[1]):Boole function meta::pure::router::routing::shouldStop(f:Function[1], extensions:meta::pure::extension::Extension[*]):Boolean[1] { - shouldStopFunctions($extensions)->contains($f); + shouldStopFunctions($extensions)->contains($f); } function meta::pure::router::routing::shouldStopFunctions(extensions:meta::pure::extension::Extension[*]):Function[*] @@ -620,6 +620,8 @@ function meta::pure::router::routing::shouldStopFunctions(extensions:meta::pure: max_Float_$1_MANY$__Float_1_, max_Number_MANY__Number_$0_1$_, max_Number_$1_MANY$__Number_1_, + greatest_X_MANY__X_$0_1$_, + greatest_X_$1_MANY$__X_1_, getAllForEachDate_Class_1__Date_MANY__T_MANY_, greaterThan_Number_1__Number_1__Boolean_1_, greaterThan_String_1__String_1__Boolean_1_, @@ -683,6 +685,8 @@ function meta::pure::router::routing::shouldStopFunctions(extensions:meta::pure: min_Date_MANY__Date_$0_1$_, min_StrictDate_MANY__StrictDate_$0_1$_, min_DateTime_MANY__DateTime_$0_1$_, + least_X_MANY__X_$0_1$_, + least_X_$1_MANY$__X_1_, variancePopulation_Number_MANY__Number_1_, varianceSample_Number_MANY__Number_1_, makeString_Any_MANY__String_1__String_1_, diff --git a/legend-engine-xts-java/legend-engine-xt-javaPlatformBinding-pure/src/main/resources/core_java_platform_binding/legendJavaPlatformBinding/planConventions/collectionsLibrary.pure b/legend-engine-xts-java/legend-engine-xt-javaPlatformBinding-pure/src/main/resources/core_java_platform_binding/legendJavaPlatformBinding/planConventions/collectionsLibrary.pure index 6cb0f936ce1..f625b1ab4cc 100644 --- a/legend-engine-xts-java/legend-engine-xt-javaPlatformBinding-pure/src/main/resources/core_java_platform_binding/legendJavaPlatformBinding/planConventions/collectionsLibrary.pure +++ b/legend-engine-xts-java/legend-engine-xt-javaPlatformBinding-pure/src/main/resources/core_java_platform_binding/legendJavaPlatformBinding/planConventions/collectionsLibrary.pure @@ -47,6 +47,13 @@ function meta::pure::executionPlan::platformBinding::legendJava::library::collec fc2(min_T_$1_MANY$__Function_1__T_1_, {ctx,collection,comp | if( $collection.type->isJavaList(), | minComp($ctx, $collection, $comp, $library), | $collection )}), fc1(max_X_MANY__X_$0_1$_, {ctx,collection | if( $collection.type->isJavaList(), | max($ctx, $collection, $library), | $collection )}), fc1(min_X_MANY__X_$0_1$_, {ctx,collection | if( $collection.type->isJavaList(), | min($ctx, $collection, $library), | $collection )}), + fc(max_X_$1_MANY$__X_1_, fcAlias( max_X_MANY__X_$0_1$_)), + fc(min_X_$1_MANY$__X_1_, fcAlias( min_X_MANY__X_$0_1$_)), + + fc(greatest_X_MANY__X_$0_1$_, fcAlias( max_X_MANY__X_$0_1$_)), + fc(least_X_MANY__X_$0_1$_, fcAlias( min_X_MANY__X_$0_1$_)), + fc(greatest_X_$1_MANY$__X_1_, fcAlias( max_X_MANY__X_$0_1$_)), + fc(least_X_$1_MANY$__X_1_, fcAlias( min_X_MANY__X_$0_1$_)), fc1(range_Integer_1__Integer_MANY_, {ctx,stop | javaLongStream()->j_invoke('range', [j_long(0), $stop])->j_invoke('boxed', [])}), fc2(range_Integer_1__Integer_1__Integer_MANY_, {ctx,start,stop | javaLongStream()->j_invoke('range', [$start, $stop])->j_invoke('boxed', [])}), diff --git a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-dbExtension/legend-engine-xt-relationalStore-sybase/legend-engine-xt-relationalStore-sybase-pure/src/main/resources/core_relational_sybase/relational/sqlQueryToString/sybaseASEExtension.pure b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-dbExtension/legend-engine-xt-relationalStore-sybase/legend-engine-xt-relationalStore-sybase-pure/src/main/resources/core_relational_sybase/relational/sqlQueryToString/sybaseASEExtension.pure index 1cb86366689..7e069c49a80 100644 --- a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-dbExtension/legend-engine-xt-relationalStore-sybase/legend-engine-xt-relationalStore-sybase-pure/src/main/resources/core_relational_sybase/relational/sqlQueryToString/sybaseASEExtension.pure +++ b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-dbExtension/legend-engine-xt-relationalStore-sybase/legend-engine-xt-relationalStore-sybase-pure/src/main/resources/core_relational_sybase/relational/sqlQueryToString/sybaseASEExtension.pure @@ -105,6 +105,7 @@ function meta::relational::functions::sqlQueryToString::sybaseASE::getDynaFuncti dynaFnToSql('firstDayOfThisYear', $allStates, ^ToSql(format='dateadd(DAY, -(datepart(dayofyear, today()) - 1), today())%s', transform={p:String[*] | ''})), dynaFnToSql('firstDayOfWeek', $allStates, ^ToSql(format='dateadd(DAY, -(mod(datepart(weekday, %s)+5, 7)), %s)', transform={p:String[1] | $p->repeat(2)})), dynaFnToSql('firstDayOfYear', $allStates, ^ToSql(format='dateadd(DAY, -(datepart(dayofyear, %s) - 1), %s)', transform={p:String[1] | $p->repeat(2)})), + dynaFnToSql('greatest', $allStates, ^ToSql(format='%s', transform={p:String[*] | convertGreatestLeastToCaseStatement('>=', $p)})), dynaFnToSql('hour', $allStates, ^ToSql(format='hour(%s)')), dynaFnToSql('indexOf', $allStates, ^ToSql(format='LOCATE(%s)', transform={p:String[2] | $p->at(0) + ', ' + $p->at(1)})), dynaFnToSql('isEmpty', $selectOutsideWhen, ^ToSql(format='case when (%s is null) then \'true\' else \'false\' end', parametersWithinWhenClause=true)), @@ -117,6 +118,7 @@ function meta::relational::functions::sqlQueryToString::sybaseASE::getDynaFuncti dynaFnToSql('isNull', $notSelectOutsideWhen, ^ToSql(format='%s is null')), dynaFnToSql('isNumeric', $allStates, ^ToSql(format='isnumeric(%s)')), dynaFnToSql('joinStrings', $allStates, ^ToSql(format='list(%s,%s)')), + dynaFnToSql('least', $allStates, ^ToSql(format='%s', transform={p:String[*] | convertGreatestLeastToCaseStatement('<=', $p)})), dynaFnToSql('left', $allStates, ^ToSql(format='left(%s,%s)')), dynaFnToSql('length', $allStates, ^ToSql(format='char_length(%s)')), dynaFnToSql('matches', $allStates, ^ToSql(format=regexpPattern('%s'), transform={p:String[2]|$p->transformRegexpParams()})), diff --git a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-dbExtension/legend-engine-xt-relationalStore-sybaseiq/legend-engine-xt-relationalStore-sybaseiq-pure/src/main/resources/core_relational_sybaseiq/relational/sqlQueryToString/sybaseIQExtension.pure b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-dbExtension/legend-engine-xt-relationalStore-sybaseiq/legend-engine-xt-relationalStore-sybaseiq-pure/src/main/resources/core_relational_sybaseiq/relational/sqlQueryToString/sybaseIQExtension.pure index a167de9a353..0f64b03218e 100644 --- a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-dbExtension/legend-engine-xt-relationalStore-sybaseiq/legend-engine-xt-relationalStore-sybaseiq-pure/src/main/resources/core_relational_sybaseiq/relational/sqlQueryToString/sybaseIQExtension.pure +++ b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-dbExtension/legend-engine-xt-relationalStore-sybaseiq/legend-engine-xt-relationalStore-sybaseiq-pure/src/main/resources/core_relational_sybaseiq/relational/sqlQueryToString/sybaseIQExtension.pure @@ -102,6 +102,7 @@ function meta::relational::functions::sqlQueryToString::sybaseIQ::getDynaFunctio dynaFnToSql('firstMillisecondOfSecond', $allStates, ^ToSql(format='dateadd(microsecond, -(datepart(microsecond, %s)), %s)', transform={p:String[1] | $p->repeat(2)})), dynaFnToSql('firstMinuteOfHour', $allStates, ^ToSql(format='dateadd(hour, datepart(hour, %s), date(%s))', transform={p:String[1] | $p->repeat(2)})), dynaFnToSql('firstSecondOfMinute', $allStates, ^ToSql(format='dateadd(minute, datepart(minute, %s), dateadd(hour, datepart(hour, %s), date(%s)))', transform={p:String[1] | $p->repeat(3)})), + dynaFnToSql('greatest', $allStates, ^ToSql(format='%s', transform={p:String[*] | convertGreatestLeastToCaseStatement('>=', $p)})), dynaFnToSql('hour', $allStates, ^ToSql(format='hour(%s)')), dynaFnToSql('indexOf', $allStates, ^ToSql(format='LOCATE(%s)', transform={p:String[2] | $p->at(0) + ', ' + $p->at(1)})), dynaFnToSql('isEmpty', $selectOutsideWhen, ^ToSql(format='case when (%s is null) then \'true\' else \'false\' end', parametersWithinWhenClause=true)), @@ -114,6 +115,7 @@ function meta::relational::functions::sqlQueryToString::sybaseIQ::getDynaFunctio dynaFnToSql('isNull', $notSelectOutsideWhen, ^ToSql(format='%s is null')), dynaFnToSql('isNumeric', $allStates, ^ToSql(format='isnumeric(%s)')), dynaFnToSql('joinStrings', $allStates, ^ToSql(format='list(%s,%s)')), + dynaFnToSql('least', $allStates, ^ToSql(format='%s', transform={p:String[*] | convertGreatestLeastToCaseStatement('<=', $p)})), dynaFnToSql('left', $allStates, ^ToSql(format='left(%s,%s)')), dynaFnToSql('length', $allStates, ^ToSql(format='char_length(%s)')), dynaFnToSql('matches', $allStates, ^ToSql(format=regexpPattern('%s'), transform={p:String[2]|$p->transformRegexpParams()})), diff --git a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-dbExtension/legend-engine-xt-relationalStore-sybaseiq/legend-engine-xt-relationalStore-sybaseiq-pure/src/main/resources/core_relational_sybaseiq/relational/sqlQueryToString/tests/testSybaseIQWithFunction.pure b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-dbExtension/legend-engine-xt-relationalStore-sybaseiq/legend-engine-xt-relationalStore-sybaseiq-pure/src/main/resources/core_relational_sybaseiq/relational/sqlQueryToString/tests/testSybaseIQWithFunction.pure index 61d826295ee..3d414336487 100644 --- a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-dbExtension/legend-engine-xt-relationalStore-sybaseiq/legend-engine-xt-relationalStore-sybaseiq-pure/src/main/resources/core_relational_sybaseiq/relational/sqlQueryToString/tests/testSybaseIQWithFunction.pure +++ b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-dbExtension/legend-engine-xt-relationalStore-sybaseiq/legend-engine-xt-relationalStore-sybaseiq-pure/src/main/resources/core_relational_sybaseiq/relational/sqlQueryToString/tests/testSybaseIQWithFunction.pure @@ -88,3 +88,20 @@ function <> meta::relational::tests::query::function::sybaseIQ::testD let s = toSQLString($fn, simpleRelationalMapping, meta::relational::runtime::DatabaseType.SybaseIQ, meta::relational::extension::relationalExtensions()); assertEquals('select datename(WEEKDAY,"root".tradeDate) as "WeekDay Name" from tradeTable as "root"',$s); } + +function <> meta::relational::tests::query::function::sybaseIQ::testGreatestLeast():Boolean[1] +{ + let fn = {|Trade.all() + ->project([ + col(t| greatest([$t.quantity, 1, 3]), 'greatest'), + col(t| greatest([]->cast(@String)), 'greatest_empty'), + col(t| least([$t.quantity, 1, 3]), 'least'), + col(t| least([]->cast(@String)), 'least_empty') + ])}; + + let s = toSQLString($fn, simpleRelationalMapping, meta::relational::runtime::DatabaseType.SybaseIQ, meta::relational::extension::relationalExtensions()); + assertEquals('select case when "root".quantity >= "root".quantity and "root".quantity >= 1 and "root".quantity >= 3 then "root".quantity when 1 >= "root".quantity and 1 >= 1 and 1 >= 3 then 1 when 3 >= "root".quantity and 3 >= 1 and 3 >= 3 then 3 else null end as "greatest", ' + + 'null as "greatest_empty", ' + + 'case when "root".quantity <= "root".quantity and "root".quantity <= 1 and "root".quantity <= 3 then "root".quantity when 1 <= "root".quantity and 1 <= 1 and 1 <= 3 then 1 when 3 <= "root".quantity and 3 <= 1 and 3 <= 3 then 3 else null end as "least", ' + + 'null as "least_empty" from tradeTable as "root"',$s); +} \ No newline at end of file diff --git a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/src/main/resources/core_relational/relational/pureToSQLQuery/pureToSQLQuery.pure b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/src/main/resources/core_relational/relational/pureToSQLQuery/pureToSQLQuery.pure index ce76a5a6195..1dd9c25367d 100644 --- a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/src/main/resources/core_relational/relational/pureToSQLQuery/pureToSQLQuery.pure +++ b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/src/main/resources/core_relational/relational/pureToSQLQuery/pureToSQLQuery.pure @@ -2669,6 +2669,7 @@ function meta::relational::functions::pureToSqlQuery::processTdsOlapOperation(o: function meta::relational::functions::pureToSqlQuery::processTdsFilter(f:FunctionExpression[1], currentPropertyMapping:PropertyMapping[*], operation:SelectWithCursor[1], vars:Map[1], state:State[1], joinType:JoinType[1], nodeId:String[1], aggFromMap:List[1], context:DebugContext[1], extensions:Extension[*]):RelationalOperationElement[1] { let mainQuery = processValueSpecification($f.parametersValues->at(0), $currentPropertyMapping, $operation, $vars, $state, $joinType, $nodeId, $aggFromMap, $context, $extensions)->toOne()->cast(@SelectWithCursor); + let mainSelect = $mainQuery.select; if($state.insertDriverTablePkInTempTable->isNotEmpty(), @@ -2677,6 +2678,7 @@ function meta::relational::functions::pureToSqlQuery::processTdsFilter(f:Functio let select = $res.select; let mainQueryFilter=$mainQuery.select.filteringOperation->concatenate($mainQuery.select.extraFilteringOperation)->andFilters($extensions); let mainQueryColumns = $mainQuery.select.columns; + let finalColumns = $mainQueryColumns->map(c| $c->match( [ @@ -4150,7 +4152,6 @@ function meta::relational::functions::pureToSqlQuery::processIn(f:FunctionExpres function meta::relational::functions::pureToSqlQuery::processIn(valueArg:ValueSpecification[1], collectionArg:ValueSpecification[1], currentPropertyMapping:PropertyMapping[*], operation:SelectWithCursor[1], vars:Map[1], state:State[1], nodeId:String[1], aggFromMap:List[1], context:DebugContext[1], extensions:Extension[*]):RelationalOperationElement[1] { let processedValueArg = $valueArg->processValueSpecificationReturnPropertyMapping($currentPropertyMapping, $operation, $vars, $state, JoinType.LEFT_OUTER, $nodeId, $aggFromMap, $context, $extensions)->toOne(); - let processedCollectionArg = $collectionArg->processValueSpecification($currentPropertyMapping, $operation, $vars, ^$state(shouldIsolate=false), JoinType.LEFT_OUTER, $nodeId, $aggFromMap, $context, $extensions)->cast(@SelectWithCursor); let singleProcessedCollectionArg = $processedCollectionArg->size() == 1; let reprocessedCollectionArg = if($singleProcessedCollectionArg, @@ -4162,6 +4163,8 @@ function meta::relational::functions::pureToSqlQuery::processIn(valueArg:ValueSp let firstSelect = $first.select; ^$first(select=if($state.inFilter, |^$firstSelect(filteringOperation=$list), |^$firstSelect(columns=$list)));); + + let mergedSQL = mergeSQLQueryData([$processedValueArg.element->cast(@SelectWithCursor), $reprocessedCollectionArg, $operation]->map(x|$x->extractSelectSQLQuery()), $nodeId, $state, $context, $extensions); let leftTable = if ($reprocessedCollectionArg.parent == [], |[],|$reprocessedCollectionArg.parent.alias.relationalElement); @@ -4170,7 +4173,8 @@ function meta::relational::functions::pureToSqlQuery::processIn(valueArg:ValueSp let isJoinToFilterTable = $state.inFilter && $leftTable != [] && $leftTable != $filterTable && $processedCollectionArg->at(0).select.filteringOperation->at(0)->instanceOf(TableAliasColumn); let valueFilter = if ($isJoinToFilterTable, | $reprocessedCollectionArg.select ,| $mergedSQL); - let value = if($state.inFilter,|$valueFilter.filteringOperation->filter(p|$p->instanceOf(TableAliasColumn) || $p->instanceOf(DynaFunction) || $p->instanceOf(JoinStrings) || $p->instanceOf(SemiStructuredArrayFlattenOutput) || $p->instanceOf(SemiStructuredObjectNavigation))->at(0),|$mergedSQL.columns->at(0)); + + let value = if($state.inFilter,|$valueFilter.filteringOperation->filter(p|$p->instanceOf(TableAliasColumn) || $p->instanceOf(ColumnName) || $p->instanceOf(DynaFunction) || $p->instanceOf(JoinStrings) || $p->instanceOf(SemiStructuredArrayFlattenOutput) || $p->instanceOf(SemiStructuredObjectNavigation))->at(0),|$mergedSQL.columns->at(0)); let collection = if($state.inFilter, |$mergedSQL.filteringOperation->filter(p|$p->instanceOf(Literal) || $p->instanceOf(LiteralList) || $p->instanceOf(FreeMarkerOperationHolder))->at(0),|$mergedSQL.columns->at(1)); let selectWithCursor = $value->extractSelectWithCursor($operation); @@ -4254,6 +4258,8 @@ function meta::relational::functions::pureToSqlQuery::processObjectReferenceIn(f let reProcessedleftSide = $objectReferenceInImp->processColumnsInRelationalOperationElements($state, $processedLeftSide, $nodeId, $aggFromMap, true, $context->shift(), $extensions); let processedCollectionArg = $f.parametersValues->at(1)->processValueSpecification($currentPropertyMapping, $operation, $vars, ^$state(shouldIsolate=false), JoinType.LEFT_OUTER, $nodeId, $aggFromMap, $context->shift(), $extensions)->cast(@SelectWithCursor); + + let processedCollection = if($state.inFilter, |$processedCollectionArg->map(s | $s.select.filteringOperation), |$processedCollectionArg->map(s | $s.select.columns)); assert($processedCollection->forAll(pc | $pc->instanceOf(Literal)), 'ObjectReferenceIn is supported only for Literal'); let isPostProcessingReq = !$processedCollection->forAll(pc | $pc->cast(@Literal).value->instanceOf(String) || $pc->cast(@Literal).value->instanceOf(SQLNull)); @@ -7617,6 +7623,10 @@ function meta::relational::functions::pureToSqlQuery::getSupportedFunctions():Ma ^PureFunctionToRelationalFunctionPair(first=meta::pure::functions::collection::first_T_MANY__T_$0_1$_, second=meta::relational::functions::pureToSqlQuery::processNoOp_FunctionExpression_1__PropertyMapping_MANY__SelectWithCursor_1__Map_1__State_1__JoinType_1__String_1__List_1__DebugContext_1__Extension_MANY__RelationalOperationElement_1_), ^PureFunctionToRelationalFunctionPair(first=meta::pure::functions::collection::concatenate_T_MANY__T_MANY__T_MANY_, second=meta::relational::functions::pureToSqlQuery::processConcatenate_FunctionExpression_1__PropertyMapping_MANY__SelectWithCursor_1__Map_1__State_1__JoinType_1__String_1__List_1__DebugContext_1__Extension_MANY__RelationalOperationElement_1_), ^PureFunctionToRelationalFunctionPair(first=meta::pure::functions::collection::union_T_MANY__T_MANY__T_MANY_, second=meta::relational::functions::pureToSqlQuery::processConcatenate_FunctionExpression_1__PropertyMapping_MANY__SelectWithCursor_1__Map_1__State_1__JoinType_1__String_1__List_1__DebugContext_1__Extension_MANY__RelationalOperationElement_1_), + ^PureFunctionToRelationalFunctionPair(first=meta::pure::functions::collection::greatest_X_MANY__X_$0_1$_, second=meta::relational::functions::pureToSqlQuery::processDynaFunction_FunctionExpression_1__PropertyMapping_MANY__SelectWithCursor_1__Map_1__State_1__JoinType_1__String_1__List_1__DebugContext_1__Extension_MANY__RelationalOperationElement_1_), + ^PureFunctionToRelationalFunctionPair(first=meta::pure::functions::collection::greatest_X_$1_MANY$__X_1_, second=meta::relational::functions::pureToSqlQuery::processDynaFunction_FunctionExpression_1__PropertyMapping_MANY__SelectWithCursor_1__Map_1__State_1__JoinType_1__String_1__List_1__DebugContext_1__Extension_MANY__RelationalOperationElement_1_), + ^PureFunctionToRelationalFunctionPair(first=meta::pure::functions::collection::least_X_MANY__X_$0_1$_, second=meta::relational::functions::pureToSqlQuery::processDynaFunction_FunctionExpression_1__PropertyMapping_MANY__SelectWithCursor_1__Map_1__State_1__JoinType_1__String_1__List_1__DebugContext_1__Extension_MANY__RelationalOperationElement_1_), + ^PureFunctionToRelationalFunctionPair(first=meta::pure::functions::collection::least_X_$1_MANY$__X_1_, second=meta::relational::functions::pureToSqlQuery::processDynaFunction_FunctionExpression_1__PropertyMapping_MANY__SelectWithCursor_1__Map_1__State_1__JoinType_1__String_1__List_1__DebugContext_1__Extension_MANY__RelationalOperationElement_1_), ^PureFunctionToRelationalFunctionPair(first=meta::pure::functions::lang::cast_Any_m__T_1__T_m_, second=meta::relational::functions::pureToSqlQuery::processNoOp_FunctionExpression_1__PropertyMapping_MANY__SelectWithCursor_1__Map_1__State_1__JoinType_1__String_1__List_1__DebugContext_1__Extension_MANY__RelationalOperationElement_1_), ^PureFunctionToRelationalFunctionPair(first=meta::pure::functions::lang::if_Boolean_1__Function_1__Function_1__T_m_,second=meta::relational::functions::pureToSqlQuery::processIf_FunctionExpression_1__PropertyMapping_MANY__SelectWithCursor_1__Map_1__State_1__JoinType_1__String_1__List_1__DebugContext_1__Extension_MANY__RelationalOperationElement_1_), ^PureFunctionToRelationalFunctionPair(first=meta::pure::functions::boolean::greaterThan_Number_1__Number_1__Boolean_1_,second=meta::relational::functions::pureToSqlQuery::processDynaFunction_FunctionExpression_1__PropertyMapping_MANY__SelectWithCursor_1__Map_1__State_1__JoinType_1__String_1__List_1__DebugContext_1__Extension_MANY__RelationalOperationElement_1_), diff --git a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/src/main/resources/core_relational/relational/relationalExtension.pure b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/src/main/resources/core_relational/relational/relationalExtension.pure index 96e257fa526..741eee09f4b 100644 --- a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/src/main/resources/core_relational/relational/relationalExtension.pure +++ b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/src/main/resources/core_relational/relational/relationalExtension.pure @@ -729,6 +729,16 @@ function <> meta::relational::functions::typeInference::getDynaF ]) ), + pair( + 'greatest', + list([ + pair( + {params: RelationalOperationElement[*] | true}, + {params: RelationalOperationElement[*] | $params->at(0)->inferRelationalType()} + ) + ]) + ), + pair( 'hour', list([ @@ -829,6 +839,16 @@ function <> meta::relational::functions::typeInference::getDynaF ]) ), + pair( + 'least', + list([ + pair( + {params: RelationalOperationElement[*] | true}, + {params: RelationalOperationElement[*] | $params->at(0)->inferRelationalType()} + ) + ]) + ), + pair( 'left', list([ diff --git a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/src/main/resources/core_relational/relational/sqlQueryToString/dbExtension.pure b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/src/main/resources/core_relational/relational/sqlQueryToString/dbExtension.pure index deb07bafd16..870dad211c5 100644 --- a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/src/main/resources/core_relational/relational/sqlQueryToString/dbExtension.pure +++ b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/src/main/resources/core_relational/relational/sqlQueryToString/dbExtension.pure @@ -938,6 +938,8 @@ Enum meta::relational::functions::sqlQueryToString::DynaFunctionRegistry equal, exists, exp, + explodeSemiStructured, + extractFromSemiStructured, firstDayOfMonth, firstDayOfQuarter, firstDayOfThisMonth, @@ -950,10 +952,9 @@ Enum meta::relational::functions::sqlQueryToString::DynaFunctionRegistry firstSecondOfMinute, firstMillisecondOfSecond, floor, - extractFromSemiStructured, - explodeSemiStructured, greaterThan, greaterThanEqual, + greatest, group, hour, if, @@ -967,6 +968,7 @@ Enum meta::relational::functions::sqlQueryToString::DynaFunctionRegistry isNull, isNumeric, joinStrings, + least, left, length, lessThan, diff --git a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/src/main/resources/core_relational/relational/sqlQueryToString/extensionDefaults.pure b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/src/main/resources/core_relational/relational/sqlQueryToString/extensionDefaults.pure index 7a1fabd8994..07e6de10a8c 100644 --- a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/src/main/resources/core_relational/relational/sqlQueryToString/extensionDefaults.pure +++ b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/src/main/resources/core_relational/relational/sqlQueryToString/extensionDefaults.pure @@ -150,6 +150,15 @@ function meta::relational::functions::sqlQueryToString::default::convertDateToSq | format('%t{[' + $timeZone + ']yyyy-MM-dd}', $date)); } +function meta::relational::functions::sqlQueryToString::default::convertGreatestLeastToCaseStatement(func:String[1], params:String[*]):String[1] +{ + let nonNullParams = $params->filter(p | $p->toLower() != 'null'); + + if ($nonNullParams->isEmpty(), | 'null', | $nonNullParams->filter(p | $p != 'null')->map(p | + 'when ' + $nonNullParams->map(c | $p + ' ' + $func + ' ' + $c)->joinStrings(' and ') + ' then ' + $p + )->joinStrings('case ', ' ', ' else null end')); +} + function meta::relational::functions::sqlQueryToString::default::processJoinStringsOperationWithConcatCall(js:JoinStrings[1], sgc:SqlGenerationContext[1]): String[1] { processJoinStringsOperation($js, $sgc, [], {strs, sep| $strs->joinStrings('concat(', if('\'\'' == $sep, |', ', |',' + $sep + ',') , ')')}); @@ -187,6 +196,7 @@ function meta::relational::functions::sqlQueryToString::default::getDynaFunction dynaFnToSql('floor', $allStates, ^ToSql(format='floor(%s)')), dynaFnToSql('greaterThan', $allStates, ^ToSql(format='%s > %s')), dynaFnToSql('greaterThanEqual', $allStates, ^ToSql(format='%s >= %s')), + dynaFnToSql('greatest', $allStates, ^ToSql(format='greatest(%s)', transform={p:String[*]|$p->joinStrings(', ')})), dynaFnToSql('group', $allStates, ^ToSql(format='(%s)')), dynaFnToSql('if', $allStates, ^ToSql(format='case when %s then %s else %s end', parametersWithinWhenClause = [true, false, false])), dynaFnToSql('in', $allStates, ^ToSql(format='%s in %s', transform={p:String[2] | if($p->at(1)->startsWith('(') && $p->at(1)->endsWith(')'), | $p, | [$p->at(0), ('(' + $p->at(1) + ')')])})), @@ -196,6 +206,7 @@ function meta::relational::functions::sqlQueryToString::default::getDynaFunction dynaFnToSql('isNotEmpty', $allStates, ^ToSql(format='%s is not null')), dynaFnToSql('isNotNull', $allStates, ^ToSql(format='%s is not null')), dynaFnToSql('isNull', $allStates, ^ToSql(format='%s is null')), + dynaFnToSql('least', $allStates, ^ToSql(format='least(%s)', transform={p:String[*]|$p->joinStrings(', ')})), dynaFnToSql('lessThan', $allStates, ^ToSql(format='%s < %s')), dynaFnToSql('lessThanEqual', $allStates, ^ToSql(format='%s <= %s')), dynaFnToSql('log', $allStates, ^ToSql(format='ln(%s)')), diff --git a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/src/main/resources/core_relational/relational/sqlQueryToString/testSuite/dynaFunctions/misc.pure b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/src/main/resources/core_relational/relational/sqlQueryToString/testSuite/dynaFunctions/misc.pure index 99f922b5e7c..508aec3f9da 100644 --- a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/src/main/resources/core_relational/relational/sqlQueryToString/testSuite/dynaFunctions/misc.pure +++ b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/src/main/resources/core_relational/relational/sqlQueryToString/testSuite/dynaFunctions/misc.pure @@ -50,3 +50,59 @@ function <> meta::relational::tests::dbSpecificTests::sqlQueryTe let expected = ^Literal(value='2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824'); runDynaFunctionDatabaseTest($dynaFunc, $expected, $config); } + +function <> meta::relational::tests::dbSpecificTests::sqlQueryTests::dynaFunctions::greatest::string(config:DbTestConfig[1]):Boolean[1] +{ + let dynaFunc = ^DynaFunction(name='greatest', parameters=[^Literal(value = 'b'), ^Literal(value = 'c'), ^Literal(value = 'a')]); + let expected = ^Literal(value = 'c'); + runDynaFunctionDatabaseTest($dynaFunc, $expected, $config); +} + +function <> meta::relational::tests::dbSpecificTests::sqlQueryTests::dynaFunctions::greatest::number(config:DbTestConfig[1]):Boolean[1] +{ + let dynaFunc = ^DynaFunction(name='greatest', parameters=[^Literal(value = 2), ^Literal(value = 3), ^Literal(value = 1)]); + let expected = ^Literal(value = 3); + runDynaFunctionDatabaseTest($dynaFunc, $expected, $config); +} + +function <> meta::relational::tests::dbSpecificTests::sqlQueryTests::dynaFunctions::greatest::date(config:DbTestConfig[1]):Boolean[1] +{ + let dynaFunc = ^DynaFunction(name='greatest', parameters=[^Literal(value = %2023-06-01), ^Literal(value = %2023-12-01), ^Literal(value = %2023-01-01)]); + let expected = ^Literal(value = %2023-12-01); + runDynaFunctionDatabaseTest($dynaFunc, $expected, $config); +} + +function <> meta::relational::tests::dbSpecificTests::sqlQueryTests::dynaFunctions::greatest::boolean(config:DbTestConfig[1]):Boolean[1] +{ + let dynaFunc = ^DynaFunction(name='greatest', parameters=[^Literal(value = true), ^Literal(value = false)]); + let expected = ^Literal(value = true); + runDynaFunctionDatabaseTest($dynaFunc, $expected, $config); +} + +function <> meta::relational::tests::dbSpecificTests::sqlQueryTests::dynaFunctions::least::string(config:DbTestConfig[1]):Boolean[1] +{ + let dynaFunc = ^DynaFunction(name='least', parameters=[^Literal(value = 'b'), ^Literal(value = 'a'), ^Literal(value = 'c')]); + let expected = ^Literal(value = 'a'); + runDynaFunctionDatabaseTest($dynaFunc, $expected, $config); +} + +function <> meta::relational::tests::dbSpecificTests::sqlQueryTests::dynaFunctions::least::number(config:DbTestConfig[1]):Boolean[1] +{ + let dynaFunc = ^DynaFunction(name='least', parameters=[^Literal(value = 2), ^Literal(value = 1), ^Literal(value = 3)]); + let expected = ^Literal(value = 1); + runDynaFunctionDatabaseTest($dynaFunc, $expected, $config); +} + +function <> meta::relational::tests::dbSpecificTests::sqlQueryTests::dynaFunctions::least::date(config:DbTestConfig[1]):Boolean[1] +{ + let dynaFunc = ^DynaFunction(name='least', parameters=[^Literal(value = %2023-06-01), ^Literal(value = %2023-01-01), ^Literal(value = %2023-12-01)]); + let expected = ^Literal(value = %2023-01-01); + runDynaFunctionDatabaseTest($dynaFunc, $expected, $config); +} + +function <> meta::relational::tests::dbSpecificTests::sqlQueryTests::dynaFunctions::least::boolean(config:DbTestConfig[1]):Boolean[1] +{ + let dynaFunc = ^DynaFunction(name='least', parameters=[^Literal(value = true), ^Literal(value = false)]); + let expected = ^Literal(value = false); + runDynaFunctionDatabaseTest($dynaFunc, $expected, $config); +} \ No newline at end of file diff --git a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/src/main/resources/core_relational/relational/tds/tests/testTDSFilter.pure b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/src/main/resources/core_relational/relational/tds/tests/testTDSFilter.pure index 93aabe30d04..cad496d8d1c 100644 --- a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/src/main/resources/core_relational/relational/tds/tests/testTDSFilter.pure +++ b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/src/main/resources/core_relational/relational/tds/tests/testTDSFilter.pure @@ -209,6 +209,20 @@ function <> meta::relational::tests::tds::tdsFilter::testEvalInFilter assertEquals('select "root".FIRSTNAME as "first", "root".LASTNAME as "last" from personTable as "root" where ("root".LASTNAME = \'Johnson\' or "root".LASTNAME = \'Hill\')', $result->sqlRemoveFormatting()); } +function <> meta::relational::tests::tds::tdsFilter::testInOnColumnInSubselect():Boolean[1] +{ + let result = execute(|Person.all()->project([ + col(p | $p.firstName, 'name'), + col(p | $p.age, 'age') + ]) + ->olapGroupBy('name', desc('age'), x | $x->meta::pure::functions::math::olap::rank(), 'rank') + ->filter({r | $r.getString('name')->in(['John', 'Peter'])}), simpleRelationalMapping, testRuntime(), meta::relational::extension::relationalExtensions()); + + assertEquals('select "name" as "name", "age" as "age", "rank" as "rank" from (select "root".FIRSTNAME as "name", "root".AGE as "age", rank() OVER (Partition By "root".FIRSTNAME Order By "root".AGE DESC) as "rank" from personTable as "root") as "subselect" where "name" in (\'John\', \'Peter\')', $result->sqlRemoveFormatting()); + assertSize($result.values.rows, 3); + assertEquals(['John', 22, 1, 'John', 12, 2, 'Peter', 23, 1], $result.values.rows->map(r|$r.values)); +} + function <> meta::relational::tests::tds::tdsFilter::testFilterOnQuotedColumnFromTableToTds():Boolean[1] { let queryWithoutQuotes = {|tableToTDS(meta::relational::functions::database::tableReference(meta::relational::tests::db,'default','tableWithQuotedColumns')) diff --git a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/src/main/resources/core_relational/relational/transform/fromPure/tests/testToSQLString.pure b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/src/main/resources/core_relational/relational/transform/fromPure/tests/testToSQLString.pure index ce101f8b7fb..01a668a1a26 100644 --- a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/src/main/resources/core_relational/relational/transform/fromPure/tests/testToSQLString.pure +++ b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/src/main/resources/core_relational/relational/transform/fromPure/tests/testToSQLString.pure @@ -674,6 +674,20 @@ function <> meta::relational::tests::functions::sqlstring::testToSQLS assertEquals('select cast("root".quantity as decimal) as "decimal", cast("root".quantity as double precision) as "float" from tradeTable as "root"', $result); } +function <> meta::relational::tests::functions::sqlstring::testGreatestLeast():Boolean[1] +{ + let result = toSQLString( + |Trade.all() + ->project([ + col(t| greatest([$t.quantity, 1, 3]), 'greatest'), + col(t| greatest([]->cast(@String)), 'greatest_empty'), + col(t| least([$t.quantity, 1, 3]), 'least'), + col(t| least([]->cast(@String)), 'least_empty') + ]), simpleRelationalMapping, DatabaseType.H2, meta::relational::extension::relationalExtensions()); + + assertEquals('select greatest("root".quantity, 1, 3) as "greatest", greatest(null) as "greatest_empty", least("root".quantity, 1, 3) as "least", least(null) as "least_empty" from tradeTable as "root"', $result); +} + function <> meta::relational::tests::functions::sqlstring::testToSQLStringForTDSStringJoin():Boolean[1] { let result = toSQLString( diff --git a/legend-engine-xts-sql/legend-engine-xt-sql-pure/src/main/resources/core_external_query_sql/binding/fromPure/fromPure.pure b/legend-engine-xts-sql/legend-engine-xt-sql-pure/src/main/resources/core_external_query_sql/binding/fromPure/fromPure.pure index c1c38849f61..42c90cb498a 100644 --- a/legend-engine-xts-sql/legend-engine-xt-sql-pure/src/main/resources/core_external_query_sql/binding/fromPure/fromPure.pure +++ b/legend-engine-xts-sql/legend-engine-xt-sql-pure/src/main/resources/core_external_query_sql/binding/fromPure/fromPure.pure @@ -258,17 +258,21 @@ function <> meta::external::query::sql::transformation::queryToP s:SingleColumn[1] | let name = extractNameFromSingleColumn($s, []); let nonAliasedName = extractNamePartsFromSingleColumn(^$s(alias = []), []); - let alias = $query.alias($nonAliasedName, $s.alias, true)->toOne(); let finalAlias = $query.alias($alias.name, $s.alias, false); - let realias = if ($alias.realias->isNotEmpty(), | $alias.realias, | $finalAlias.realias); - + //if the final alias has an alias, we know that it has already been renamed + let realias = if ($alias.realias->isNotEmpty() && $finalAlias->isEmpty(), | $alias.realias, | $finalAlias.realias); ^$alias(alias = if ($finalAlias->isEmpty(), | $alias.alias, | $finalAlias.alias), realias = $realias);, a:AllColumns[1] | if ($a.prefix->isEmpty(), - | $query.contexts->map(c | $c.aliases)->concatenate($query.aliases), - | $query.context($a.prefix).aliases); + | $query.aliases;,//$query.contexts->map(c | $c.aliases)->concatenate($query.aliases), + | $query.context($a.prefix).aliases->map(alias | + let finalAlias = $query.alias($alias.name, $alias.alias, false); + let realias = if ($alias.realias->isNotEmpty() && $finalAlias->isEmpty(), | $alias.realias, | $finalAlias.realias); + + ^$alias(alias = if ($finalAlias->isEmpty(), | $alias.alias, | $finalAlias.alias), realias = $realias); + );); ]) ); @@ -281,11 +285,15 @@ function <> meta::external::query::sql::transformation::queryToP let currentSchema = $query.columns.name; - let renamed = if ($expected.alias != $currentSchema, | ^$query(expression = processRename($expected->filter(e | !$e.alias->in($currentSchema)), $query)), | $query); + let restrict = if ($currentSchema != $expectedWithRealias.actual, + | ^$query(expression = processRestrict($expectedWithRealias.actual, $query)), + | $query); - let final = if ($renamed.columns.name != $expected.alias->removeDuplicates() && $expected.alias->isNotEmpty(), - | ^$query(expression = processRestrict($expected.alias, $renamed)), - | $renamed); + let restrictSchema = $restrict.columns.name; + + let final = if ($expected.alias != $restrictSchema, + | ^$restrict(expression = processRename($expected->filter(e | !$e.alias->in($restrictSchema)), $restrict)), + | $restrict); let aliases = $final.columns.name->map(c | ^SQLColumnAlias(name = $c)); @@ -294,23 +302,19 @@ function <> meta::external::query::sql::transformation::queryToP function <> meta::external::query::sql::transformation::queryToPure::extractAggregatesFromExpression(expression:meta::external::query::sql::metamodel::Expression[0..1]):meta::external::query::sql::metamodel::Expression[*] { - $expression->match([ - a:ArithmeticExpression[1] | $a.left->extractAggregatesFromExpression()->concatenate($a.right->extractAggregatesFromExpression()), - b:BetweenPredicate[1] | $b.min->extractAggregatesFromExpression()->concatenate($b.value->extractAggregatesFromExpression())->concatenate($b.max->extractAggregatesFromExpression()), - c:Cast[1] | $c.expression->extractAggregatesFromExpression(), - c:ComparisonExpression[1] | $c.left->extractAggregatesFromExpression()->concatenate($c.right->extractAggregatesFromExpression()), - e:Extract[1] | $e.expression->extractAggregatesFromExpression(), - f:FunctionCall[1] | if (isExpressionAggregate($f, false, false), | $f, | $f.arguments->map(a | $a->extractAggregatesFromExpression())), - i:IsNotNullPredicate[1] | $i.value->extractAggregatesFromExpression(), - i:IsNullPredicate[1] | $i.value->extractAggregatesFromExpression(), - l:LogicalBinaryExpression[1] | $l.left->extractAggregatesFromExpression()->concatenate($l.right->extractAggregatesFromExpression()), - n:NegativeExpression[1] | $n.value->extractAggregatesFromExpression(), - n:NotExpression[1] | $n.value->extractAggregatesFromExpression(), - s:SimpleCaseExpression[1] | $s->convertToSearchedCaseExpression()->extractAggregatesFromExpression(), - s:SearchedCaseExpression[1] | - $s.whenClauses->map(w | $w.operand->extractAggregatesFromExpression()->concatenate($w.result->extractAggregatesFromExpression()))->concatenate($s.defaultValue->extractAggregatesFromExpression());, - e:meta::external::query::sql::metamodel::Expression[0..1] | [] - ]); + + walk($expression, + t | $t, + f:FunctionCall[1] | if (isExpressionAggregate($f, false, false), | $f, | $f.arguments->map(a | $a->extractAggregatesFromExpression())) + ) +} + +function <> meta::external::query::sql::transformation::queryToPure::extractUsedColumnNames(expression:meta::external::query::sql::metamodel::Expression[*]):String[*] +{ + walk($expression, + q | $q, + q:QualifiedNameReference[1] | $q->extractNameFromExpression([]) + )->removeDuplicates() } @@ -318,54 +322,73 @@ function <> meta::external::query::sql::transformation::queryToP { debug('processProjection', $context.debug); - let aggregates = $originalSelect.selectItems->filter(si | $si->isSelectItemAggregate()); + let aggregates = $originalSelect.selectItems->filter(si | $si->isSelectItemAggregate())->cast(@SingleColumn); let windows = $originalSelect.selectItems->filter(si | $si->isSelectItemWindow())->cast(@SingleColumn); let standard = $originalSelect.selectItems->removeAll($aggregates)->removeAll($windows); let havingExtensions = extractAggregatesFromExpression($having)->map(e | ^SingleColumn(expression = $e)); - let standardExtensions = $standard->filter(si | !$si->isNonAliasedSelectItemColumnReference() && !$si->isSelectItemAggregate()); + let standardExtensions = $standard->filter(si | !$si->isNonAliasedSelectItemColumnReference() && !$si->isSelectItemAggregate())->cast(@SingleColumn); let windowExtensions = extractWindowExtensionExpressions($windows); + let isAggregate = $groupBy->isNotEmpty() || anyColumnAggregate($originalSelect); + let isWindow = $windows->isNotEmpty(); + let columns = $context.columns.name; + let extensionNames = $standardExtensions->concatenate($windowExtensions)->map(s | $s->extractNameFromSingleColumn($context)); + + //we pull all columns used within the scope to then understand which ones we need to realias in extend. We only need to re-alias a column if + //1. it clashes with existing column + //2. we need to carry a column through the lambda to be used in a group/agg/order/having clause + let usedColumns = extractUsedColumnNames($groupBy->concatenate($orderBy)->concatenate($having) + ->concatenate($aggregates.expression)->concatenate($aggregates.expression)->concatenate($havingExtensions.expression) + )->filter(c | $columns->contains($c)); + + let extensionPairs = $standardExtensions->concatenate($windowExtensions)->cast(@SingleColumn)->map(sc | let name = extractNameFromSingleColumn($sc->toOne(), $context); - pair($sc, if ($columns->contains($name), | ^$sc(alias = $name + '_1'), | $sc)); + pair($sc, if ($columns->contains($name) || $usedColumns->contains($name), | ^$sc(alias = $name + '_1'), | $sc)); ); + let extensions = $extensionPairs.second; + + let extendRequired = $extensions->isNotEmpty() && ($isAggregate || $isWindow || $havingExtensions->isNotEmpty()); + let selectItems = $originalSelect.selectItems->map(si | let extension = $extensionPairs->filter(e | $e.first == $si)->first(); - if ($extension.first == $extension.second, | $si, | $extension->toOne().second); + + if (!$extendRequired || $extension.first == $extension.second, + | $si, + | $extension->toOne().second); ); - let extensions = $extensionPairs.second; + let select = ^$originalSelect(selectItems = $selectItems); + let aliases = range(0, $originalSelect.selectItems->size())->map(i | let si = $originalSelect.selectItems->at($i); let eis = $selectItems->at($i); $si->match([ s:SingleColumn[1] | - let alias = extractAliasFromColumn($s); + let foundAlias = $context.alias($s->extractNamePartsFromSingleColumn([]), $s.alias, false); + let alias = if ($foundAlias->isEmpty(), | extractAliasFromColumn($s), | $foundAlias->toOne()); let ealias = extractAliasFromColumn($eis->cast(@SingleColumn)); - if ($si != $eis, | ^$alias(realias = $ealias.actual), | $alias);, - a:AllColumns[1] | $context.aliases->map(a | ^SQLColumnAlias(name = $a.actual)) + pair($s, if ($si != $eis, | ^$alias(realias = $ealias.actual), | $alias));, + a:AllColumns[1] | + let contexts = if ($a.prefix->isEmpty(), | $context.contexts->concatenate($context), | $context.context($a.prefix)); + $contexts.aliases->map(a | pair(^SingleColumn(expression = ^QualifiedNameReference(name=^QualifiedName(parts = $a.expected))), $a)); ]); ); - let select = ^$originalSelect(selectItems = $selectItems); - - let isAggregate = $groupBy->isNotEmpty() || anyColumnAggregate($select); - let isWindow = $windows->isNotEmpty(); - - let project = if ($standard->isNotEmpty() && !($isAggregate || $isWindow || $havingExtensions->isNotEmpty()), - | processSelect(^$select(selectItems = $standard), true, $context), + let project = if ($selectItems->isNotEmpty() && !($isAggregate || $isWindow || $havingExtensions->isNotEmpty()), + | processSelect($select, true, $context), | $context.expression->toOne()); - let extend = if ($extensions->isNotEmpty() && ($isAggregate || $isWindow || $havingExtensions->isNotEmpty()), - | processExtend(^$select(selectItems = $extensions), ^$context(expression = $project)), + let extend = if ($extendRequired, + | processExtend($extensions, $usedColumns, ^$context(expression = $project)), | $project); let group = if ($isAggregate || $havingExtensions->isNotEmpty(), @@ -380,9 +403,29 @@ function <> meta::external::query::sql::transformation::queryToP | appendTdsFunc($olapGroupBy, distinct_TabularDataSet_1__TabularDataSet_1_, []), | $olapGroupBy); - pair(^$context(expression = $distinctExp, aliases = $aliases), $select); + let newContext = ^$context(expression = $distinctExp); + + let newColumns = $newContext.columns.name; + + //we now want to add the current schema to the context aliases, to do this we lookup to see whether an aliased column has been re-named throughout + //this projection code (eg in project) and update the alias accordingly + let newAliases = $aliases->map(pair | + let si = $pair.first; + let alias = $pair.second; + + let found = $newColumns->contains($alias.actual->toOne()); + + ^$alias( + name = if (!$found, | $si->extractNameFromSingleColumn([]), | $alias.name), + alias = if (!$found, | $si.alias, | $alias.alias), + realias = if (!$found, | [], | $alias.realias) + ); + ); + + pair(^$newContext(aliases = $newAliases), $select); } + function <> meta::external::query::sql::transformation::queryToPure::extractColumnNameFromExpression(expression:meta::external::query::sql::metamodel::Expression[1], selectItems: SelectItem[*], context: SqlTransformContext[1]):String[1] { $expression->match([ @@ -565,15 +608,13 @@ function <> meta::external::query::sql::transformation::queryToP $functionCall; } -function <> meta::external::query::sql::transformation::queryToPure::processExtend(select: Select[1], context: SqlTransformContext[1]):FunctionExpression[1] +function <> meta::external::query::sql::transformation::queryToPure::processExtend(items: SelectItem[*], columnsToRealias:String[*], context: SqlTransformContext[1]):FunctionExpression[1] { debug('processExtend', $context.debug); - let selectItems = $select.selectItems->processSelectItems($context, false); - - let columns = $context.columns.name; + let selectItems = $items->processSelectItems($context, false); let args = $selectItems->map(item | - let rename = $columns->contains($item.second); + let rename = $columnsToRealias->contains($item.second); let name = if ($rename, | $item.second + '_1', | $item.second); createCol($item.first, $name); ); @@ -584,7 +625,7 @@ function <> meta::external::query::sql::transformation::queryToP function meta::external::query::sql::transformation::queryToPure::createCol(lambda:LambdaFunction[1], name:String[1]):SimpleFunctionExpression[1] { let typeArguments = ^GenericType(rawType = TDSRow); - let genericType = ^GenericType(rawType = BasicColumnSpecification, typeArguments = $typeArguments); + let genericType = ^GenericType(rawType = BasicColumnSpecification, typeArguments = $typeArguments); sfe(col_Function_1__String_1__BasicColumnSpecification_1_, $genericType, $typeArguments, [iv($lambda), iv($name)]); } @@ -612,26 +653,12 @@ function <> meta::external::query::sql::transformation::queryToP function <> meta::external::query::sql::transformation::queryToPure::isExpressionAggregate(e:meta::external::query::sql::metamodel::Expression[0..1], includeParameters:Boolean[1], includeWindow:Boolean[1]):Boolean[1] { - $e->match([ - a:ArithmeticExpression[1] | $a.left->isExpressionAggregate($includeParameters, $includeWindow) || $a.right->isExpressionAggregate($includeParameters, $includeWindow), - b:BetweenPredicate[1] | $b.min->isExpressionAggregate($includeParameters, $includeWindow) || $b.value->isExpressionAggregate($includeParameters, $includeWindow) || $b.max->isExpressionAggregate($includeParameters, $includeWindow), - c:Cast[1] | $c.expression->isExpressionAggregate($includeParameters, $includeWindow), - c:ComparisonExpression[1] | $c.left->isExpressionAggregate($includeParameters, $includeWindow) || $c.right->isExpressionAggregate($includeParameters, $includeWindow), - e:Extract[1] | $e.expression->isExpressionAggregate($includeParameters, $includeWindow), + walk($e, + t | $t->exists(v | $v), f:FunctionCall[1] | - let functionProcessor = functionProcessor($f.name); - $functionProcessor.isAggregate || ($includeWindow && $functionProcessor.isWindow) || ($includeParameters && $f.arguments->exists(a | $a->isExpressionAggregate($includeParameters, $includeWindow)));, - i:IsNotNullPredicate[1] | $i.value->isExpressionAggregate($includeParameters, $includeWindow), - i:IsNullPredicate[1] | $i.value->isExpressionAggregate($includeParameters, $includeWindow), - l:LogicalBinaryExpression[1] | $l.left->isExpressionAggregate($includeParameters, $includeWindow) || $l.right->isExpressionAggregate($includeParameters, $includeWindow), - n:NegativeExpression[1] | $n.value->isExpressionAggregate($includeParameters, $includeWindow), - n:NotExpression[1] | $n.value->isExpressionAggregate($includeParameters, $includeWindow), - s:SimpleCaseExpression[1] | $s->convertToSearchedCaseExpression()->isExpressionAggregate($includeParameters, $includeWindow), - s:SearchedCaseExpression[1] | - $s.whenClauses->exists(w | $w.operand->isExpressionAggregate($includeParameters, $includeWindow) || $w.result->isExpressionAggregate($includeParameters, $includeWindow)) - || $s.defaultValue->isExpressionAggregate($includeParameters, $includeWindow), - e:meta::external::query::sql::metamodel::Expression[0..1] | false - ]) + let functionProcessor = functionProcessor($f.name); + $functionProcessor.isAggregate || ($includeWindow && $functionProcessor.isWindow) || ($includeParameters && $f.arguments->exists(a | $a->isExpressionAggregate($includeParameters, $includeWindow))); + ) } function <> meta::external::query::sql::transformation::queryToPure::isSelectItemWindow(si: SelectItem[1]):Boolean[1] @@ -641,11 +668,13 @@ function <> meta::external::query::sql::transformation::queryToP && $si->cast(@SingleColumn).expression->cast(@FunctionCall).window->isNotEmpty() } +//we determins nonAliased as a column that either has no alias, or the alias is the same as the column name itself function <> meta::external::query::sql::transformation::queryToPure::isNonAliasedSelectItemColumnReference(si:SelectItem[1]):Boolean[1] { $si->match([ a:AllColumns[1] | true, - s:SingleColumn[1] | $s.expression->instanceOf(QualifiedNameReference) && $s.alias->isEmpty() + s:SingleColumn[1] | + ($s.expression->instanceOf(QualifiedNameReference) && $s.alias->isEmpty()) || ($s.expression->instanceOf(QualifiedNameReference) && $s.alias == $s.expression->extractNameFromExpression([])) ]) } @@ -877,7 +906,7 @@ function <> meta::external::query::sql::transformation::queryToP pair(JoinType.RIGHT, meta::relational::metamodel::join::JoinType.RIGHT_OUTER), pair(JoinType.INNER, meta::relational::metamodel::join::JoinType.INNER), pair(JoinType.CROSS, meta::relational::metamodel::join::JoinType.INNER), - pair(JoinType.FULL, meta::relational::metamodel::join::JoinType.FULL_OUTER) + pair(JoinType.FULL, meta::relational::metamodel::join::JoinType.FULL_OUTER) ]->getValue($joinType); } @@ -1387,16 +1416,19 @@ function <> meta::external::query::sql::transformation::queryToP function <> meta::external::query::sql::transformation::queryToPure::processExpression(expression: meta::external::query::sql::metamodel::Expression[1], expContext:SqlTransformExpressionContext[1], context: SqlTransformContext[1]):ValueSpecification[1] { - debug('processExpression', $context.debug); + + //we do not want to maintain type casting if we enter a function as it could cast any param incorrectly + let functionalContext = ^$expContext(type = []); + let exp = $expression->match([ a:ArithmeticExpression[1] | processArithmeticExpression($a, $expContext, $context), b:BetweenPredicate[1] | processBetweenPredicate($b, $expContext, $context), c:Cast[1] | processCast($c, $expContext, $context), c:ComparisonExpression[1] | processComparisonExpression($c, $expContext, $context), c:CurrentTime[1] | processCurrentTime($c, $expContext, $context), - e:Extract[1] | processExtract($e, $expContext, $context), - f:FunctionCall[1] | processFunctionCall($f, $expContext, $context), + e:Extract[1] | processExtract($e, $functionalContext, $context), + f:FunctionCall[1] | processFunctionCall($f, $functionalContext, $context), i:InListExpression[1] | processInListExpression($i, $expContext, $context), i:InPredicate[1] | processInPredicate($i, $expContext, $context), i:IsNullPredicate[1] | processIsNullPredicate($i, $expContext, $context), @@ -2057,6 +2089,7 @@ function meta::external::query::sql::transformation::queryToPure::functionProces processor('date_trunc', Date, {args, fc, ctx | assertEquals(2, $args->size(), 'incorrect number of args for date_trunc'); let part = $args->at(0); + let value = $part->reactivate()->toOne()->cast(@String); let func = [ @@ -2075,19 +2108,33 @@ function meta::external::query::sql::transformation::queryToPure::functionProces //COLLECTION processor('coalesce', false, false, [], {args, fc, ctx | - let filteredArgs = $args->filter(a | $a->match([ - i:InstanceValue[1] | !($i.genericType.rawType == Nil && $i.values->isEmpty()), - v:ValueSpecification[1] | true - ])); - + let filteredArgs = getNonNullArguments($args); let type = $filteredArgs.genericType->first().rawType; sfe(meta::pure::tds::extensions::firstNotNull_T_MANY__T_$0_1$_, ^GenericType(rawType = $type), ^GenericType(rawType = $type), $filteredArgs->iv()); }), + processor('greatest', false, false, [], {args, fc, ctx | + let filteredArgs = getNonNullArguments($args); + let type = $filteredArgs.genericType->first().rawType; + + let func = if ($filteredArgs->isEmpty(), | greatest_X_MANY__X_$0_1$_, | greatest_X_$1_MANY$__X_1_); + + sfe($func, ^GenericType(rawType = $type), ^GenericType(rawType = $type), $filteredArgs->iv()); + }), + + processor('least', false, false, [], {args, fc, ctx | + let filteredArgs = getNonNullArguments($args); + let type = $filteredArgs.genericType->first().rawType; + + let func = if ($filteredArgs->isEmpty(), | least_X_MANY__X_$0_1$_, | least_X_$1_MANY$__X_1_); + + sfe($func, ^GenericType(rawType = $type), ^GenericType(rawType = $type), $filteredArgs->iv()); + }), + //FORMAT processor('to_char', String, {args, fc, ctx | - assertEquals(2, $args->size(), 'incorrect number of args for t-_char'); + assertEquals(2, $args->size(), 'incorrect number of args for to_char'); let arg = $args->at(0); let type = $arg.genericType.rawType; @@ -2114,6 +2161,14 @@ function meta::external::query::sql::transformation::queryToPure::functionProces ] } +function <> meta::external::query::sql::transformation::queryToPure::getNonNullArguments(args:ValueSpecification[*]):ValueSpecification[*] +{ + $args->filter(a | $a->match([ + i:InstanceValue[1] | !($i.genericType.rawType == Nil && $i.values->isEmpty()), + v:ValueSpecification[1] | true + ])); +} + function <> meta::external::query::sql::transformation::queryToPure::toCharFormats():Pair ValueSpecification[1]}>>[*] { [ @@ -2225,20 +2280,20 @@ function <> meta::external::query::sql::transformation::queryToP let prefixes = toCharPrefixes(); let suffixes = toCharSuffixes(); - if ($format->length() == 0, + if ($format->length() == 0, | if ($result->isEmpty(), | $arg, | $result->toOne()), - | - let prefix = $prefixes->fold({p, acc | + | + let prefix = $prefixes->fold({p, acc | if ($format->startsWith($p.first) && $p.second->eval(), | pair($format->substring($p.first->length(), $format->length()), $p.first), | $acc); }, pair($format, '')); - let scan = $formats->fold({token, acc | - if (!$acc.matched && $acc.format->startsWith($token.first), - | + let scan = $formats->fold({token, acc | + if (!$acc.matched && $acc.format->startsWith($token.first), + | let newFormat = $acc.format->substring($token.first->length(), $acc.format->length()); - let suffix = $suffixes->fold({s, acc | + let suffix = $suffixes->fold({s, acc | if ($newFormat->startsWith($s.first) && $s.second->eval(), | pair($newFormat->substring($s.first->length(), $newFormat->length()), $s.first), | $acc); }, pair($newFormat, '')); @@ -2248,7 +2303,7 @@ function <> meta::external::query::sql::transformation::queryToP | $acc); }, ^ToCharContext(format = $prefix.first, result = $result->evaluateAndDeactivate(), matched = false, prefix = if ($prefix.second != '', | $prefix.second, | []))); - if ($scan.matched, + if ($scan.matched, | toChar($scan.format, $scan.result->evaluateAndDeactivate(), $arg), | toChar($scan.format->substring(1, $scan.format->length()), toCharCombine($scan.result->evaluateAndDeactivate(), iv($scan.format->chunk(1)->at(0))), $arg)); ); @@ -2281,11 +2336,11 @@ function <> meta::external::query::sql::transformation::queryTo let f = nullOrSfe($func, $arg->evaluateAndDeactivate())->evaluateAndDeactivate(); let type = $func->functionReturnType().rawType->toOne(); - let str = if ($type != String, | nullOrSfe(toString_Any_1__String_1_, $f), | $f); + let str = if ($type != String, | nullOrSfe(toString_Any_1__String_1_, $f), | $f); - let cased = if ($upper, + let cased = if ($upper, | nullOrSfe(toUpper_String_1__String_1_, $str), - | if ($lower, + | if ($lower, | nullOrSfe(toLower_String_1__String_1_, $str), | $str)); @@ -2848,6 +2903,49 @@ function <> meta::external::query::sql::transformation::queryToP nullOrSfe($func, [$left, $right]); } +function <> meta::external::query::sql::transformation::queryToPure::walk(e:meta::external::query::sql::metamodel::Expression[0..1], accumulator:Function<{T[*]->T[m]}>[1], extras:Function<{Nil[1]->T[m]}>[1]):T[m] +{ + let result = $e->match($extras->concatenate([ + a:ArithmeticExpression[1] | walk($a.left, $a.right, $accumulator, $extras), + b:BetweenPredicate[1] | walk($b.min, $b.value, $b.max, $accumulator, $extras), + c:Cast[1] | walk($c.expression, $accumulator, $extras), + c:ComparisonExpression[1] | walk($c.left, $c.right, $accumulator, $extras), + e:Extract[1] | walk($e.expression, $accumulator, $extras), + f:FunctionCall[1] | + let w1 = walk($f.window.partitions, $f.window.orderBy.sortKey, $f.group.orderBy.sortKey, $accumulator, $extras); + let w2 = walk($f.arguments, $f.filter, $accumulator, $extras); + $w1->concatenate($w2);, + i:InListExpression[1] | walk($i.values, $accumulator, $extras), + i:IsNotNullPredicate[1] | walk($i.value, $accumulator, $extras), + i:IsNullPredicate[1] | walk($i.value, $accumulator, $extras), + l:LikePredicate[1] | walk($l.value, $l.pattern, $l.escape, $accumulator, $extras), + l:LogicalBinaryExpression[1] | walk($l.left, $l.right, $accumulator, $extras), + n:NegativeExpression[1] | walk($n.value, $accumulator, $extras), + n:NotExpression[1] | walk($n.value, $accumulator, $extras), + s:SimpleCaseExpression[1] | $s->convertToSearchedCaseExpression()->walk($accumulator, $extras), + s:SearchedCaseExpression[1] | $s.whenClauses->map(w | walk($w.operand, $w.result, $accumulator, $extras))->concatenate(walk($s.defaultValue, $accumulator, $extras)), + e:meta::external::query::sql::metamodel::Expression[0..1] | [] + ])->toOneMany()); + + $accumulator->eval($result); +} + +function <> meta::external::query::sql::transformation::queryToPure::walk(e1:meta::external::query::sql::metamodel::Expression[*], e2:meta::external::query::sql::metamodel::Expression[*], accumulator:Function<{T[*]->T[m]}>[1], extras:Function<{Nil[1]->T[m]}>[1]):T[*] +{ + walk($e1, $e2, [], $accumulator, $extras); +} + + +function <> meta::external::query::sql::transformation::queryToPure::walk(e1:meta::external::query::sql::metamodel::Expression[*], e2:meta::external::query::sql::metamodel::Expression[*], e3:meta::external::query::sql::metamodel::Expression[*], accumulator:Function<{T[*]->T[m]}>[1], extras:Function<{Nil[1]->T[m]}>[1]):T[*] +{ + walk($e1->concatenate($e2)->concatenate($e3), $accumulator, $extras); +} + +function <> meta::external::query::sql::transformation::queryToPure::walk(expressions:meta::external::query::sql::metamodel::Expression[*], accumulator:Function<{T[*]->T[m]}>[1], extras:Function<{Nil[1]->T[m]}>[1]):T[*] +{ + $expressions->map(e | $e->walk($accumulator, $extras)); +} + function meta::external::query::sql::transformation::queryToPure::appendTdsFunc(lambda:LambdaFunction[1], execFunc: meta::pure::metamodel::function::Function[1], args: List[*]): LambdaFunction[1] { @@ -2983,7 +3081,7 @@ function <> meta::external::query::sql::transformation::queryToP ^Multiplicity(lowerBound = $mv, upperBound = $mv); ]); - + ^InstanceValue(multiplicity = $multiplicity, genericType = $genericType, values = $value); } diff --git a/legend-engine-xts-sql/legend-engine-xt-sql-pure/src/main/resources/core_external_query_sql/binding/fromPure/tests/testTranspile.pure b/legend-engine-xts-sql/legend-engine-xt-sql-pure/src/main/resources/core_external_query_sql/binding/fromPure/tests/testTranspile.pure index 926356cd1e2..cd8787fe0e7 100644 --- a/legend-engine-xts-sql/legend-engine-xt-sql-pure/src/main/resources/core_external_query_sql/binding/fromPure/tests/testTranspile.pure +++ b/legend-engine-xts-sql/legend-engine-xt-sql-pure/src/main/resources/core_external_query_sql/binding/fromPure/tests/testTranspile.pure @@ -77,7 +77,9 @@ function <> meta::external::query::sql::transformation::queryToPure:: pair('Integer', 'Integer_table2'), pair('String', 'String_table2') ]), meta::relational::metamodel::join::JoinType.LEFT_OUTER, {row1:TDSRow[1], row2:TDSRow[1] | $row1.getInteger('Integer_table1') == $row2.getInteger('Integer_table2')} - )->renameColumns([ + ) + ->restrict(['Boolean_table1', 'Integer_table1', 'Float_table1', 'Decimal_table1', 'StrictDate_table1', 'DateTime_table1', 'String_table1']) + ->renameColumns([ pair('Boolean_table1', 'Boolean'), pair('Integer_table1', 'Integer'), pair('Float_table1', 'Float'), @@ -85,7 +87,7 @@ function <> meta::external::query::sql::transformation::queryToPure:: pair('StrictDate_table1', 'StrictDate'), pair('DateTime_table1', 'DateTime'), pair('String_table1', 'String') - ])->restrict(['Boolean', 'Integer', 'Float', 'Decimal', 'StrictDate', 'DateTime', 'String']) + ]) } ) } @@ -234,16 +236,11 @@ function <> meta::external::query::sql::transformation::queryToPure:: [ 'Boolean', 'Integer', 'Float', 'Decimal', 'StrictDate', 'DateTime', 'String' ]) ->extend([ col(row:TDSRow[1] | $row.getString('String'), 'col1'), - col(row:TDSRow[1] | $row.getString('String'), 'col2'), - col(row:TDSRow[1] | $row.getStrictDate('StrictDate'), 'StrictDate_1'), - col(row:TDSRow[1] | $row.getString('String'), 'String_1') + col(row:TDSRow[1] | $row.getString('String'), 'col2') ])->groupBy( - ['StrictDate_1', 'String_1', 'col1', 'col2'], + ['StrictDate', 'String', 'col1', 'col2'], agg('sum', row | $row.getInteger('Integer'), y | $y->sum()) - )->renameColumns([ - pair('StrictDate_1', 'StrictDate'), - pair('String_1', 'String') - ])->restrict([ + )->restrict([ 'col1', 'col2', 'StrictDate', 'String', 'sum' ]) }, false @@ -480,23 +477,42 @@ function <> meta::external::query::sql::transformation::queryToPure:: }) } -function <> meta::external::query::sql::transformation::queryToPure::tests::testOrderByWithExtendAlias():Boolean[1] +function <> meta::external::query::sql::transformation::queryToPure::tests::testOrderByColumnRealiasWithGroupBy():Boolean[1] { - test( + test([ 'SELECT Calc AS Calc, Calc AS Calc2 FROM (select 1 AS Calc from service."/service/service1") GROUP BY 1 ORDER BY 1, Calc2 ASC', + 'SELECT Calc AS Calc, Calc AS Calc2 FROM (select 1 AS Calc from service."/service/service1") GROUP BY 1 ORDER BY Calc, Calc2 ASC' + ], {| FlatInput.all()->project( [ x | $x.booleanIn, x | $x.integerIn, x | $x.floatIn, x | $x.decimalIn, x | $x.strictDateIn, x | $x.dateTimeIn, x | $x.stringIn ], [ 'Boolean', 'Integer', 'Float', 'Decimal', 'StrictDate', 'DateTime', 'String' ]) ->project(col(row:TDSRow[1] | 1, 'Calc')) ->extend([ - col(row:TDSRow[1] | $row.getInteger('Calc'), 'Calc_1'), col(row:TDSRow[1] | $row.getInteger('Calc'), 'Calc2') ]) - ->restrict(['Calc_1', 'Calc2']) + ->restrict(['Calc', 'Calc2']) ->distinct() - ->sort([asc('Calc_1'), asc('Calc2')]) - ->renameColumns(pair('Calc_1', 'Calc')) + ->sort([asc('Calc'), asc('Calc2')]) + }) +} + +function <> meta::external::query::sql::transformation::queryToPure::tests::testOrderByColumnRealiasWithNoGroupBy():Boolean[1] +{ + test([ + 'SELECT Calc AS Calc, Calc AS Calc2 FROM (select 1 AS Calc from service."/service/service1") ORDER BY 1, Calc2 ASC', + 'SELECT Calc AS Calc, Calc AS Calc2 FROM (select 1 AS Calc from service."/service/service1") ORDER BY Calc, Calc2 ASC' + ], + + {| FlatInput.all()->project( + [ x | $x.booleanIn, x | $x.integerIn, x | $x.floatIn, x | $x.decimalIn, x | $x.strictDateIn, x | $x.dateTimeIn, x | $x.stringIn ], + [ 'Boolean', 'Integer', 'Float', 'Decimal', 'StrictDate', 'DateTime', 'String' ]) + ->project(col(row:TDSRow[1] | 1, 'Calc')) + ->project([ + col(row:TDSRow[1] | $row.getInteger('Calc'), 'Calc'), + col(row:TDSRow[1] | $row.getInteger('Calc'), 'Calc2') + ]) + ->sort([asc('Calc'), asc('Calc2')]) }) } @@ -605,13 +621,16 @@ function <> meta::external::query::sql::transformation::queryToPure:: function <> meta::external::query::sql::transformation::queryToPure::tests::testComparisonOperatorsDate():Boolean[1] { test( - 'SELECT * FROM service."/service/service1" WHERE StrictDate > TIMESTAMP \'2023-01-01\' OR StrictDate < CURRENT_DATE OR StrictDate >= TIMESTAMP \'2023-01-02\' OR StrictDate <= CURRENT_DATE OR StrictDate > \'2023-01-03\'', + 'SELECT * FROM service."/service/service1" WHERE StrictDate > TIMESTAMP \'2023-01-01\' OR StrictDate < CURRENT_DATE OR StrictDate >= TIMESTAMP \'2023-01-02\' ' + + 'OR StrictDate <= CURRENT_DATE OR StrictDate > \'2023-01-03\' OR StrictDate > date_trunc(\'month\', StrictDate)', {| FlatInput.all()->project( [ x | $x.booleanIn, x | $x.integerIn, x | $x.floatIn, x | $x.decimalIn, x | $x.strictDateIn, x | $x.dateTimeIn, x | $x.stringIn ], [ 'Boolean', 'Integer', 'Float', 'Decimal', 'StrictDate', 'DateTime', 'String' ]) ->filter(row | ($row.getStrictDate('StrictDate') > parseDate('2023-01-01')) || ($row.getStrictDate('StrictDate') < today()) - || ($row.getStrictDate('StrictDate') >= parseDate('2023-01-02')) || ($row.getStrictDate('StrictDate') <= today()) || ($row.getStrictDate('StrictDate') > parseDate('2023-01-03'))) + || ($row.getStrictDate('StrictDate') >= parseDate('2023-01-02')) || ($row.getStrictDate('StrictDate') <= today()) + || ($row.getStrictDate('StrictDate') > parseDate('2023-01-03')) + || ($row.getStrictDate('StrictDate') > firstDayOfMonth($row.getStrictDate('StrictDate')))) }, false) } @@ -726,7 +745,7 @@ function <> meta::external::query::sql::transformation::queryToPure:: function <> meta::external::query::sql::transformation::queryToPure::tests::testGroupByWithAlias():Boolean[1] { test( - 'SELECT String, sum(Integer) AS "sum", Boolean AS "bool" FROM service."/service/service1" GROUP BY String, "bool"', + 'SELECT String, sum(Integer) AS "sum", Boolean AS "bool" FROM service."/service/service1" GROUP BY String, bool', {| FlatInput.all()->project( [ x | $x.booleanIn, x | $x.integerIn, x | $x.floatIn, x | $x.decimalIn, x | $x.strictDateIn, x | $x.dateTimeIn, x | $x.stringIn ], @@ -846,11 +865,11 @@ function <> meta::external::query::sql::transformation::queryToPure:: agg('Count', row | $row.getInteger('Integer'), y | $y->count()), agg('Float Count', row | $row.getFloat('Float'), y | $y->count()) ]) + ->restrict(['Count', 'Float Count', 'String_1', 'Float_1', 'Original Float']) ->renameColumns([ pair('String_1', 'String'), pair('Float_1', 'Float') ]) - ->restrict(['Count', 'Float Count', 'String', 'Float', 'Original Float']) }, false ) } @@ -929,6 +948,9 @@ function <> meta::external::query::sql::transformation::queryToPure:: 'str' ], agg('COUNT(1)', row | 1, y | $y->count())) ->filter(row | $row.getInteger('COUNT(1)') > 0) + ->restrict([ + 'Boolean_table1', 'Integer_table1', 'Float_table1', 'Decimal_table1', 'StrictDate_table1', 'DateTime_table1', 'String_table1', 'str' + ]) ->renameColumns([ pair('Boolean_table1', 'Boolean'), pair('Integer_table1', 'Integer'), @@ -938,7 +960,6 @@ function <> meta::external::query::sql::transformation::queryToPure:: pair('DateTime_table1', 'DateTime'), pair('String_table1', 'String') ]) - ->restrict(['Boolean', 'Integer', 'Float', 'Decimal', 'StrictDate', 'DateTime', 'String', 'str']) }, false) } @@ -1324,11 +1345,7 @@ function <> meta::external::query::sql::transformation::queryToPure:: {| FlatInput.all()->project( [ x | $x.booleanIn, x | $x.integerIn, x | $x.floatIn, x | $x.decimalIn, x | $x.strictDateIn, x | $x.dateTimeIn, x | $x.stringIn ], [ 'Boolean', 'Integer', 'Float', 'Decimal', 'StrictDate', 'DateTime', 'String' ]) - ->extend([ - col(row:TDSRow[1]|$row.getString('String'), 'String_1') - ]) - ->restrict('String_1')->distinct() - ->renameColumns(pair('String_1', 'String')) + ->restrict('String')->distinct() ->renameColumns(pair('String', 'String_t0')) ->join(FlatInput.all()->project( [ x | $x.booleanIn, x | $x.integerIn, x | $x.floatIn, x | $x.decimalIn, x | $x.strictDateIn, x | $x.dateTimeIn, x | $x.stringIn ], @@ -1904,7 +1921,11 @@ function <> meta::external::query::sql::transformation::queryToPure:: function <> meta::external::query::sql::transformation::queryToPure::tests::testCollectionFunctions():Boolean[1] { test( - 'SELECT coalesce(NULL, Integer, 1) AS "COALESCE" FROM service."/service/service1"', + 'SELECT ' + + 'coalesce(NULL, Integer, 1) AS "COALESCE", ' + + 'greatest(NULL, Integer, 1) AS "GREATEST", ' + + 'least(NULL, Integer, 1) AS "LEAST" ' + + 'FROM service."/service/service1"', {| FlatInput.all() @@ -1912,7 +1933,9 @@ function <> meta::external::query::sql::transformation::queryToPure:: [ x | $x.booleanIn, x | $x.integerIn, x | $x.floatIn, x | $x.decimalIn, x | $x.strictDateIn, x | $x.dateTimeIn, x | $x.stringIn ], [ 'Boolean', 'Integer', 'Float', 'Decimal', 'StrictDate', 'DateTime', 'String' ]) ->project([ - col(row:TDSRow[1] | meta::pure::tds::extensions::firstNotNull([$row.getInteger('Integer'), 1]), 'COALESCE') + col(row:TDSRow[1] | meta::pure::tds::extensions::firstNotNull([$row.getInteger('Integer'), 1]), 'COALESCE'), + col(row:TDSRow[1] | greatest([$row.getInteger('Integer'), 1]), 'GREATEST'), + col(row:TDSRow[1] | least([$row.getInteger('Integer'), 1]), 'LEAST') ]) }) } @@ -2374,7 +2397,7 @@ function meta::external::query::sql::transformation::queryToPure::tests::test(sq { $sqls->forAll(sql | let sqlTransformContext = $sql->processQuery($sources, $scopeWithFrom); - let actual = $sqlTransformContext.lambda(); + let actual = $sqlTransformContext.lambda(); if ($assertLambda, | assertLambdaEquals($expected, $actual), | true); if ($assertJSON, | assertLambdaJSONEquals($expected, $actual), | true);