From 2460d71ff9298889da0a2bc9903b807300a781d5 Mon Sep 17 00:00:00 2001 From: gs-jp1 <80327721+gs-jp1@users.noreply.github.com> Date: Mon, 18 Sep 2023 15:48:19 +0100 Subject: [PATCH] Legend SQL - assortment of updates (#2278) - handle nullable args in function translations - small int cast support - fix some generic types --- .../binding/fromPure/fromPure.pure | 238 +++++++++++------- .../binding/fromPure/tests/testTranspile.pure | 149 ++++++++++- 2 files changed, 288 insertions(+), 99 deletions(-) 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 2eace96a34c..58e8402307e 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 @@ -112,8 +112,8 @@ function meta::external::query::sql::transformation::queryToPure::getPlan( function meta::external::query::sql::transformation::queryToPure::parameterConstantValue(value:ValueSpecification[1], many:Boolean[1]):Any[0..1] { $value->evaluateAndDeactivate()->match([ - i:InstanceValue[1] | if ($many, - | list($i.values->map(v | $v->normalizeParameterValue())), + i:InstanceValue[1] | if ($many, + | list($i.values->map(v | $v->normalizeParameterValue())), | $i.values->first()->normalizeParameterValue()), v:ValueSpecification[1] | [] ]); @@ -131,7 +131,7 @@ function meta::external::query::sql::transformation::queryToPure::parameterPlan( { $value->evaluateAndDeactivate()->match([ i:InstanceValue[1] | [], - v:ValueSpecification[1] | + v:ValueSpecification[1] | let expression = if ($v.genericType.rawType->in([Date, StrictDate, DateTime]), | sfe(toString_Any_1__String_1_, $v)->evaluateAndDeactivate(), | $v); lambda(^FunctionType(returnMultiplicity = $expression.multiplicity, returnType = $expression.genericType), $expression)->executionPlan($extensions); ]); @@ -307,7 +307,7 @@ function <> meta::external::query::sql::transformation::queryToP let standardExtensions = $standard->filter(si | !$si->isSelectItemColumnReference() && !$si->isSelectItemAggregate()); let windowExtensions = extractWindowExtensionExpressions($windows); - + let extensions = $standardExtensions->concatenate($windowExtensions); let isAggregate = $groupBy->isNotEmpty() || anyColumnAggregate($select); @@ -457,7 +457,7 @@ function <> meta::external::query::sql::transformation::queryToP function <> meta::external::query::sql::transformation::queryToPure::processAggregationLambda(expression:meta::external::query::sql::metamodel::Expression[1], type:Type[1], context:SqlTransformContext[1]):LambdaFunction[1] { - let expContext = expressionContext(^VariableExpression(genericType = ^GenericType(rawType = $type),name = 'y', multiplicity = PureOne), ^Map()); + let expContext = expressionContext(^VariableExpression(genericType = ^GenericType(rawType = $type),name = 'y', multiplicity = PureOne), ^Map()); let aggExpression = processExpression($expression, ^$expContext(withinAggregate = true), $context); @@ -527,10 +527,11 @@ function <> meta::external::query::sql::transformation::queryToP function <> meta::external::query::sql::transformation::queryToPure::processExtend(select: Select[1], context: SqlTransformContext[1]):FunctionExpression[1] { debug('processExtend', $context.debug); - let genericType = ^GenericType(rawType = TDSRow); + let typeArguments = ^GenericType(rawType = TDSRow); + let genericType = ^GenericType(rawType = BasicColumnSpecification, typeArguments = $typeArguments); let args = $select.selectItems->processSelectItems($context, false)->map(item | - sfe(col_Function_1__String_1__BasicColumnSpecification_1_, $genericType, [$item.first->iv(), $item.second->iv()]); + sfe(col_Function_1__String_1__BasicColumnSpecification_1_, $genericType, $typeArguments, [$item.first->iv(), $item.second->iv()]); ); let iv = iv($args); @@ -567,7 +568,7 @@ function <> meta::external::query::sql::transformation::queryToP b:BetweenPredicate[1] | $b.min->isExpressionAggregate($includeParameters, $includeWindow) || $b.value->isExpressionAggregate($includeParameters, $includeWindow) || $b.max->isExpressionAggregate($includeParameters, $includeWindow), c:ComparisonExpression[1] | $c.left->isExpressionAggregate($includeParameters, $includeWindow) || $c.right->isExpressionAggregate($includeParameters, $includeWindow), e:Extract[1] | $e.expression->isExpressionAggregate($includeParameters, $includeWindow), - f:FunctionCall[1] | + 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), @@ -594,10 +595,11 @@ function <> meta::external::query::sql::transformation::queryToP function <> meta::external::query::sql::transformation::queryToPure::processSelectToProject(select: Select[1], context: SqlTransformContext[1]):FunctionExpression[1] { debug('processSelectToProject', $context.debug); - let genericType = ^GenericType(rawType = TDSRow); + let typeArguments = ^GenericType(rawType = TDSRow); + let genericType = ^GenericType(rawType = BasicColumnSpecification, typeArguments = $typeArguments); let args = $select.selectItems->processSelectItems($context, false)->map(item | - sfe(col_Function_1__String_1__BasicColumnSpecification_1_, $genericType, [$item.first->iv(), $item.second->iv()]); + sfe(col_Function_1__String_1__BasicColumnSpecification_1_, $genericType, $typeArguments, [$item.first->iv(), $item.second->iv()]); ); let iv = iv($args); @@ -668,6 +670,7 @@ function <> meta::external::query::sql::transformation::queryToP function <> meta::external::query::sql::transformation::queryToPure::processFrom(relations: Relation[*], context: SqlTransformContext[1]): SqlTransformContext[1] { debug('processFrom', $context.debug); + // TODO: fix logic for multiple tables in from $relations->match([ relation: Relation[1] | processRelation($relation, $context), @@ -1107,7 +1110,7 @@ function <> meta::external::query::sql::transformation::queryToP | $context.context($a.prefix).aliases); $columns->map(c | - //set doCastNonPrimitiveColumnAccessor to ensure correct column type for enums + //set doCastNonPrimitiveColumnAccessor to ensure correct column type for enums let expression = createTdsColumn($c.actual, $expContext.var, ^$expContext(doCastNonPrimitiveColumnAccessor = true), $context); let functionType = ^FunctionType(parameters = $expContext.defaultVar, returnMultiplicity = PureOne, returnType = $expression.genericType->toOne()); let lambda = lambda($functionType, $expression); @@ -1119,8 +1122,15 @@ function <> meta::external::query::sql::transformation::queryToP //we handle the Null expression here as we have to treat differently in the context of "select null" to explicitly type let expression = $s.expression->match([ n:NullLiteral[1] | createCast(iv([]), String)->evaluateAndDeactivate(), - e:meta::external::query::sql::metamodel::Expression[1] | $e->processExpression(^$expContext(doCastNonPrimitiveColumnAccessor = true), $context)->toOne() - ]); + e:meta::external::query::sql::metamodel::Expression[1] | + let vs = $e->processExpression(^$expContext(doCastNonPrimitiveColumnAccessor = true), $context)->toOne(); + + $vs->match([ + iv:InstanceValue[1] | if ($iv.multiplicity == PureZero, | doCreateCast($iv, $iv.genericType.rawType->toOne()), | $iv), + v:ValueSpecification[1] | $v + ]); + + ])->evaluateAndDeactivate(); let functionType = ^FunctionType(parameters = $expContext.defaultVar, returnMultiplicity = PureOne, returnType = $expression.genericType); let lambda = lambda($functionType, $expression); @@ -1411,7 +1421,7 @@ function <> meta::external::query::sql::transformation::queryToP pair($castName == 'VARCHAR', | processVarcharCast($c, $expression, $expContext, $context)), pair($castName == 'NUMERIC', | processNumericCast($c, $expression, $expContext, $context)), pair($castName == 'DOUBLE PRECISION', | processDoublePrecisionCast($c, $expression, $expContext, $context)), - pair($type == String, | processCastAsParse($c, $expression, $expContext, $context)), + pair($type == String || $castName == 'TEXT', | processCastAsParse($c, $expression, $expContext, $context)), pair(true, | processCastAsCast($c, $expression, $expContext, $context)) ]->getValues(true)->first()->toOne()->eval()->evaluateAndDeactivate(); ]); @@ -1463,6 +1473,7 @@ function <> meta::external::query::sql::transformation::queryToP pair('DATE', | possiblyProcessParseDate($v)), pair('INTEGER', | sfe(parseInteger_String_1__Integer_1_, $v)), pair('BIGINT', | sfe(parseInteger_String_1__Integer_1_, $v)), + pair('SMALLINT', | sfe(parseInteger_String_1__Integer_1_, $v)), pair('DOUBLE PRECISION', | sfe(parseFloat_String_1__Float_1_, $v)), pair('BOOLEAN', | sfe(parseBoolean_String_1__Boolean_1_, $v)), pair('NUMERIC', | sfe(parseDecimal_String_1__Decimal_1_, $v)), @@ -1481,11 +1492,11 @@ function <> meta::external::query::sql::transformation::queryToP function <> meta::external::query::sql::transformation::queryToPure::possiblyProcessToString(v:ValueSpecification[1]):ValueSpecification[1] { let type = $v->evaluateAndDeactivate().genericType.rawType; - if ($type == String, | $v, | sfe(toString_Any_1__String_1_, $v)); + if ($type == String, | $v, | sfe(toString_Any_1__String_1_, $v)); } //TODO revisit to fully push down -function meta::external::query::sql::transformation::queryToPure::processParseDate(v:ValueSpecification[1]):SimpleFunctionExpression[1] +function meta::external::query::sql::transformation::queryToPure::processParseDate(v:ValueSpecification[1]):ValueSpecification[1] { let value = if ($v->instanceOf(InstanceValue) && $v.multiplicity == PureOne && $v->cast(@InstanceValue).values->toOne()->instanceOf(String), | let string = $v->cast(@InstanceValue).values->at(0)->cast(@String); @@ -1494,7 +1505,7 @@ function meta::external::query::sql::transformation::queryToPure::processParseDa | $v);, | $v); - sfe(parseDate_String_1__Date_1_, $value); + nullOrSfe(parseDate_String_1__Date_1_, $value); } function <> meta::external::query::sql::transformation::queryToPure::processCastAsCast(c:Cast[1], v:ValueSpecification[1], expContext:SqlTransformExpressionContext[1], context:SqlTransformContext[1]):ValueSpecification[1] @@ -1507,13 +1518,19 @@ function <> meta::external::query::sql::transformation::queryToP createCast($v, $type); } + function <> meta::external::query::sql::transformation::queryToPure::createCast(expression:ValueSpecification[1], type:Type[1]):ValueSpecification[1] { let expressionType = $expression->evaluateAndDeactivate().genericType.rawType->toOne(); if (normalizeType($expressionType) == normalizeType($type), | $expression, - | sfe(cast_Any_m__T_1__T_m_, ^GenericType(rawType = $type), [], PureOne, [$expression, ^InstanceValue(genericType = ^GenericType(rawType = $type), multiplicity = PureOne)])); + | doCreateCast($expression, $type)); +} + +function <> meta::external::query::sql::transformation::queryToPure::doCreateCast(expression:ValueSpecification[1], type:Type[1]):ValueSpecification[1] +{ + sfe(cast_Any_m__T_1__T_m_, ^GenericType(rawType = $type), [], $expression->evaluateAndDeactivate().multiplicity, [$expression, ^InstanceValue(genericType = ^GenericType(rawType = $type), multiplicity = PureOne)]) } function <> meta::external::query::sql::transformation::queryToPure::getCastType(c:Cast[1]):Type[1] @@ -1521,9 +1538,10 @@ function <> meta::external::query::sql::transformation::queryToP [ pair('VARCHAR', String), pair('TEXT', String), - pair('DATE', Date), + pair('DATE', StrictDate), pair('INTEGER', Integer), pair('BIGINT', Integer), + pair('SMALLINT', Integer), pair('DOUBLE PRECISION', Float), pair('BOOLEAN', Boolean), pair('NUMERIC', Decimal), @@ -1554,7 +1572,7 @@ function <> meta::external::query::sql::transformation::queryToP processSearchedCaseExpression($sce, $expContext, $context); } -function <> meta::external::query::sql::transformation::queryToPure::processWhenClause(s:WhenClause[1], else:ValueSpecification[1], expContext:SqlTransformExpressionContext[1], context:SqlTransformContext[1]):SimpleFunctionExpression[1] +function <> meta::external::query::sql::transformation::queryToPure::processWhenClause(s:WhenClause[1], else:ValueSpecification[1], expContext:SqlTransformExpressionContext[1], context:SqlTransformContext[1]):ValueSpecification[1] { debug('processWhenClause', $context.debug); let condition = $s.operand->processExpression($expContext, $context); @@ -1573,12 +1591,11 @@ function <> meta::external::query::sql::transformation::queryToP ]); } -function <> meta::external::query::sql::transformation::queryToPure::createIfStatement(condition:ValueSpecification[1], truth:ValueSpecification[1], else:ValueSpecification[1]):SimpleFunctionExpression[1] +function <> meta::external::query::sql::transformation::queryToPure::createIfStatement(condition:ValueSpecification[1], truth:ValueSpecification[1], else:ValueSpecification[1]):ValueSpecification[1] { let truthMultiplicity = $truth->valueSpecificationMultiplicity(); let elseMultiplicity = $else->valueSpecificationMultiplicity(); - let truthLambda = lambda(^FunctionType(returnMultiplicity = $truthMultiplicity, returnType = $truth.genericType), $truth); let elseLambda = lambda(^FunctionType(returnMultiplicity = $elseMultiplicity, returnType = $else.genericType), $else); @@ -1586,7 +1603,7 @@ function <> meta::external::query::sql::transformation::queryToP let multiplicity = if ($truthMultiplicity == PureOne && $elseMultiplicity == PureOne, | PureOne, | ZeroOne); - sfe(if_Boolean_1__Function_1__Function_1__T_m_, $genericType, [], $multiplicity, [$condition, $truthLambda->iv(), $elseLambda->iv()])->evaluateAndDeactivate(); + nullOrSfe(if_Boolean_1__Function_1__Function_1__T_m_, $genericType, [], $multiplicity, [$condition, $truthLambda->iv(), $elseLambda->iv()])->evaluateAndDeactivate(); } function <> meta::external::query::sql::transformation::queryToPure::createTdsColumn(qualifiedName:QualifiedName[1], var:VariableExpression[1], expContext:SqlTransformExpressionContext[1], context: SqlTransformContext[1]):ValueSpecification[1] @@ -1755,7 +1772,7 @@ function meta::external::query::sql::transformation::queryToPure::functionProces pair(Decimal, abs_Decimal_1__Decimal_1_) ]->getValue($type, abs_Number_1__Number_1_); - sfe($func, $args); + nullOrSfe($func, $args); }), processor('acos', acos_Number_1__Float_1_), processor('asin', asin_Number_1__Float_1_), @@ -1793,7 +1810,7 @@ function meta::external::query::sql::transformation::queryToPure::functionProces assert(($args->size() == 1) || ($args->size() == 2), 'incorrect number of args for round'); let func = if ($args->size() == 1, | round_Number_1__Integer_1_, | round_Decimal_1__Integer_1__Decimal_1_); - sfe($func, $args); + nullOrSfe($func, $args); }), processor('sign', sign_Number_1__Integer_1_), processor('sin', sin_Number_1__Float_1_), @@ -1819,9 +1836,9 @@ function meta::external::query::sql::transformation::queryToPure::functionProces //TODO replace with pure trunc function when implemented; assert($args->size() == 1, | 'trunc with defined decimal places is not currently supported'); - let condition = sfe(greaterThan_Number_1__Number_1__Boolean_1_, [$args->at(0), iv(0)])->evaluateAndDeactivate(); - let truth = sfe(floor_Number_1__Integer_1_, $args->at(0))->evaluateAndDeactivate(); - let else = sfe(ceiling_Number_1__Integer_1_, $args->at(0))->evaluateAndDeactivate(); + let condition = nullOrSfe(greaterThan_Number_1__Number_1__Boolean_1_, [$args->at(0), iv(0)])->evaluateAndDeactivate(); + let truth = nullOrSfe(floor_Number_1__Integer_1_, $args->at(0))->evaluateAndDeactivate(); + let else = nullOrSfe(ceiling_Number_1__Integer_1_, $args->at(0))->evaluateAndDeactivate(); createIfStatement($condition, $truth, $else); }), @@ -1831,35 +1848,20 @@ function meta::external::query::sql::transformation::queryToPure::functionProces //STRING processor('ascii', ascii_String_1__Integer_1_), - processor('btrim', String, {args | - assert($args->size() == 1 || $args->size() == 2, 'incorrect number of args'); - assert($args->size() == 1 || ($args->at(1)->reactivate() == ' '), 'only empty string trim is currently supported'); - - sfe(trim_String_1__String_1_, $args->at(0)); - }), + processor('btrim', String, {args | processTrim(trim_String_1__String_1_, $args)}), processor('char_length', length_String_1__Integer_1_), processor('chr', char_Integer_1__String_1_), processor('concat', plus_String_MANY__String_1_), processor('length', length_String_1__Integer_1_), processor('lower', toLower_String_1__String_1_), - processor('ltrim', String, {args | - assert($args->size() == 1 || $args->size() == 2, 'incorrect number of args'); - assert($args->size() == 1 || ($args->at(1)->reactivate() == ' '), 'only empty string ltrim is currently supported'); - - sfe(ltrim_String_1__String_1_, $args->at(0)); - }), - processor('md5', String, {args | processHash($args, meta::pure::functions::hash::HashType.MD5)}), + processor('ltrim', String, {args | processTrim(ltrim_String_1__String_1_, $args)}), + processor('md5', String, {args | processHash($args, meta::pure::functions::hash::HashType.MD5)}), processor('regexp_like', matches_String_1__String_1__Boolean_1_), processor('repeat', repeatString_String_$0_1$__Integer_1__String_$0_1$_), processor('replace', replace_String_1__String_1__String_1__String_1_), processor('reverse', reverseString_String_1__String_1_), - processor('rtrim', String, {args | - assert($args->size() == 1 || $args->size() == 2, 'incorrect number of args'); - assert($args->size() == 1 || ($args->at(1)->reactivate() == ' '), 'only empty string rtrim is currently supported'); - - sfe(rtrim_String_1__String_1_, $args->at(0)); - }), - processor('sha256', String, {args | processHash($args, meta::pure::functions::hash::HashType.SHA256)}), + processor('rtrim', String, {args | processTrim(rtrim_String_1__String_1_, $args)}), + processor('sha256', String, {args | processHash($args, meta::pure::functions::hash::HashType.SHA256)}), processor('starts_with', startsWith_String_1__String_1__Boolean_1_), processor('string_agg', true, false, String, {args | @@ -1871,11 +1873,11 @@ function meta::external::query::sql::transformation::queryToPure::functionProces sfe($func, $args); }), processor('strpos', indexOf_String_1__String_1__Integer_1_), - processor('substr', false, processSubstring_ValueSpecification_MANY__SimpleFunctionExpression_1_), - processor('substring', false, processSubstring_ValueSpecification_MANY__SimpleFunctionExpression_1_), + processor('substr', false, processSubstring_ValueSpecification_MANY__ValueSpecification_1_), + processor('substring', false, processSubstring_ValueSpecification_MANY__ValueSpecification_1_), processor('upper', toUpper_String_1__String_1_), //DATE - processor('date', Date, {args | + processor('date', Date, {args | possiblyProcessParseDate($args->at(0)) }), processor('date_part', Integer, {args | @@ -1897,7 +1899,7 @@ function meta::external::query::sql::transformation::queryToPure::functionProces pair('epoch', toEpochValue_Date_1__Integer_1_) ]->getValue($value->toLower()); - sfe($func, $args->at(1)); + nullOrSfe($func, $args->at(1)); }), processor('date_trunc', Date, {args | assertEquals(2, $args->size(), 'incorrect number of args'); @@ -1915,7 +1917,7 @@ function meta::external::query::sql::transformation::queryToPure::functionProces pair('second', firstMillisecondOfSecond_Date_1__DateTime_1_) ]->getValue($value->toLower()); - sfe($func, $args->at(1)); + nullOrSfe($func, $args->at(1)); }), //COLLECTION @@ -1924,8 +1926,11 @@ function meta::external::query::sql::transformation::queryToPure::functionProces i:InstanceValue[1] | !($i.genericType.rawType == Nil && $i.values->isEmpty()), v:ValueSpecification[1] | true ])); - sfe(meta::pure::tds::extensions::firstNotNull_T_MANY__T_$0_1$_, ^GenericType(rawType = String), ^GenericType(rawType = String), $filteredArgs->iv()); - }), + + 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()); + }), //WINDOW processor('row_number', false, true, [], {args | @@ -1940,30 +1945,38 @@ function meta::external::query::sql::transformation::queryToPure::functionProces ] } -function meta::external::query::sql::transformation::queryToPure::processHash(args:ValueSpecification[*], type:meta::pure::functions::hash::HashType[1]):SimpleFunctionExpression[1] -{ +function meta::external::query::sql::transformation::queryToPure::processTrim(func:Function[1], args:ValueSpecification[*]):ValueSpecification[1] +{ + assert($args->size() == 1 || $args->size() == 2, 'incorrect number of args'); + assert($args->size() == 1 || ($args->at(1)->reactivate() == ' '), 'only empty string trim is currently supported'); + + nullOrSfe($func, $args->at(0)); +} + +function meta::external::query::sql::transformation::queryToPure::processHash(args:ValueSpecification[*], type:meta::pure::functions::hash::HashType[1]):ValueSpecification[1] +{ assert($args->size() == 1, 'incorrect number of args'); - sfe(meta::pure::functions::hash::hash_String_1__HashType_1__String_1_, [$args->at(0), processExtractEnumValue(meta::pure::functions::hash::HashType, $type.name->toOne())]); + nullOrSfe(meta::pure::functions::hash::hash_String_1__HashType_1__String_1_, [$args->at(0), processExtractEnumValue(meta::pure::functions::hash::HashType, $type.name->toOne())]); } -function meta::external::query::sql::transformation::queryToPure::processSubstring(args:ValueSpecification[*]):SimpleFunctionExpression[1] +function meta::external::query::sql::transformation::queryToPure::processSubstring(args:ValueSpecification[*]):ValueSpecification[1] { assert($args->size() == 2 || $args->size() == 3, 'invalid number of args for substring'); let func = if ($args->size() == 2, | substring_String_1__Integer_1__String_1_, | substring_String_1__Integer_1__Integer_1__String_1_); - sfe($func, $args); + nullOrSfe($func, $args); } -function <> meta::external::query::sql::transformation::queryToPure::processNotExpression(n:NotExpression[1], expContext:SqlTransformExpressionContext[1], context:SqlTransformContext[1]):SimpleFunctionExpression[1] +function <> meta::external::query::sql::transformation::queryToPure::processNotExpression(n:NotExpression[1], expContext:SqlTransformExpressionContext[1], context:SqlTransformContext[1]):ValueSpecification[1] { debug('processNotExpression', $context.debug); let value = $n.value->processExpression($expContext, $context); - sfe(not_Boolean_1__Boolean_1_, $value); + nullOrSfe(not_Boolean_1__Boolean_1_, $value); } -function <> meta::external::query::sql::transformation::queryToPure::processNegativeExpression(n:NegativeExpression[1], expContext:SqlTransformExpressionContext[1], context:SqlTransformContext[1]):SimpleFunctionExpression[1] +function <> meta::external::query::sql::transformation::queryToPure::processNegativeExpression(n:NegativeExpression[1], expContext:SqlTransformExpressionContext[1], context:SqlTransformContext[1]):ValueSpecification[1] { debug('processNegativeExpression', $context.debug); let value = $n.value->processExpression($expContext, $context); @@ -1998,8 +2011,12 @@ function <> meta::external::query::sql::transformation::queryToP if ($leftTypeNormalized == Date || $rightTypeNormalized == Date, | Date, | Number);, b:BetweenPredicate[1] | Boolean, c:Cast[1] | getCastType($c), - c:CurrentTime[1] | DateTime, c:ComparisonExpression[1] | Boolean, + c:CurrentTime[1] | [ + pair(CurrentTimeType.DATE, StrictDate), + pair(CurrentTimeType.TIME, DateTime), + pair(CurrentTimeType.TIMESTAMP, DateTime) + ]->getValue($c.type), e:Extract[1] | Integer, f:FunctionCall[1] | let processor = functionProcessor($f.name); @@ -2013,13 +2030,22 @@ function <> meta::external::query::sql::transformation::queryToP n:NegativeExpression[1] | getExpressionType($n.value, $context), n:NotExpression[1] | Boolean, q:QualifiedNameReference[1] | $context.columnByNameParts($q.name.parts, true).type->toOne(), - s:SearchedCaseExpression[1] | Any, - s:SimpleCaseExpression[1] | Any, + s:SearchedCaseExpression[1] | caseExpressionType($s.whenClauses, $s.defaultValue, $context), + s:SimpleCaseExpression[1] | caseExpressionType($s.whenClauses, $s.defaultValue, $context), e:meta::external::query::sql::metamodel::Expression[1] | Any ]); } -function <> meta::external::query::sql::transformation::queryToPure::processArithmeticExpression(a:ArithmeticExpression[1], expContext:SqlTransformExpressionContext[1], context:SqlTransformContext[1]):SimpleFunctionExpression[1] +function <> meta::external::query::sql::transformation::queryToPure::caseExpressionType(whenClauses:WhenClause[*], defaultValue:meta::external::query::sql::metamodel::Expression[0..1], context:SqlTransformContext[1]):Type[1] +{ + if ($whenClauses->isNotEmpty(), + | $whenClauses->at(0).result->getExpressionType($context), + | if ($defaultValue->isNotEmpty(), + | $defaultValue->toOne()->getExpressionType($context), + | Any)); +} + +function <> meta::external::query::sql::transformation::queryToPure::processArithmeticExpression(a:ArithmeticExpression[1], expContext:SqlTransformExpressionContext[1], context:SqlTransformContext[1]):ValueSpecification[1] { debug('processArithmeticExpression', $context.debug); @@ -2044,7 +2070,7 @@ function <> meta::external::query::sql::transformation::queryToP ]->getValue($t, $t); } -function <> meta::external::query::sql::transformation::queryToPure::processNumericArithmeticExpression(a:ArithmeticExpression[1], expContext:SqlTransformExpressionContext[1], context:SqlTransformContext[1]):SimpleFunctionExpression[1] +function <> meta::external::query::sql::transformation::queryToPure::processNumericArithmeticExpression(a:ArithmeticExpression[1], expContext:SqlTransformExpressionContext[1], context:SqlTransformContext[1]):ValueSpecification[1] { debug('processNumericArithmeticExpression', $context.debug); @@ -2055,7 +2081,7 @@ function <> meta::external::query::sql::transformation::queryToP let rightType = $right.genericType.rawType; let type = if ($leftType->isNotEmpty() && $leftType == $rightType, | $leftType->toOne(), | Number); - [ + let expression = [ pair(ArithmeticType.ADD, | sfe([ pair(Integer, plus_Integer_MANY__Integer_1_), pair(Float, plus_Float_MANY__Float_1_), @@ -2071,10 +2097,13 @@ function <> meta::external::query::sql::transformation::queryToP pair(Float, times_Float_MANY__Float_1_), pair(Decimal, times_Decimal_MANY__Decimal_1_) ]->getValue($type, times_Number_MANY__Number_1_), iv($left->concatenate($right)))), - pair(ArithmeticType.DIVIDE, | sfe(divide_Number_1__Number_1__Float_1_, [$left, $right])), - pair(ArithmeticType.MODULUS, | sfe(mod_Integer_1__Integer_1__Integer_1_, [$left, $right])), - pair(ArithmeticType.POWER, | sfe(pow_Number_1__Number_1__Number_1_, [$left, $right])) + pair(ArithmeticType.DIVIDE, | nullOrSfe(divide_Number_1__Number_1__Float_1_, [$left, $right])), + pair(ArithmeticType.MODULUS, | nullOrSfe(mod_Integer_1__Integer_1__Integer_1_, [$left, $right])), + pair(ArithmeticType.POWER, | nullOrSfe(pow_Number_1__Number_1__Number_1_, [$left, $right])) ]->getValue($a.type)->eval(); + + if ($left->isNull() || $right->isNull(), | iv([], $expression->evaluateAndDeactivate().genericType), | $expression); + } //purely internal class to ensure we can handle expressions in the interval calculation logic @@ -2123,7 +2152,7 @@ function <> meta::external::query::sql::transformation::queryToP if ($value->isNotEmpty(), | sfe(times_Integer_MANY__Integer_1_, $multiplier->concatenate($value->iv())->iv()), | []); } -function <> meta::external::query::sql::transformation::queryToPure::processDateArithmeticExpression(a:ArithmeticExpression[1], leftTypeNormalized:Type[1], rightTypeNormalized:Type[1], expContext:SqlTransformExpressionContext[1], context:SqlTransformContext[1]):SimpleFunctionExpression[1] +function <> meta::external::query::sql::transformation::queryToPure::processDateArithmeticExpression(a:ArithmeticExpression[1], leftTypeNormalized:Type[1], rightTypeNormalized:Type[1], expContext:SqlTransformExpressionContext[1], context:SqlTransformContext[1]):ValueSpecification[1] { debug('processDateArithmeticExpression', $context.debug); @@ -2148,15 +2177,15 @@ function <> meta::external::query::sql::transformation::queryToP pair($simplified.right->instanceOf(NullLiteral) || $simplified.left->instanceOf(NullLiteral), {| let left = $simplified.left->processExpression($expContext, $context); let cast = ^Cast(expression = ^NullLiteral(), type = ^ColumnType(name = 'DATE')); - processCastAsCast($cast, processExpression($cast, $expContext, $context), $expContext, $context)->cast(@SimpleFunctionExpression);}) + processCastAsCast($cast, processExpression($cast, $expContext, $context), $expContext, $context);}) ]->getValue(true, {| let left = $simplified.left->processExpression($expContext, $context); let right = $simplified.right->processExpression($expContext, $context); - sfe(adjust_Date_1__Integer_1__DurationUnit_1__Date_1_, [$left, $right, processExtractEnumValue(DurationUnit, DurationUnit.DAYS.name)]); + nullOrSfe(adjust_Date_1__Integer_1__DurationUnit_1__Date_1_, [$left, $right, processExtractEnumValue(DurationUnit, DurationUnit.DAYS.name)]); })->eval(); } -function <> meta::external::query::sql::transformation::queryToPure::processComparisonExpression(c:ComparisonExpression[1], expContext:SqlTransformExpressionContext[1], context:SqlTransformContext[1]):SimpleFunctionExpression[1] +function <> meta::external::query::sql::transformation::queryToPure::processComparisonExpression(c:ComparisonExpression[1], expContext:SqlTransformExpressionContext[1], context:SqlTransformContext[1]):ValueSpecification[1] { debug('processComparisonExpression', $context.debug); @@ -2174,7 +2203,7 @@ function <> meta::external::query::sql::transformation::queryToP } //we have to create this as SQL providers are working on varchars, not enums, so can form comparison operators safely -function <> meta::external::query::sql::transformation::queryToPure::processEnumComparison(c:ComparisonExpression[1], enum:Enumeration[1], left:ValueSpecification[1], right:ValueSpecification[1], type:Type[1], expContext:SqlTransformExpressionContext[1], context:SqlTransformContext[1]):SimpleFunctionExpression[1] +function <> meta::external::query::sql::transformation::queryToPure::processEnumComparison(c:ComparisonExpression[1], enum:Enumeration[1], left:ValueSpecification[1], right:ValueSpecification[1], type:Type[1], expContext:SqlTransformExpressionContext[1], context:SqlTransformContext[1]):ValueSpecification[1] { let enumValues = $enum->enumValues().name->sort(); @@ -2214,7 +2243,7 @@ function <> meta::external::query::sql::transformation::queryToP ]); } -function <> meta::external::query::sql::transformation::queryToPure::createStandardComparison(c:ComparisonExpression[1], left:ValueSpecification[1], right:ValueSpecification[1], type:Type[1]):SimpleFunctionExpression[1] +function <> meta::external::query::sql::transformation::queryToPure::createStandardComparison(c:ComparisonExpression[1], left:ValueSpecification[1], right:ValueSpecification[1], type:Type[1]):ValueSpecification[1] { let leftOne = $left.multiplicity->isToOne(); let rightOne = $right.multiplicity->isToOne(); @@ -2330,10 +2359,10 @@ function <> meta::external::query::sql::transformation::queryToP let sfe = sfe($func, [$left, $right]); - if ($c.operator == ComparisonOperator.NOT_EQUAL, | sfe(not_Boolean_1__Boolean_1_, $sfe), | $sfe); + if ($c.operator == ComparisonOperator.NOT_EQUAL, | nullOrSfe(not_Boolean_1__Boolean_1_, $sfe), | $sfe); } -function <> meta::external::query::sql::transformation::queryToPure::processStandardComparison(c:ComparisonExpression[1], left:ValueSpecification[1], right:ValueSpecification[1], type:Type[1], expContext:SqlTransformExpressionContext[1], context:SqlTransformContext[1]):SimpleFunctionExpression[1] +function <> meta::external::query::sql::transformation::queryToPure::processStandardComparison(c:ComparisonExpression[1], left:ValueSpecification[1], right:ValueSpecification[1], type:Type[1], expContext:SqlTransformExpressionContext[1], context:SqlTransformContext[1]):ValueSpecification[1] { [ pair(ComparisonOperator.IS_DISTINCT_FROM, | createIsDistinctFrom($left, $right)), @@ -2357,7 +2386,7 @@ function <> meta::external::query::sql::transformation::queryToP ]); } -function <> meta::external::query::sql::transformation::queryToPure::processIntervalToAdjust(input:ValueSpecification[1], i:IntervalLiteralWrapper[1], negate:Boolean[1]):SimpleFunctionExpression[1] +function <> meta::external::query::sql::transformation::queryToPure::processIntervalToAdjust(input:ValueSpecification[1], i:IntervalLiteralWrapper[1], negate:Boolean[1]):ValueSpecification[1] { let ago = $i.ago->isTrue() || $negate; @@ -2374,19 +2403,19 @@ function <> meta::external::query::sql::transformation::queryToP $i.days, DurationUnit.DAYS, $ago), $i.hours, DurationUnit.HOURS, $ago), $i.minutes, DurationUnit.MINUTES, $ago), - $i.seconds, DurationUnit.SECONDS, $ago)->cast(@SimpleFunctionExpression); + $i.seconds, DurationUnit.SECONDS, $ago); } function <> meta::external::query::sql::transformation::queryToPure::processIntervalLiteralValue(input:ValueSpecification[1], amount:ValueSpecification[0..1], unit:DurationUnit[1], ago:Boolean[1]):ValueSpecification[1] { if ($amount->isNotEmpty(), | - let adjustedAmount = if ($ago, | sfe(minus_Number_MANY__Number_1_, $amount->toOne()), | $amount); - sfe(adjust_Date_1__Integer_1__DurationUnit_1__Date_1_, [$input, iv($adjustedAmount->toOne()), processExtractEnumValue(DurationUnit, $unit.name)]);, + let adjustedAmount = if ($ago, | nullOrSfe(minus_Number_MANY__Number_1_, $amount->toOne()), | $amount); + nullOrSfe(adjust_Date_1__Integer_1__DurationUnit_1__Date_1_, [$input, iv($adjustedAmount->toOne()), processExtractEnumValue(DurationUnit, $unit.name)]);, | $input) } -function <> meta::external::query::sql::transformation::queryToPure::processLogicalBinaryExpression(l:LogicalBinaryExpression[1], expContext:SqlTransformExpressionContext[1], context:SqlTransformContext[1]):SimpleFunctionExpression[1] +function <> meta::external::query::sql::transformation::queryToPure::processLogicalBinaryExpression(l:LogicalBinaryExpression[1], expContext:SqlTransformExpressionContext[1], context:SqlTransformContext[1]):ValueSpecification[1] { debug('processLogicalBinaryExpression', $context.debug); let left = $l.left->processExpression($expContext, $context); @@ -2397,7 +2426,7 @@ function <> meta::external::query::sql::transformation::queryToP pair(LogicalBinaryType.OR, or_Boolean_1__Boolean_1__Boolean_1_) ]->getValue($l.type); - sfe($func, [$left, $right]); + nullOrSfe($func, [$left, $right]); } @@ -2411,6 +2440,39 @@ function <> meta::external::query::sql::transformation::queryToP sfe($execFunc, $pvs); } + +function <> meta::external::query::sql::transformation::queryToPure::nullOrSfe(func: meta::pure::metamodel::function::Function[1], pvs:ValueSpecification[*]):ValueSpecification[1] +{ + nullOrSfe($func, [], $pvs); +} + +function <> meta::external::query::sql::transformation::queryToPure::nullOrSfe(func: meta::pure::metamodel::function::Function[1], resolvedTypeParameters:GenericType[*], pvs:ValueSpecification[*]):ValueSpecification[1] +{ + nullOrSfe($func, $func->functionReturnType(), $resolvedTypeParameters, $pvs); +} + +function <> meta::external::query::sql::transformation::queryToPure::nullOrSfe(func: meta::pure::metamodel::function::Function[1], genericType:GenericType[1], resolvedTypeParameters:GenericType[*], pvs:ValueSpecification[*]):ValueSpecification[1] +{ + nullOrSfe($func, $genericType, $resolvedTypeParameters, $func->functionReturnMultiplicity(), $pvs) +} + +function <> meta::external::query::sql::transformation::queryToPure::nullOrSfe(func: meta::pure::metamodel::function::Function[1], genericType:GenericType[1], resolvedTypeParameters:GenericType[*], multiplicity: Multiplicity[1], pvs:ValueSpecification[*]):ValueSpecification[1] +{ + let zipped = $func->getParameters()->evaluateAndDeactivate()->zip($pvs); + let null = $zipped->exists(p | $p.first.multiplicity->hasLowerBound() && $p.second->isNull()); + + if ($null, + | let i = iv([], $genericType), + | sfe($func, $genericType, $resolvedTypeParameters, $multiplicity, $pvs)); +} + + +function <> meta::external::query::sql::transformation::queryToPure::isNull(v:ValueSpecification[1]):Boolean[1] +{ + $v->evaluateAndDeactivate().multiplicity == PureZero; +} + + function <> meta::external::query::sql::transformation::queryToPure::sfe(func: meta::pure::metamodel::function::Function[1], pvs:ValueSpecification[*]):SimpleFunctionExpression[1] { sfe($func, [], $pvs); @@ -2688,7 +2750,7 @@ function <> meta::external::query::sql::transformation::queryToP function meta::external::query::sql::transformation::queryToPure::simpleFunctionTransform(func:Function[1]):Function<{ValueSpecification[*]->ValueSpecification[1]}>[1] { - {args:ValueSpecification[*] | sfe($func, $args)} + {args:ValueSpecification[*] | nullOrSfe($func, $args)} } //MISC @@ -2724,4 +2786,4 @@ function meta::external::query::sql::transformation::queryToPure::getParameters( function meta::external::query::sql::transformation::queryToPure::debug(a:String[1], debug:DebugContext[1]):Any[0] { if ($debug.debug, | println($debug.space + $a), | []); -} +} \ No newline at end of file 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 7faf38e0e88..28450da2621 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 @@ -242,6 +242,23 @@ function <> meta::external::query::sql::transformation::queryToPure:: assertLambdaAndJSONEquals($expected, $sqlTransformContext.lambda()); } +function <> meta::external::query::sql::transformation::queryToPure::tests::testSimplificationOfNullablesComplex():Boolean[1] +{ + let sqlString = 'SELECT (date_trunc(\'day\', CAST(\'1900-01-01\' AS DATE) + NULL * INTERVAL \'1 DAY\') + 1 * INTERVAL \'1 YEAR\') AS "NULL1", ' + + 'CAST( FLOOR(EXTRACT(EPOCH FROM CAST((CAST(\'1900-01-01 00:00:00\' AS TIMESTAMP) + NULL * INTERVAL \'1 DAY\') AS TIMESTAMP)) / 86400) - FLOOR(EXTRACT(EPOCH FROM CAST((CAST(\'1900-01-01 00:00:00\' AS TIMESTAMP) + NULL * INTERVAL \'1 DAY\') AS TIMESTAMP)) / 86400) AS BIGINT) AS "NULL2" FROM service."/service/service1"'; + let sqlTransformContext = $sqlString->processQuery(); + + let expected = {| 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] | []->cast(@Date), 'NULL1'), + col(row:TDSRow[1] | []->cast(@Integer), 'NULL2') + ]) + }; + + assertLambdaAndJSONEquals($expected, $sqlTransformContext.lambda()); +} + //DISTINCT function <> meta::external::query::sql::transformation::queryToPure::tests::testDistinctAllColumns():Boolean[1] { @@ -610,7 +627,7 @@ function <> meta::external::query::sql::transformation::queryToPure:: 'min(Integer) AS "minInt", min(StrictDate) AS "minDate", min(String) AS "minString", ' + 'max(Integer) AS "maxInt", max(StrictDate) AS "maxDate", max(String) AS "maxString", ' + 'string_agg(String) AS "stringAgg", string_agg(cast(Integer AS VARCHAR), \' \') AS "stringAgg2" FROM service."/service/service1" GROUP BY String'; - let sqlTransformContext = $sqlString->processQuery(); + let sqlTransformContext = $sqlString->processQuery(); let expected = {| 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' ] @@ -624,7 +641,7 @@ function <> meta::external::query::sql::transformation::queryToPure:: agg('stdDev', row | $row.getInteger('Integer'), y | $y->stdDevSample()), agg('variancePop', row | $row.getInteger('Integer'), y | $y->variancePopulation()), agg('varianceSamp', row | $row.getInteger('Integer'), y | $y->varianceSample()), - agg('variance', row | $row.getInteger('Integer'), y | $y->varianceSample()), + agg('variance', row | $row.getInteger('Integer'), y | $y->varianceSample()), agg('minInt', row | $row.getInteger('Integer'), y | $y->min()), agg('minDate', row | $row.getStrictDate('StrictDate'), y | $y->min()), agg('minString', row | $row.getString('String'), y | $y->min()), @@ -777,8 +794,9 @@ function <> meta::external::query::sql::transformation::queryToPure:: { let sqlString = 'SELECT' + ' CAST(\'2023-01-01\' AS DATE) AS "constant", CAST(String AS VARCHAR) AS "string", CAST(String AS TEXT) AS "text", CAST(String AS DATE) AS "date",' - + ' CAST(String AS INTEGER) AS "integer", CAST(String AS BOOLEAN) AS "boolean", CAST(String AS DOUBLE PRECISION) AS "double", CAST(String AS NUMERIC) AS "numeric",' - + ' CAST(String AS TIMESTAMP) AS "timestamp", CAST(Integer AS VARCHAR) AS "integerString", CAST(Integer AS Integer) AS "expression",' + + ' CAST(String AS INTEGER) AS "integer", CAST(String AS BIGINT) AS "bigint", CAST(String AS SMALLINT) AS "smallint", CAST(String AS BOOLEAN) AS "boolean", ' + + ' CAST(String AS DOUBLE PRECISION) AS "double", CAST(String AS NUMERIC) AS "numeric",' + + ' CAST(String AS TIMESTAMP) AS "timestamp", CAST(Integer AS TEXT) AS "integerText", CAST(Integer AS VARCHAR) AS "integerString", CAST(Integer AS Integer) AS "expression",' + ' CAST(String AS VARCHAR(2)) AS "stringChars", CAST(Integer AS VARCHAR(2)) AS "integerStringChars",' + ' CAST(Float AS NUMERIC) AS "floatNumeric", CAST(Decimal AS NUMERIC) AS "decimalNumeric", CAST(Decimal AS DOUBLE PRECISION) AS "decimalDoublePrecision",' + ' CAST(Float AS DOUBLE PRECISION) AS "floatDoublePrecision", CAST(String AS NUMERIC(4, 2)) AS "numericParams" FROM service."/service/service1"'; @@ -792,10 +810,13 @@ function <> meta::external::query::sql::transformation::queryToPure:: col(row:TDSRow[1] | $row.getString('String'), 'text'), col(row:TDSRow[1] | parseDate($row.getString('String')), 'date'), col(row:TDSRow[1] | parseInteger($row.getString('String')), 'integer'), + col(row:TDSRow[1] | parseInteger($row.getString('String')), 'bigint'), + col(row:TDSRow[1] | parseInteger($row.getString('String')), 'smallint'), col(row:TDSRow[1] | parseBoolean($row.getString('String')), 'boolean'), col(row:TDSRow[1] | parseFloat($row.getString('String')), 'double'), col(row:TDSRow[1] | parseDecimal($row.getString('String')), 'numeric'), col(row:TDSRow[1] | parseDate($row.getString('String')), 'timestamp'), + col(row:TDSRow[1] | toString($row.getInteger('Integer')), 'integerText'), col(row:TDSRow[1] | toString($row.getInteger('Integer')), 'integerString'), col(row:TDSRow[1] | $row.getInteger('Integer'), 'expression'), col(row:TDSRow[1] | substring($row.getString('String'), 1, 2), 'stringChars'), @@ -842,7 +863,7 @@ function <> meta::external::query::sql::transformation::queryToPure:: )->project([ col(row:TDSRow[1] | cast([], @String), 'string'), col(row:TDSRow[1] | cast([], @String), 'text'), - col(row:TDSRow[1] | cast([], @Date), 'date'), + col(row:TDSRow[1] | cast([], @StrictDate), 'date'), col(row:TDSRow[1] | cast([], @Integer), 'integer'), col(row:TDSRow[1] | cast([], @Boolean), 'boolean'), col(row:TDSRow[1] | cast([], @Float), 'double'), @@ -912,6 +933,28 @@ function <> meta::external::query::sql::transformation::queryToPure:: assertLambdaAndJSONEquals($expected, $sqlTransformContext.lambda()); } +function <> meta::external::query::sql::transformation::queryToPure::tests::testArithmeticNullable():Boolean[1] +{ + let sqlString = 'SELECT 1 + NULL AS "plus", 1 - NULL AS "minus", 1 * NULL AS "multiply", 1 / NULL AS "divide", 1 % NULL AS "mod", 1 ^ NULL AS "pow" FROM service."/service/service1"'; + + let sqlTransformContext = $sqlString->processQuery(); + let expected = {| 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] | []->cast(@Number), 'plus'), + col(row:TDSRow[1] | []->cast(@Number), 'minus'), + col(row:TDSRow[1] | []->cast(@Number), 'multiply'), + col(row:TDSRow[1] | []->cast(@Float), 'divide'), + col(row:TDSRow[1] | []->cast(@Integer), 'mod'), + col(row:TDSRow[1] | []->cast(@Number), 'pow') + ] + ) + }; + assertLambdaAndJSONEquals($expected, $sqlTransformContext.lambda()); +} + //JOIN function <> meta::external::query::sql::transformation::queryToPure::tests::testLeftJoinSubqueryOn():Boolean[1] { @@ -1105,7 +1148,7 @@ function <> meta::external::query::sql::transformation::queryToPure:: //DATE_TRUNC function <> meta::external::query::sql::transformation::queryToPure::tests::testDateTrunc():Boolean[1] { - let sqlString = 'SELECT date_trunc(\'year\', StrictDate) AS "YEAR", date_trunc(\'quarter\', CAST(\'2023-01-01\' AS DATE)) AS "QUARTER", date_trunc(\'month\', StrictDate) AS "MONTH", date_trunc(\'week\', StrictDate) AS "WEEK", ' + + let sqlString = 'SELECT date_trunc(\'year\', StrictDate) AS "YEAR", date_trunc(\'quarter\', CAST(\'2023-01-01\' AS DATE)) AS "QUARTER", date_trunc(\'month\', StrictDate) AS "MONTH", date_trunc(\'week\', StrictDate) AS "WEEK", ' + 'date_trunc(\'day\', StrictDate) AS "DAY", date_trunc(\'hour\', StrictDate) AS "HOUR", date_trunc(\'minute\', StrictDate) AS "MINUTE", date_trunc(\'second\', StrictDate) AS "SECOND" FROM service."/service/service1"'; let sqlTransformContext = $sqlString->processQuery(); @@ -1214,8 +1257,8 @@ function <> meta::external::query::sql::transformation::queryToPure:: col(row:TDSRow[1] | $row.getStrictDate('StrictDate')->adjust(1, DurationUnit.DAYS)->adjust(1, DurationUnit.YEARS)->adjust(3, DurationUnit.WEEKS)->adjust(2, DurationUnit.DAYS), 'INTERVAL_MULTI_ADD'), col(row:TDSRow[1] | $row.getStrictDate('StrictDate')->adjust(1, DurationUnit.DAYS), 'NUMERIC_ADD'), col(row:TDSRow[1] | $row.getStrictDate('StrictDate')->adjust(6 * 1, DurationUnit.YEARS)->adjust(6 * 3, DurationUnit.WEEKS)->adjust(6 * 2, DurationUnit.DAYS), 'INTERVAL_TIMES'), - col(row:TDSRow[1] | []->cast(@Date), 'INTERVAL_TIMES_NULL'), - col(row:TDSRow[1] | []->cast(@Date), 'INTERVAL_ADD_NULL'), + col(row:TDSRow[1] | []->cast(@StrictDate), 'INTERVAL_TIMES_NULL'), + col(row:TDSRow[1] | []->cast(@StrictDate), 'INTERVAL_ADD_NULL'), col(row:TDSRow[1] | parseDate('2023-01-01')->adjust(2 * 1, DurationUnit.DAYS)->adjust(3 * 2, DurationUnit.DAYS), 'INTERVAL_MIX'), col(row:TDSRow[1] | $row.getStrictDate('StrictDate')->adjust(year($row.getStrictDate('StrictDate')) * 2, DurationUnit.YEARS)->adjust(year($row.getStrictDate('StrictDate')) * 3, DurationUnit.DAYS), 'INTERVAL_MIX2'), col(row:TDSRow[1] | $row.getStrictDate('StrictDate')->firstHourOfDay()->adjust(($row.getStrictDate('StrictDate')->dayOfWeekNumber() * 1), DurationUnit.DAYS), 'INTERVAL_MIX3') @@ -1227,8 +1270,8 @@ function <> meta::external::query::sql::transformation::queryToPure:: //STRING FUNCTIONS function <> meta::external::query::sql::transformation::queryToPure::tests::testStringFunctions():Boolean[1] { - let sqlString = 'SELECT ascii(String) AS "ASCII", chr(Integer) AS "CHR", regexp_like(String, \'test\') AS "MATCH", char_length(String) AS "CHAR_LENGTH", length(String) AS "LENGTH", ltrim(String) AS "LTRIM", ltrim(String, \' \') AS "LTRIM2", md5(String) AS "MD5", upper(String) AS "UPPER", ' + - 'lower(String) AS "LOWER", repeat(String, 2) AS "REPEAT", replace(String, \'A\', \'a\') AS "REPLACE", starts_with(String, \'a\') AS "STARTSWITH", strpos(String, \'abc\') AS "STRPOS", reverse(String) AS "REVERSE", rtrim(String) AS "RTRIM", rtrim(String, \' \') AS "RTRIM2", ' + + let sqlString = 'SELECT ascii(String) AS "ASCII", chr(Integer) AS "CHR", regexp_like(String, \'test\') AS "MATCH", char_length(String) AS "CHAR_LENGTH", length(String) AS "LENGTH", ltrim(String) AS "LTRIM", ltrim(String, \' \') AS "LTRIM2", md5(String) AS "MD5", upper(String) AS "UPPER", ' + + 'lower(String) AS "LOWER", repeat(String, 2) AS "REPEAT", replace(String, \'A\', \'a\') AS "REPLACE", starts_with(String, \'a\') AS "STARTSWITH", strpos(String, \'abc\') AS "STRPOS", reverse(String) AS "REVERSE", rtrim(String) AS "RTRIM", rtrim(String, \' \') AS "RTRIM2", ' + 'sha256(String) AS "SHA256", substring(String, 1) AS "SUBSTRING", substr(String, 1, 2) AS "SUBSTR", btrim(String) AS "TRIM", btrim(String, \' \') AS "TRIM2" FROM service."/service/service1"'; let sqlTransformContext = $sqlString->processQuery(); @@ -1265,11 +1308,51 @@ function <> meta::external::query::sql::transformation::queryToPure:: assertLambdaAndJSONEquals($expected, $sqlTransformContext.lambda()); } +function <> meta::external::query::sql::transformation::queryToPure::tests::testStringFunctionsNullable():Boolean[1] +{ + let sqlString = 'SELECT ascii(NULL) AS "ASCII", chr(NULL) AS "CHR", regexp_like(NULL, \'test\') AS "MATCH", char_length(NULL) AS "CHAR_LENGTH", length(NULL) AS "LENGTH", ltrim(NULL) AS "LTRIM", ltrim(NULL, \' \') AS "LTRIM2", md5(NULL) AS "MD5", upper(NULL) AS "UPPER", ' + + 'lower(NULL) AS "LOWER", replace(NULL, \'A\', \'a\') AS "REPLACE", starts_with(NULL, \'a\') AS "STARTSWITH", strpos(NULL, \'abc\') AS "STRPOS", reverse(NULL) AS "REVERSE", rtrim(NULL) AS "RTRIM", rtrim(NULL, \' \') AS "RTRIM2", ' + + 'sha256(NULL) AS "SHA256", substring(NULL, 1) AS "SUBSTRING", substr(NULL, 1, 2) AS "SUBSTR", btrim(NULL) AS "TRIM", btrim(NULL, \' \') AS "TRIM2" FROM service."/service/service1"'; + + let sqlTransformContext = $sqlString->processQuery(); + let expected = {| + 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] | []->cast(@Integer), 'ASCII'), + col(row:TDSRow[1] | []->cast(@String), 'CHR'), + col(row:TDSRow[1] | []->cast(@Boolean), 'MATCH'), + col(row:TDSRow[1] | []->cast(@Integer), 'CHAR_LENGTH'), + col(row:TDSRow[1] | []->cast(@Integer), 'LENGTH'), + col(row:TDSRow[1] | []->cast(@String), 'LTRIM'), + col(row:TDSRow[1] | []->cast(@String), 'LTRIM2'), + col(row:TDSRow[1] | []->cast(@String), 'MD5'), + col(row:TDSRow[1] | []->cast(@String), 'UPPER'), + col(row:TDSRow[1] | []->cast(@String), 'LOWER'), + col(row:TDSRow[1] | []->cast(@String), 'REPLACE'), + col(row:TDSRow[1] | []->cast(@Boolean), 'STARTSWITH'), + col(row:TDSRow[1] | []->cast(@Integer), 'STRPOS'), + col(row:TDSRow[1] | []->cast(@String), 'REVERSE'), + col(row:TDSRow[1] | []->cast(@String), 'RTRIM'), + col(row:TDSRow[1] | []->cast(@String), 'RTRIM2'), + col(row:TDSRow[1] | []->cast(@String), 'SHA256'), + col(row:TDSRow[1] | []->cast(@String), 'SUBSTRING'), + col(row:TDSRow[1] | []->cast(@String), 'SUBSTR'), + col(row:TDSRow[1] | []->cast(@String), 'TRIM'), + col(row:TDSRow[1] | []->cast(@String), 'TRIM2') + ]) + }; + assertLambdaAndJSONEquals($expected, $sqlTransformContext.lambda()); +} + + //MATH FUNCTIONS function <> meta::external::query::sql::transformation::queryToPure::tests::testMathFunctions():Boolean[1] { let sqlString = 'SELECT abs(Integer) AS "ABS", acos(Integer) AS "ACOS", asin(Integer) AS "ASIN", atan(Integer) AS "ATAN", atan2(Integer, 1) AS "ATAN2", ' + - 'ceil(Integer) AS "CEIL", ceiling(Integer) AS "CEILING", cos(Integer) AS "COS", cot(Integer) AS "COT", degrees(Integer) AS "DEGREES", div(Integer, 1) AS "DIV", exp(Integer) AS "EXP", floor(Float) AS "FLOOR", ' + + 'ceil(Integer) AS "CEIL", ceiling(Integer) AS "CEILING", cos(Integer) AS "COS", cot(Integer) AS "COT", degrees(Integer) AS "DEGREES", div(Integer, 1) AS "DIV", exp(Integer) AS "EXP", floor(Float) AS "FLOOR", ' + 'log(Integer) AS "LOG", ln(Integer) AS "LN", mod(Integer, 1) AS "MOD", pi() AS "PI", power(Integer, 2) AS "POWER", radians(Integer) AS "RADIANS", round(Integer) AS "ROUND", round(Decimal, 1) AS "ROUND_DEC", sign(Integer) AS "SIGN", ' + 'sin(Integer) AS "SIN", sqrt(Integer) AS "SQRT", tan(Integer) AS "TAN", trunc(Integer) AS "TRUNC" FROM service."/service/service1"'; @@ -1311,6 +1394,50 @@ function <> meta::external::query::sql::transformation::queryToPure:: assertLambdaAndJSONEquals($expected, $sqlTransformContext.lambda()); } +function <> meta::external::query::sql::transformation::queryToPure::tests::testMathFunctionsNullable():Boolean[1] +{ + let sqlString = 'SELECT abs(NULL) AS "ABS", acos(NULL) AS "ACOS", asin(NULL) AS "ASIN", atan(NULL) AS "ATAN", atan2(NULL, 1) AS "ATAN2", ' + + 'ceil(NULL) AS "CEIL", ceiling(NULL) AS "CEILING", cos(NULL) AS "COS", cot(NULL) AS "COT", degrees(NULL) AS "DEGREES", div(NULL, 1) AS "DIV", exp(NULL) AS "EXP", floor(NULL) AS "FLOOR", ' + + 'log(NULL) AS "LOG", ln(NULL) AS "LN", mod(NULL, 1) AS "MOD", power(NULL, 2) AS "POWER", radians(NULL) AS "RADIANS", round(NULL) AS "ROUND", round(NULL, 1) AS "ROUND_DEC", sign(NULL) AS "SIGN", ' + + 'sin(NULL) AS "SIN", sqrt(NULL) AS "SQRT", tan(NULL) AS "TAN", trunc(NULL) AS "TRUNC" FROM service."/service/service1"'; + + let sqlTransformContext = $sqlString->processQuery(); + let expected = {| + 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] | []->cast(@Number), 'ABS'), + col(row:TDSRow[1] | []->cast(@Float), 'ACOS'), + col(row:TDSRow[1] | []->cast(@Float), 'ASIN'), + col(row:TDSRow[1] | []->cast(@Float), 'ATAN'), + col(row:TDSRow[1] | []->cast(@Float), 'ATAN2'), + col(row:TDSRow[1] | []->cast(@Integer), 'CEIL'), + col(row:TDSRow[1] | []->cast(@Integer), 'CEILING'), + col(row:TDSRow[1] | []->cast(@Float), 'COS'), + col(row:TDSRow[1] | []->cast(@Float), 'COT'), + col(row:TDSRow[1] | []->cast(@Float), 'DEGREES'), + col(row:TDSRow[1] | []->cast(@Float), 'DIV'), + col(row:TDSRow[1] | []->cast(@Float), 'EXP'), + col(row:TDSRow[1] | []->cast(@Integer), 'FLOOR'), + col(row:TDSRow[1] | []->cast(@Float), 'LOG'), + col(row:TDSRow[1] | []->cast(@Float), 'LN'), + col(row:TDSRow[1] | []->cast(@Number), 'MOD'), + col(row:TDSRow[1] | []->cast(@Number), 'POWER'), + col(row:TDSRow[1] | []->cast(@Float), 'RADIANS'), + col(row:TDSRow[1] | []->cast(@Integer), 'ROUND'), + col(row:TDSRow[1] | []->cast(@Decimal), 'ROUND_DEC'), + col(row:TDSRow[1] | []->cast(@Integer), 'SIGN'), + col(row:TDSRow[1] | []->cast(@Float), 'SIN'), + col(row:TDSRow[1] | []->cast(@Float), 'SQRT'), + col(row:TDSRow[1] | []->cast(@Float), 'TAN'), + col(row:TDSRow[1] | []->cast(@Integer), 'TRUNC') + ]) + }; + assertLambdaAndJSONEquals($expected, $sqlTransformContext.lambda()); +} + //COLLECTION FUNCTIONS function <> meta::external::query::sql::transformation::queryToPure::tests::testCollectionFunctions():Boolean[1] {