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 965871119a0..ec5bd2c906c 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 @@ -228,7 +228,9 @@ function <> meta::external::query::sql::transformation::queryToP let newExp = appendTdsFunc($left.expression->toOne(), concatenate_TabularDataSet_1__TabularDataSet_1__TabularDataSet_1_, [list($right.expression->toOne())]); - ^$context(expression = $newExp, contexts = [$left, $right]); + let aliases = $left.aliases->concatenate($right.aliases)->removeDuplicates(); + + ^$context(expression = $newExp, contexts = [$left, $right], aliases = $aliases); } function <> meta::external::query::sql::transformation::queryToPure::processQuerySpec(querySpec: QuerySpecification[1], context: SqlTransformContext[1]): SqlTransformContext[1] @@ -236,10 +238,15 @@ function <> meta::external::query::sql::transformation::queryToP debug('processQuerySpec', $context.debug); let from = $querySpec.from->processFrom($context); + //if the from context comes back and is still the same (ie no subquery, realias etc) then we can use that directly, else we + //need to subnest the context + let fromContext = if ($from.root || $from.name == $context.name, | $from, | ^$context(contexts = ^$from(contexts = []), expression = $from.expression)); + + let query = $querySpec.limit->processLimitOffset($querySpec.offset, $querySpec.orderBy->processOrderBy($querySpec.select.selectItems, $querySpec.having->processHaving($querySpec.select, - $querySpec.select->processProjection($querySpec.groupBy, $querySpec.having, $querySpec.where->processWhere($from)) + $querySpec.select->processProjection($querySpec.groupBy, $querySpec.having, $querySpec.orderBy.sortKey, $querySpec.where->processWhere($fromContext)) ) ) ); @@ -253,7 +260,10 @@ function <> meta::external::query::sql::transformation::queryToP let alias = $query.alias($nonAliasedName, $s.alias, true)->toOne(); let finalAlias = $query.alias($alias.name, $s.alias, false); - ^$alias(alias = if ($finalAlias->isEmpty(), | $alias.alias, | $finalAlias.alias));, + + let realias = if ($alias.realias->isNotEmpty(), | $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), @@ -268,15 +278,17 @@ function <> meta::external::query::sql::transformation::queryToP expression = ^QualifiedNameReference(name = ^QualifiedName(parts = if ($clash, | $p.expected, | $p.actual)))); ); - //if this is the root context we want to align the schema (order and name) with what has been specified in the sql let currentSchema = $query.columns.name; - let toRename = $expected->filter(sc | $sc.expression->extractNameFromExpression([])->in($currentSchema)); - let renamed = if ($query.root && $expected.alias != $currentSchema, | ^$query(expression = processRename($toRename, $query)), | $query); + let renamed = if ($expected.alias != $currentSchema, | ^$query(expression = processRename($expected->filter(e | !$e.alias->in($currentSchema)), $query)), | $query); - if ($renamed.columns.name != $expected.alias->removeDuplicates() && $expected.alias->isNotEmpty(), + let final = if ($renamed.columns.name != $expected.alias->removeDuplicates() && $expected.alias->isNotEmpty(), | ^$query(expression = processRestrict($expected.alias, $renamed)), | $renamed); + + let aliases = $final.columns.name->map(c | ^SQLColumnAlias(name=$c)); + + ^$final(aliases = $aliases); } function <> meta::external::query::sql::transformation::queryToPure::extractAggregatesFromExpression(expression:meta::external::query::sql::metamodel::Expression[0..1]):meta::external::query::sql::metamodel::Expression[*] @@ -300,20 +312,47 @@ function <> meta::external::query::sql::transformation::queryToP } -function <> meta::external::query::sql::transformation::queryToPure::processProjection(select: Select[1], groupBy: meta::external::query::sql::metamodel::Expression[*], having:meta::external::query::sql::metamodel::Expression[0..1], context: SqlTransformContext[1]): SqlTransformContext[1] +function <> meta::external::query::sql::transformation::queryToPure::processProjection(originalSelect: Select[1], groupBy: meta::external::query::sql::metamodel::Expression[*], having:meta::external::query::sql::metamodel::Expression[0..1], orderBy:meta::external::query::sql::metamodel::Expression[*], context: SqlTransformContext[1]): SqlTransformContext[1] { debug('processProjection', $context.debug); - let aggregates = $select.selectItems->filter(si | $si->isSelectItemAggregate()); - let windows = $select.selectItems->filter(si | $si->isSelectItemWindow())->cast(@SingleColumn); - let standard = $select.selectItems->removeAll($aggregates)->removeAll($windows); + let aggregates = $originalSelect.selectItems->filter(si | $si->isSelectItemAggregate()); + 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->isSelectItemColumnReference() && !$si->isSelectItemAggregate()); + let standardExtensions = $standard->filter(si | !$si->isNonAliasedSelectItemColumnReference() && !$si->isSelectItemAggregate()); let windowExtensions = extractWindowExtensionExpressions($windows); - let extensions = $standardExtensions->concatenate($windowExtensions); + let columns = $context.columns.name; + + 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)); + ); + + let selectItems = $originalSelect.selectItems->map(si | + let extension = $extensionPairs->filter(e | $e.first == $si)->first(); + if ($extension.first == $extension.second, | $si, | $extension->toOne().second); + ); + + let extensions = $extensionPairs.second; + + 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 ealias = extractAliasFromColumn($eis->cast(@SingleColumn)); + + if ($si != $eis, | ^$alias(realias = $ealias.actual), | $alias);, + a:AllColumns[1] | $context.aliases->map(a | ^SQLColumnAlias(name = $a.actual)) + ]); + ); + + let select = ^$originalSelect(selectItems = $selectItems); let isAggregate = $groupBy->isNotEmpty() || anyColumnAggregate($select); let isWindow = $windows->isNotEmpty(); @@ -338,13 +377,6 @@ function <> meta::external::query::sql::transformation::queryToP | appendTdsFunc($olapGroupBy, distinct_TabularDataSet_1__TabularDataSet_1_, []), | $olapGroupBy); - let aliases = $select.selectItems->map(si | - $si->match([ - s:SingleColumn[1] | extractAliasFromColumn($s), - a:AllColumns[1] | $context.aliases->map(a | ^SQLColumnAlias(name = $a.name)); - ]) - ); - ^$context(expression = $distinctExp, aliases = $aliases); } @@ -374,23 +406,10 @@ function <> meta::external::query::sql::transformation::queryToP let groupByColumns = $groupBy->map(g | $g->extractColumnNameFromExpression($select.selectItems, $context)); - - let aggregates = $select.selectItems->filter(s | $s->isSelectItemAggregate() && !$s->isSelectItemWindow()); + let aggregates = $select.selectItems->filter(s | $s->isSelectItemAggregate() && !$s->isSelectItemWindow())->cast(@SingleColumn); let rename = processSelect(^$select(selectItems = $select.selectItems->removeAll($aggregates)->removeAll($windows)->removeAll($extensions)->removeAll($havingExtensions)->filter(c | $c->instanceOf(SingleColumn))->cast(@SingleColumn)), false, $context); - //TODO should use equals on the expression instead of name checking in groupBy, cannot at moment due to no equality key. - let aggregatePairs = $aggregates->cast(@SingleColumn) - ->map(column | pair(extractNameFromSingleColumn($column, $context), $column)) - ->filter(pair | !$groupByColumns->contains($pair.first)); - - let aggregateExpressionNames = $aggregates->cast(@SingleColumn)->map(column | extractNameFromExpression($column.expression, $context)); - - let havingAggregatePairs = $havingExtensions->cast(@SingleColumn) - ->map(column | pair(extractNameFromSingleColumn($column, $context), $column)) - ->filter(pair | !$aggregateExpressionNames->contains($pair.first)) - ->removeDuplicatesBy(x | $x.first); - let additionalGroupColumns = $select.selectItems->removeAll($aggregates)->map(s | $s->match([ s:SingleColumn[1] | extractNameFromSingleColumn($s, $context), a:AllColumns[1] | @@ -403,11 +422,13 @@ function <> meta::external::query::sql::transformation::queryToP let allGroupByColumns = $groupByColumns->concatenate($additionalGroupColumns)->distinct(); + let aggregateExpressionNames = $aggregates->cast(@SingleColumn)->map(column | extractNameFromExpression($column.expression, $context)); + let allAggregateColumns = $aggregates->concatenate($havingExtensions->cast(@SingleColumn)->filter(h | + !extractNameFromExpression($h.expression, $context)->in($aggregateExpressionNames); + ))->removeDuplicatesBy(c | $c->extractNameFromSingleColumn($context)); - let aggregations = $aggregatePairs->concatenate($havingAggregatePairs) - ->map(pair | - let name = $pair.first; - let column = $pair.second; + let aggregations = $allAggregateColumns->map(column | + let name = extractNameFromSingleColumn($column, $context); let aggregateExpression = extractAggregatesFromExpression($column.expression); @@ -547,24 +568,28 @@ function <> meta::external::query::sql::transformation::queryToP 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, $typeArguments, [$item.first->iv(), $item.second->iv()]); - ); + let selectItems = $select.selectItems->processSelectItems($context, false); - let iv = iv($args); + let columns = $context.columns.name; - appendTdsFunc($context.expression->toOne(), extend_TabularDataSet_1__BasicColumnSpecification_MANY__TabularDataSet_1_, list($iv)); + let args = $selectItems->map(item | + let rename = $columns->contains($item.second); + let name = if ($rename, | $item.second + '_1', | $item.second); + + sfe(col_Function_1__String_1__BasicColumnSpecification_1_, $genericType, $typeArguments, [$item.first->iv(), $name->iv()]); + ); + + appendTdsFunc($context.expression->toOne(), extend_TabularDataSet_1__BasicColumnSpecification_MANY__TabularDataSet_1_, list(iv($args))); } -function <> meta::external::query::sql::transformation::queryToPure::processSelect(select: Select[1], restrict:Boolean[1],context: SqlTransformContext[1]):FunctionExpression[1] +function <> meta::external::query::sql::transformation::queryToPure::processSelect(select: Select[1], restrict:Boolean[1], context: SqlTransformContext[1]):FunctionExpression[1] { debug('processSelect', $context.debug); - if (isSelectStar($select), + if (isSelectStar($select, $context), | $context.expression->toOne(), | if (allColumnsSimpleSelect($select), | processSelectToRestrictAndRename($select, $restrict, $context), | processSelectToProject($select, $context))); - } @@ -608,9 +633,12 @@ function <> meta::external::query::sql::transformation::queryToP && $si->cast(@SingleColumn).expression->cast(@FunctionCall).window->isNotEmpty() } -function <> meta::external::query::sql::transformation::queryToPure::isSelectItemColumnReference(si:SelectItem[1]):Boolean[1] +function <> meta::external::query::sql::transformation::queryToPure::isNonAliasedSelectItemColumnReference(si:SelectItem[1]):Boolean[1] { - $si->instanceOf(AllColumns) || ($si->instanceOf(SingleColumn) && $si->cast(@SingleColumn).expression->instanceOf(QualifiedNameReference)) + $si->match([ + a:AllColumns[1] | true, + s:SingleColumn[1] | $s.expression->instanceOf(QualifiedNameReference) && $s.alias->isEmpty() + ]) } function <> meta::external::query::sql::transformation::queryToPure::processSelectToProject(select: Select[1], context: SqlTransformContext[1]):FunctionExpression[1] @@ -655,8 +683,6 @@ function <> meta::external::query::sql::transformation::queryToP let renames = $selectItems->map(si | let defaultName = extractNameFromExpression($si.expression, $context); - let existing = $context.columns.name; - if ($si.alias->isNotEmpty() && $si.alias != $defaultName, | @@ -669,9 +695,12 @@ function <> meta::external::query::sql::transformation::queryToP | $context.expression->toOne()); } -function <> meta::external::query::sql::transformation::queryToPure::isSelectStar(select: Select[1]):Boolean[1] +function <> meta::external::query::sql::transformation::queryToPure::isSelectStar(select: Select[1], context: SqlTransformContext[1]):Boolean[1] { - $select.selectItems->forAll(si | $si->instanceOf(AllColumns)); + $select.selectItems->forAll(si | $si->match([ + a:AllColumns[1] | assert($a.prefix->isEmpty() || $a.prefix == $context.name || $a.prefix->in($context.contexts.name), 'invalid select * - alias not in scope'), + s:SelectItem[1] | false + ])); } function <> meta::external::query::sql::transformation::queryToPure::allColumnsSimpleSelect(select: Select[1]):Boolean[1] @@ -749,14 +778,13 @@ function <> meta::external::query::sql::transformation::queryToP function <> meta::external::query::sql::transformation::queryToPure::processDuplicateRenames(names:String[*], suffix:String[1], context:SqlTransformContext[1]): SqlTransformContext[1] { - if ($names->isNotEmpty(), | let expression = processRename($names->map(n | ^SingleColumn(alias = $n + '_' + $suffix, expression = ^QualifiedNameReference(name = ^QualifiedName(parts = $n)))), $context); let aliases = $context.aliases->map(a | - if ($a.name->in($names) || $a.alias->in($names), - | ^$a(realias = $a.expected + '_' + $suffix), + if ($a.name->in($names) || $a.alias->in($names) || $a.realias->in($names), + | ^$a(realias = $a.actual + '_' + $suffix), | $a); ); @@ -771,8 +799,8 @@ function <> meta::external::query::sql::transformation::queryToP let leftContext = processRelation($join.left, ^$context(root = false, id = $context.id + 2)); let rightContext = processRelation($join.right, ^$context(root = false, id = $context.id + 3)); - let leftColumns = $leftContext.aliases.expected(); - let rightColumns = $rightContext.aliases.expected(); + let leftColumns = $leftContext.aliases.actual(); + let rightColumns = $rightContext.aliases.actual(); let leftName = $join.left->relationName(); let rightName = $join.right->relationName(); @@ -876,6 +904,8 @@ function <> meta::external::query::sql::transformation::queryToP function <> meta::external::query::sql::transformation::queryToPure::createSortItemFunction(si:SortItem[1], selectItems:SelectItem[*], context: SqlTransformContext[1]):FunctionExpression[1] { + assert($si.nullOrdering == SortItemNullOrdering.UNDEFINED, 'null ordering type not yet supported'); + let column = extractColumnNameFromExpression($si.sortKey, $selectItems, $context); let sortFunc = [ pair(SortItemOrdering.ASCENDING, asc_String_1__SortInformation_1_), @@ -939,7 +969,7 @@ function <> meta::external::query::sql::transformation::queryToP function <> meta::external::query::sql::transformation::queryToPure::processTableSubquery(tsq: TableSubquery[1], context: SqlTransformContext[1]): SqlTransformContext[1] { debug('processTableSubquery', $context.debug); - processQuery($tsq.query, $context); + processQuery($tsq.query, ^$context(root = false)); } function meta::external::query::sql::transformation::queryToPure::extractSourceArguments(expressions:meta::external::query::sql::metamodel::Expression[*]):SQLSourceArgument[*] @@ -1518,7 +1548,8 @@ function <> meta::external::query::sql::transformation::queryToP pair(String, | processCastAsParse(^$c(type = ^ColumnType(name = 'DOUBLE PRECISION')), $v, $expContext, $context)), pair(Float, | $v), pair(Decimal, | sfe(toFloat_Number_1__Float_1_, $v)), - pair(Integer, | sfe(toFloat_Number_1__Float_1_, $v)) + pair(Integer, | sfe(toFloat_Number_1__Float_1_, $v)), + pair(Number, | sfe(toFloat_Number_1__Float_1_, $v)) ]->getValue($type)->eval(); } @@ -1536,7 +1567,8 @@ function <> meta::external::query::sql::transformation::queryToP pair(String, | processCastAsParse(^$c(type = ^ColumnType(name = 'NUMERIC')), $v, $expContext, $context)), pair(Decimal, | $v), pair(Float, | sfe(toDecimal_Number_1__Decimal_1_, $v)), - pair(Integer, | sfe(toDecimal_Number_1__Decimal_1_, $v)) + pair(Integer, | sfe(toDecimal_Number_1__Decimal_1_, $v)), + pair(Number, | sfe(toDecimal_Number_1__Decimal_1_, $v)) ]->getValue($type)->eval(); let scale = if ($c.type.parameters->size() == 2, | $c.type.parameters->at(1), | []); @@ -1696,7 +1728,6 @@ function <> meta::external::query::sql::transformation::queryToP function <> meta::external::query::sql::transformation::queryToPure::createTdsColumn(qualifiedName:QualifiedName[1], var:VariableExpression[1], expContext:SqlTransformExpressionContext[1], context: SqlTransformContext[1]):ValueSpecification[1] { - if ($qualifiedName.parts->isEmpty(), | $var, | let column = $context.columnByNameParts($qualifiedName.parts, true); @@ -1960,10 +1991,10 @@ function meta::external::query::sql::transformation::queryToPure::functionProces let position = $args->at(2)->match([ i:InstanceValue[1] | $i.values->match([ - i:Integer[1] | iv($i + 1), + i:Integer[1] | iv($i - 1), a:Any[*] | fail('invalid split part position'); iv(1); ]), - v:ValueSpecification[1] | sfe(plus_Integer_MANY__Integer_1_, iv([$args->at(2), iv(1)])) + v:ValueSpecification[1] | sfe(minus_Integer_MANY__Integer_1_, iv([$args->at(2), iv(1)])) ]); let arguments = [$args->at(0), $args->at(1), $position]; @@ -2105,7 +2136,7 @@ function <> meta::external::query::sql::transformation::queryToP debug('processNegativeExpression', $context.debug); let value = $n.value->processExpression($expContext, $context); - sfe(minus_Number_MANY__Number_1_, $value); + createMinus($value.genericType.rawType, false, $value); } function <> meta::external::query::sql::transformation::queryToPure::getLiteralType(literal:Literal[1]):Type[1] @@ -2207,28 +2238,50 @@ function <> meta::external::query::sql::transformation::queryToP 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_), - pair(Float, plus_Decimal_MANY__Decimal_1_) - ]->getValue($type, plus_Number_MANY__Number_1_), iv($left->concatenate($right)))), - pair(ArithmeticType.SUBTRACT, | sfe([ + pair(ArithmeticType.ADD, | createPlus($type, false, $left->concatenate($right))), + pair(ArithmeticType.SUBTRACT, | createMinus($type, false, $left->concatenate($right))), + pair(ArithmeticType.MULTIPLY, | createTimes($type, false, $left->concatenate($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); + +} + +function meta::external::query::sql::transformation::queryToPure::createMinus(type:Type[0..1], null:Boolean[1], args:ValueSpecification[*]):ValueSpecification[1] +{ + createTypedFunction([ pair(Integer, minus_Integer_MANY__Integer_1_), pair(Float, minus_Float_MANY__Float_1_), pair(Decimal, minus_Decimal_MANY__Decimal_1_) - ]->getValue($type, minus_Number_MANY__Number_1_), iv($left->concatenate($right)))), - pair(ArithmeticType.MULTIPLY, | sfe([ + ], minus_Number_MANY__Number_1_, $type, $null, iv($args)); +} + +function meta::external::query::sql::transformation::queryToPure::createPlus(type:Type[0..1], null:Boolean[1], args:ValueSpecification[*]):ValueSpecification[1] +{ + createTypedFunction([ + pair(Integer, plus_Integer_MANY__Integer_1_), + pair(Float, plus_Float_MANY__Float_1_), + pair(Decimal, plus_Decimal_MANY__Decimal_1_) + ], plus_Number_MANY__Number_1_, $type, $null, iv($args)); +} + +function meta::external::query::sql::transformation::queryToPure::createTimes(type:Type[0..1], null:Boolean[1], args:ValueSpecification[*]):ValueSpecification[1] +{ + createTypedFunction([ pair(Integer, times_Integer_MANY__Integer_1_), 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, | 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(); + ], times_Number_MANY__Number_1_, $type, $null, iv($args)); +} - if ($left->isNull() || $right->isNull(), | iv([], $expression->evaluateAndDeactivate().genericType), | $expression); +function <> meta::external::query::sql::transformation::queryToPure::createTypedFunction(pairs:Pair>[*], default:Function[1], type:Type[0..1], null:Boolean[1], args:ValueSpecification[*]):ValueSpecification[1] +{ + let func = if ($type->isEmpty(), | $default, | $pairs->getValue($type->toOne(), $default)); + if ($null, | nullOrSfe($func, $args), | sfe($func, $args)); } //purely internal class to ensure we can handle expressions in the interval calculation logic @@ -2248,14 +2301,19 @@ Class <> meta::external::query::sql::transformation::queryToPure function <> meta::external::query::sql::transformation::queryToPure::simplifyDateArithmetic(e:meta::external::query::sql::metamodel::Expression[1], expContext:SqlTransformExpressionContext[1], context:SqlTransformContext[1]):meta::external::query::sql::metamodel::Expression[1] { $e->match([ - a:ArithmeticExpression[1] | + ae:ArithmeticExpression[1] | + let a = ^$ae( + left = if ($ae.left->instanceOf(StringLiteral), | ^Cast(expression = $ae.left, type = ^ColumnType(name = 'TIMESTAMP')), | $ae.left), + right = if ($ae.right->instanceOf(StringLiteral), | ^Cast(expression = $ae.right, type = ^ColumnType(name = 'TIMESTAMP')), | $ae.right) + ); + if ($a.right->instanceOf(NullLiteral) || $a.left->instanceOf(NullLiteral), | ^NullLiteral(), | if ($a.type == ArithmeticType.MULTIPLY && $a.left->instanceOf(IntervalLiteral), | multiplyIntervalLiteral($a.left->cast(@IntervalLiteral), $a.right, $expContext, $context), | if ($a.type == ArithmeticType.MULTIPLY && $a.right->instanceOf(IntervalLiteral), | multiplyIntervalLiteral($a.right->cast(@IntervalLiteral), $a.left, $expContext, $context), - | ^$a(left = $a.left->simplifyDateArithmetic($expContext, $context), right = $a.right->simplifyDateArithmetic($expContext, $context))))), + | ^$a(left = $a.left->simplifyDateArithmetic($expContext, $context), right = $a.right->simplifyDateArithmetic($expContext, $context)))));, i:IntervalLiteral[1] | ^IntervalLiteralWrapper(ago = $i.ago, years = $i.years->ivIfNotEmpty(), months = $i.months->ivIfNotEmpty(), weeks = $i.weeks->ivIfNotEmpty(), days = $i.days->ivIfNotEmpty(), hours = $i.hours->ivIfNotEmpty(), minutes = $i.minutes->ivIfNotEmpty(), seconds = $i.seconds->ivIfNotEmpty()), e:meta::external::query::sql::metamodel::Expression[1] | $e @@ -2292,18 +2350,27 @@ function <> meta::external::query::sql::transformation::queryToP assert($simplified.type == ArithmeticType.ADD || $simplified.type == ArithmeticType.SUBTRACT, | 'arithmetic type ' + $simplified.type.name + ' not currently supported for dates'); //note we are making assumption here that Any is fine. This results from a function call that is generic return type (e.g. max) assert($leftTypeNormalized == Date || $leftTypeNormalized == Any, | 'left side of date arithmetic must be non interval date'); - assert($rightTypeNormalized == Number || $simplified.right->instanceOf(IntervalLiteralWrapper) || $simplified.right->instanceOf(NullLiteral), | 'right side of date arithmetic must be numeric or interval'); + + assert($rightTypeNormalized == Number || $rightTypeNormalized == String || $simplified.right->instanceOf(IntervalLiteralWrapper) || $simplified.right->instanceOf(NullLiteral) || ($rightTypeNormalized == Date && $simplified.type == ArithmeticType.SUBTRACT), | 'right side of date arithmetic must be numeric or interval'); let negate = $simplified.type == ArithmeticType.SUBTRACT; [ pair($simplified.right->instanceOf(IntervalLiteralWrapper) && !$simplified.left->instanceOf(NullLiteral), {| let left = $simplified.left->processExpression($expContext, $context); - processIntervalToAdjust($left, $simplified.right->cast(@IntervalLiteralWrapper), $negate);}), + processIntervalToAdjust($left, $simplified.right->cast(@IntervalLiteralWrapper), $negate); + }), 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);}) + processCastAsCast($cast, processExpression($cast, $expContext, $context), $expContext, $context); + }), + pair($leftTypeNormalized == Date && ($rightTypeNormalized == Date || $rightTypeNormalized == String) && $simplified.type == ArithmeticType.SUBTRACT && !$simplified.right->instanceOf(IntervalLiteralWrapper), {| + let left = $simplified.left->processExpression($expContext, $context); + let right = $simplified.right->processExpression($expContext, $context); + + sfe(dateDiff_Date_1__Date_1__DurationUnit_1__Integer_1_, [$left, $right, processExtractEnumValue(DurationUnit, DurationUnit.DAYS.name)]); + }) ]->getValue(true, {| let left = $simplified.left->processExpression($expContext, $context); let right = $simplified.right->processExpression($expContext, $context); @@ -2536,7 +2603,11 @@ function <> meta::external::query::sql::transformation::queryToP { if ($amount->isNotEmpty(), | - let adjustedAmount = if ($ago, | nullOrSfe(minus_Number_MANY__Number_1_, $amount->toOne()), | $amount); + let adjustedAmount = if ($ago, + | + let type = $amount->evaluateAndDeactivate().genericType.rawType; + createMinus($type, true, $amount->toOne());, + | $amount); nullOrSfe(adjust_Date_1__Integer_1__DurationUnit_1__Date_1_, [$input, iv($adjustedAmount->toOne()), processExtractEnumValue(DurationUnit, $unit.name)]);, | $input) } @@ -2760,7 +2831,7 @@ Class meta::external::query::sql::transformation::queryToPure::SqlTransformConte $indent + 'name: ' + if ($this.name->isEmpty(), | '[]', | $this.name->toOne()) + '\n' + $indent + 'root: ' + $this.root->toString() + '\n' + if ($this.aliases->isNotEmpty(), | $indent + 'aliases: \n' + $indent + ' ' + $this.aliases.toString()->joinStrings('\n' + $indent + ' ') + '\n', | '') + - if ($this.contexts->isNotEmpty(), | $indent + 'contexts: \n' + $indent + $this.contexts->map(c | $c.toString($indent + ' '))->joinStrings('\n' + $indent), | '') + + if ($this.contexts->isNotEmpty(), | $indent + 'contexts: \n' + $this.contexts->map(c | $c.toString($indent + ' '))->joinStrings('\n'), | '') + if ($this.assignments->isNotEmpty(), | $indent + 'assigments: ' + $this.assignments->size()->toString(), | '') }:String[1]; @@ -2774,16 +2845,16 @@ Class meta::external::query::sql::transformation::queryToPure::SqlTransformConte }:SqlTransformContext[1]; columns(){ if ($this.expression->isNotEmpty(), - | meta::pure::tds::schema::resolveSchema($this.lambda(), $this.extensions), + | meta::pure::tds::schema::resolveSchema($this.lambda(), $this.extensions);, | []) }: TDSColumn[*]; columnByNameParts(parts:String[*], failIfNotFound:Boolean[1]) { let name = if ($parts->size() > 1, | $parts->last(), | $parts)->joinStrings('.'); let contextName = $parts->init()->joinStrings('.'); + let foundContext = $this.contexts->filter(c | $c.name == $contextName); let context = if ($contextName->isEmpty() || $foundContext->isEmpty(), | $this, | $foundContext->toOne()); - $context.columnByName($name, $failIfNotFound); }:TDSColumn[0..1]; columnByName(name:String[1], failIfNotFound:Boolean[1]){ @@ -2797,14 +2868,14 @@ Class meta::external::query::sql::transformation::queryToPure::SqlTransformConte }: TDSColumn[0..1]; alias(nameParts:String[*], alias:String[0..1], failIfNotFound:Boolean[1]){ - let name = $nameParts->last()->toOne(); - let contexts = if ($nameParts->size() > 1, | $this.context($nameParts->at(0)), | $this->concatenate($this.contexts)); + let contextName = $nameParts->at(0); + let contexts = if ($nameParts->size() > 1, | $this.context($contextName), | $this->concatenate($this.contexts)); let aliases = $contexts.aliases->removeDuplicates(); - let filter = if ($nameParts->size() > 1, - | {a:SQLColumnAlias[1] | $a.name == $name || $a.alias == $name}, + let filter = if ($nameParts->size() > 1 && $contextName != $this.name, + | {a:SQLColumnAlias[1] | ($a.alias->isEmpty() && $a.name == $name) || ($a.alias == $name)}, | {a:SQLColumnAlias[1] | $a.name == $name && $a.alias == $alias}); let found = $aliases->filter($filter); @@ -2923,5 +2994,10 @@ 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), | []); + debug({|$a}, $debug) +} + +function meta::external::query::sql::transformation::queryToPure::debug(f:FunctionDefinition<{->String[1]}>[1], debug:DebugContext[1]):Any[0] +{ + if ($debug.debug, | println($debug.space + $f->eval()), | []); } 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 1d745b94da8..4dcbce199cd 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 @@ -157,6 +157,19 @@ function <> meta::external::query::sql::transformation::queryToPure:: }) } +function <> meta::external::query::sql::transformation::queryToPure::tests::testSelectWithTableAlias():Boolean[1] +{ + test( + 'SELECT t1."String" AS "str" FROM service."/service/service1" t1', + + {| 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' ]) + ->restrict('String')->renameColumns(pair('String', 'str')) + } + ) +} + function <> meta::external::query::sql::transformation::queryToPure::tests::testSelectColumnMultiTimes():Boolean[1] { test( @@ -174,7 +187,10 @@ function <> meta::external::query::sql::transformation::queryToPure:: function <> meta::external::query::sql::transformation::queryToPure::tests::testSelectColumnMultiTimesRealiasToExisting():Boolean[1] { test( - 'SELECT String as "str", String as "String" FROM service."/service/service1"', + [ + 'SELECT String as "str", String as "String" FROM service."/service/service1"', + 'SELECT "t1"."String" as "str", "t1".String as "String" FROM service."/service/service1" t1' + ], {| FlatInput.all()->project( [ x | $x.booleanIn, x | $x.integerIn, x | $x.floatIn, x | $x.decimalIn, x | $x.strictDateIn, x | $x.dateTimeIn, x | $x.stringIn ], @@ -185,18 +201,72 @@ function <> meta::external::query::sql::transformation::queryToPure:: }) } -function <> meta::external::query::sql::transformation::queryToPure::tests::testSelectColumnAliasedAsUnusedTableColumnName():Boolean[1] +function <> meta::external::query::sql::transformation::queryToPure::tests::testSelectColumnMultiTimesRealiasToExisting2():Boolean[1] { test( - 'SELECT Integer as "String" FROM service."/service/service1"', + [ + 'SELECT String as "str", String as "Integer" FROM service."/service/service1"', + 'SELECT "t1"."String" as "str", "t1".String as "Integer" FROM service."/service/service1" t1' + ], {| 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' ]) - ->restrict('Integer')->renameColumns(pair('Integer', 'String')) + [ 'Boolean', 'Integer', 'Float', 'Decimal', 'StrictDate', 'DateTime', 'String' ])->project([ + col(row:TDSRow[1] | $row.getString('String'), 'str'), + col(row:TDSRow[1] | $row.getString('String'), 'Integer') + ]) }) } +function <> meta::external::query::sql::transformation::queryToPure::tests::testSelectColumnMultiTimesRealiasToExistingWithGroupBy():Boolean[1] +{ + test( + 'SELECT ' + + 't1.String AS "col1", ' + + 't1.String AS "col2", ' + + 't1.StrictDate AS "StrictDate", ' + + 't1.String AS "String", ' + + 'SUM(t1.Integer) AS "sum" ' + + 'FROM (select * from service."/service/service1") "t1" GROUP BY 3, 4', + + {| 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'), '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') + ])->groupBy( + ['StrictDate_1', 'String_1', 'col1', 'col2'], + agg('sum', row | $row.getInteger('Integer'), y | $y->sum()) + )->renameColumns([ + pair('StrictDate_1', 'StrictDate'), + pair('String_1', 'String') + ])->restrict([ + 'col1', 'col2', 'StrictDate', 'String', 'sum' + ]) + }, false + ) +} + +function <> meta::external::query::sql::transformation::queryToPure::tests::selectStarFromRealisedColumnSubQuery():Boolean[1] +{ + test( + [ + 'SELECT * FROM (select String as "S" from service."/service/service1")', + 'SELECT t1.* FROM (select String as "S" from service."/service/service1") t1' + ], + + {| 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' ]) + ->restrict('String') + ->renameColumns(pair('String', 'S')) + } + ) +} + function <> meta::external::query::sql::transformation::queryToPure::tests::testSelectColumnMultiTimesGroupBy():Boolean[1] { test( @@ -204,8 +274,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' ])->project([ - col(row:TDSRow[1] | $row.getString('String'), 'String'), + [ 'Boolean', 'Integer', 'Float', 'Decimal', 'StrictDate', 'DateTime', 'String' ])->extend([ col(row:TDSRow[1] | $row.getString('String'), 'str') ])->restrict(['String', 'str'])->distinct() }) @@ -626,7 +695,9 @@ 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' ] - )->renameColumns(pair('Boolean', 'bool')) + )->extend([ + col(row:TDSRow[1] | $row.getBoolean('Boolean'), 'bool') + ]) ->groupBy(['String', 'bool'], agg('sum', row | $row.getInteger('Integer'), y | $y->sum())) ->restrict(['String', 'sum', 'bool']) }, false) @@ -640,7 +711,9 @@ 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' ] - )->renameColumns(pair('Boolean', 'bool')) + )->extend([ + col(row:TDSRow[1] | $row.getBoolean('Boolean'), 'bool') + ]) ->groupBy(['String', 'bool'], agg('sum', row | $row.getInteger('Integer'), y | $y->sum())) ->restrict(['String', 'sum', 'bool']) }, false) @@ -730,6 +803,32 @@ function <> meta::external::query::sql::transformation::queryToPure:: }, false) } +function <> meta::external::query::sql::transformation::queryToPure::tests::testAggregationWithConstantSelectItemNoGroupBy():Boolean[1] +{ + test( + 'SELECT count("Integer") AS "Count", count("Float") AS "Float Count", \'abc\' AS "String", cast("Float" AS VARCHAR) AS "Float", Float AS "Original Float" from service."/service/service1"', + + {| 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] | 'abc', 'String_1'), + col(row:TDSRow[1] | $row.getFloat('Float')->toString(), 'Float_1'), + col(row:TDSRow[1] | $row.getFloat('Float'), 'Original Float') + ]) + ->groupBy(['String_1', 'Float_1', 'Original Float'], [ + agg('Count', row | $row.getInteger('Integer'), y | $y->count()), + agg('Float Count', row | $row.getFloat('Float'), y | $y->count()) + ]) + ->renameColumns([ + pair('String_1', 'String'), + pair('Float_1', 'Float') + ]) + ->restrict(['Count', 'Float Count', 'String', 'Float', 'Original Float']) + }, false + ) +} + //HAVING function <> meta::external::query::sql::transformation::queryToPure::tests::testHaving():Boolean[1] { @@ -796,9 +895,9 @@ function <> meta::external::query::sql::transformation::queryToPure:: pair('DateTime', 'DateTime_table2'), pair('String', 'String_table2') ]), meta::relational::metamodel::join::JoinType.LEFT_OUTER, ['String']) - ->renameColumns([ - pair('String_table2', 'str') - ]) + ->extend( + col(row:TDSRow[1] | $row.getString('String_table2'), 'str') + ) ->groupBy([ 'Boolean_table1', 'Integer_table1', 'Float_table1', 'Decimal_table1', 'StrictDate_table1', 'DateTime_table1', 'String_table1', 'str' @@ -839,8 +938,8 @@ function <> meta::external::query::sql::transformation::queryToPure:: ' 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"', + ' CAST(Float AS NUMERIC) AS "floatNumeric", CAST(Decimal AS NUMERIC) AS "decimalNumeric", CAST(1 + 1.1 AS NUMERIC) AS "numberNumeric", CAST(Decimal AS DOUBLE PRECISION) AS "decimalDoublePrecision",' + + ' CAST(Float AS DOUBLE PRECISION) AS "floatDoublePrecision", CAST(1 + 1.1 AS DOUBLE PRECISION) AS "numberDoublePrecision", CAST(String AS NUMERIC(4, 2)) AS "numericParams" FROM service."/service/service1"', {| FlatInput.all()->project( [ x | $x.booleanIn, x | $x.integerIn, x | $x.floatIn, x | $x.decimalIn, x | $x.strictDateIn, x | $x.dateTimeIn, x | $x.stringIn ], @@ -864,8 +963,10 @@ function <> meta::external::query::sql::transformation::queryToPure:: col(row:TDSRow[1] | substring(toString($row.getInteger('Integer')), 1, 2), 'integerStringChars'), col(row:TDSRow[1] | toDecimal($row.getFloat('Float')), 'floatNumeric'), col(row:TDSRow[1] | $row.getDecimal('Decimal'), 'decimalNumeric'), + col(row:TDSRow[1] | toDecimal(1 + 1.1), 'numberNumeric'), col(row:TDSRow[1] | toFloat($row.getDecimal('Decimal')), 'decimalDoublePrecision'), col(row:TDSRow[1] | $row.getFloat('Float'), 'floatDoublePrecision'), + col(row:TDSRow[1] | toFloat(1 + 1.1), 'numberDoublePrecision'), col(row:TDSRow[1] | round(parseDecimal($row.getString('String')), 2), 'numericParams') ]) }) @@ -1189,6 +1290,40 @@ function <> meta::external::query::sql::transformation::queryToPure:: ])}, false) } +function <> meta::external::query::sql::transformation::queryToPure::tests::testJoinWithAliasRenamingInSubQueries():Boolean[1] +{ + test( + 'SELECT "t0"."String" AS "String", "t1"."measure" AS "sum" FROM (SELECT "t3"."String" AS "String" FROM service."/service/service1" "t3" GROUP BY 1) "t0" CROSS JOIN (SELECT SUM("t3"."Integer") AS "measure" FROM service."/service/service1" "Staples" HAVING (COUNT(1) > 0)) "t1"', + + {| 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')) + ->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 ], + ['Boolean', 'Integer', 'Float', 'Decimal', 'StrictDate', 'DateTime', 'String' ]) + ->groupBy([], [ + agg('measure', row | $row.getInteger('Integer'), y | $y->sum()), + agg('COUNT(1)', row | 1, y | $y->count()) + ]) + ->filter(row|($row.getInteger('COUNT(1)') > 0)) + ->restrict('measure') + ->renameColumns(pair('measure', 'measure_t1')), + meta::relational::metamodel::join::JoinType.INNER, {row1:TDSRow[1], row2:TDSRow[1] | true}) + ->restrict(['String_t0', 'measure_t1']) + ->renameColumns([ + pair('String_t0', 'String'), + pair('measure_t1', 'sum') + ]) + }, false + ) +} + //UNION function <> meta::external::query::sql::transformation::queryToPure::tests::testUnion():Boolean[1] @@ -1211,6 +1346,42 @@ function <> meta::external::query::sql::transformation::queryToPure:: }) } +function <> meta::external::query::sql::transformation::queryToPure::tests::testSelectFromAliasedUnion():Boolean[1] +{ + test('SELECT 1 AS "Number of Records",' + + '"t0"."String" AS "String"' + + 'FROM (SELECT "t1"."String" AS "String"' + + ' FROM (' + + ' SELECT "s1"."Integer" AS "int",' + + ' \'Value\' AS "String"' + + ' FROM service."/service/service1" "s1") "t1"' + + ' UNION ALL' + + ' SELECT "s1"."String" AS "String"' + + ' FROM (SELECT "s1"."Integer" AS "int",' + + ' \'Value2\' AS "String"' + + ' FROM service."/service/service1" "s1") "t2") "t0" LIMIT 1000', + + {|meta::external::query::sql::transformation::queryToPure::tests::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] |$row.getInteger('Integer'), 'int'), + col(row:TDSRow[1]| 'Value', 'String')]) + ->restrict('String') + ->concatenate(meta::external::query::sql::transformation::queryToPure::tests::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]| $row.getInteger('Integer'), 'int'), + col(row:TDSRow[1]|'Value2', 'String')]) + ->restrict('String') + )->project([ + col(row:TDSRow[1]|1, 'Number of Records'), + col(row:TDSRow[1]| $row.getString('String'), 'String')]) + ->limit(1000)} + ) +} + //CURRENT TIME function <> meta::external::query::sql::transformation::queryToPure::tests::testCurrentTime():Boolean[1] { @@ -1328,7 +1499,10 @@ function <> meta::external::query::sql::transformation::queryToPure:: 'StrictDate + NULL + INTERVAL \'1 YEAR 3 WEEKS 2 DAYS\' AS "INTERVAL_ADD_NULL", ' + '(CAST(\'2023-01-01\' AS DATE) + 2 * INTERVAL \'1 DAY\') + 3 * INTERVAL \'2 DAY\' AS "INTERVAL_MIX", ' + 'StrictDate + EXTRACT(\'year\' FROM StrictDate) * INTERVAL \'2 YEAR 3 DAYS\' AS "INTERVAL_MIX2", ' + - 'CAST((DATE_TRUNC( \'DAY\', CAST("StrictDate" AS DATE) ) + (EXTRACT(DOW FROM "StrictDate") * INTERVAL \'1 DAY\')) AS DATE) AS "INTERVAL_MIX3" ' + + 'CAST((DATE_TRUNC( \'DAY\', CAST("StrictDate" AS DATE) ) + (EXTRACT(DOW FROM "StrictDate") * INTERVAL \'1 DAY\')) AS DATE) AS "INTERVAL_MIX3", ' + + 'StrictDate - DateTime AS "DATE_SUBTRACT", ' + + 'StrictDate - INTERVAL \'1 DAY\' AS "INTERVAL_SUBTRACT", ' + + 'StrictDate - \'2023-01-01\' AS "STRING_SUBSTRACT"' + 'FROM service."/service/service1"', {| @@ -1345,7 +1519,10 @@ function <> meta::external::query::sql::transformation::queryToPure:: 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') + col(row:TDSRow[1] | $row.getStrictDate('StrictDate')->firstHourOfDay()->adjust(($row.getStrictDate('StrictDate')->dayOfWeekNumber() * 1), DurationUnit.DAYS), 'INTERVAL_MIX3'), + col(row:TDSRow[1] | dateDiff($row.getStrictDate('StrictDate'), $row.getDateTime('DateTime'), DurationUnit.DAYS), 'DATE_SUBTRACT'), + col(row:TDSRow[1] | adjust($row.getStrictDate('StrictDate'), -1, DurationUnit.DAYS), 'INTERVAL_SUBTRACT'), + col(row:TDSRow[1] | dateDiff($row.getStrictDate('StrictDate'), parseDate('2023-01-01'), DurationUnit.DAYS), 'STRING_SUBSTRACT') ]) }) } @@ -1386,8 +1563,8 @@ function <> meta::external::query::sql::transformation::queryToPure:: col(row:TDSRow[1] | rtrim($row.getString('String')), 'RTRIM'), col(row:TDSRow[1] | rtrim($row.getString('String')), 'RTRIM2'), col(row:TDSRow[1] | hash($row.getString('String'), HashType.SHA256), 'SHA256'), - col(row:TDSRow[1] | splitPart($row.getString('String'), ',', 2), 'SPLITPART'), - col(row:TDSRow[1] | splitPart($row.getString('String'), ',', $row.getInteger('Integer') + 1), 'SPLITPART2'), + col(row:TDSRow[1] | splitPart($row.getString('String'), ',', 0), 'SPLITPART'), + col(row:TDSRow[1] | splitPart($row.getString('String'), ',', $row.getInteger('Integer') - 1), 'SPLITPART2'), col(row:TDSRow[1] | substring($row.getString('String'), 1), 'SUBSTRING'), col(row:TDSRow[1] | substring($row.getString('String'), 1, 2), 'SUBSTR'), col(row:TDSRow[1] | trim($row.getString('String')), 'TRIM'), @@ -1582,7 +1759,9 @@ function <> meta::external::query::sql::transformation::queryToPure:: ->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' ]) - ->renameColumns(pair('String', 'string')) + ->extend([ + col(row:TDSRow[1] | $row.getString('String'), 'string') + ]) ->olapGroupBy(['String'], asc('Integer'), y | $y->meta::pure::functions::math::olap::rowNumber(), 'ROW') ->olapGroupBy(['String'], desc('Integer'), y | $y->meta::pure::functions::math::olap::denseRank(), 'DENSE RANK') ->olapGroupBy(['String'], asc('Integer'), y | $y->meta::pure::functions::math::olap::rank(), 'RANK') @@ -1983,23 +2162,25 @@ function meta::external::query::sql::transformation::queryToPure::tests::testSou ] } -function meta::external::query::sql::transformation::queryToPure::tests::test(sql:String[1], expected:FunctionDefinition[1]):Boolean[1] +function meta::external::query::sql::transformation::queryToPure::tests::test(sqls:String[*], expected:FunctionDefinition[1]):Boolean[1] { - test($sql, $expected, true); + test($sqls, $expected, true); } -function meta::external::query::sql::transformation::queryToPure::tests::test(sql:String[1], expected:FunctionDefinition[1], assertJSON:Boolean[1]):Boolean[1] +function meta::external::query::sql::transformation::queryToPure::tests::test(sqls:String[*], expected:FunctionDefinition[1], assertJSON:Boolean[1]):Boolean[1] { - test($sql, $expected, testSources(), false, true, $assertJSON); + test($sqls, $expected, testSources(), false, true, $assertJSON); } -function meta::external::query::sql::transformation::queryToPure::tests::test(sql:String[1], expected:FunctionDefinition[1], sources:SQLSource[*], scopeWithFrom:Boolean[1], assertLambda:Boolean[1], assertJSON:Boolean[1]):Boolean[1] +function meta::external::query::sql::transformation::queryToPure::tests::test(sqls:String[*], expected:FunctionDefinition[1], sources:SQLSource[*], scopeWithFrom:Boolean[1], assertLambda:Boolean[1], assertJSON:Boolean[1]):Boolean[1] { - let sqlTransformContext = $sql->processQuery($sources, $scopeWithFrom); - let actual = $sqlTransformContext.lambda(); + $sqls->forAll(sql | + let sqlTransformContext = $sql->processQuery($sources, $scopeWithFrom); + let actual = $sqlTransformContext.lambda(); - if ($assertLambda, | assertLambdaEquals($expected, $actual), | true); - if ($assertJSON, | assertLambdaJSONEquals($expected, $actual), | true); + if ($assertLambda, | assertLambdaEquals($expected, $actual), | true); + if ($assertJSON, | assertLambdaJSONEquals($expected, $actual), | true); + ) } function meta::external::query::sql::transformation::queryToPure::tests::processQuery(sql: String[1]): SqlTransformContext[1]