Skip to content

Commit

Permalink
Relational - sortBy accepts lambda function (#2460)
Browse files Browse the repository at this point in the history
  • Loading branch information
abhishoya-gs authored Nov 30, 2023
1 parent a928b72 commit 1a6b3b0
Show file tree
Hide file tree
Showing 2 changed files with 162 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import meta::pure::graphFetch::execution::*;
import meta::relational::mapping::*;
import meta::relational::tests::model::simple::*;
import meta::relational::tests::*;
import meta::external::store::relational::tests::*;
import meta::pure::profiles::*;
import meta::pure::executionPlan::toString::*;

function <<test.Test>> meta::relational::tests::query::sort::testSortSimple():Boolean[1]
{
Expand All @@ -26,13 +28,98 @@ function <<test.Test>> meta::relational::tests::query::sort::testSortSimple():Bo
assertEquals('select "root".ID as "pk_0", "root".FIRSTNAME as "firstName", "root".AGE as "age", "root".LASTNAME as "lastName", "root".LASTNAME as "o_lastName" from personTable as "root" order by "root".LASTNAME', $result->sqlRemoveFormatting());
}

function <<test.Test, test.ToFix>> meta::relational::tests::query::sort::testSortDeep():Boolean[1]
function <<test.Test>> meta::relational::tests::query::sort::testSortByLambdaSimple():Boolean[1]
{
let result = execute(|Person.all()->sortBy(p | $p.address->toOne().name), simpleRelationalMapping, testRuntime(), meta::relational::extension::relationalExtensions());
assertSize($result.values, 9);
assertEquals('Smith', $result.values->at(0).lastName);
assertEquals('Hill', $result.values->at(1).lastName);
assertSameElements(['Harris', 'Allen', 'Johnson', 'Hill'], $result.values->drop(2)->take(4).lastName);
assertEquals('Roberts', $result.values->at(6).lastName);
assertEquals('select root.FIRSTNAME as "firstName", root.AGE as "age", root.LASTNAME as "lastName", addressTable_d1_d_m1.NAME as "o_name" from personTable as root left outer join addressTable as addressTable_d1_d_m1 on (addressTable_d1_d_m1.ID = root.ADDRESSID) order by "o_name"', $result->sqlRemoveFormatting());
let result = execute(|Person.all()->sortBy(p | $p.lastName), simpleRelationalMapping, testRuntime(), meta::relational::extension::relationalExtensions());
assertSize($result.values, 12);
assertEquals(['Allen', 'Firm B', 'Harris', 'Hill', 'Hill', 'Johnson', 'New York', 'Roberts', 'Smith', 'Smith', 'York', 'no Firm'], $result.values.lastName);
assertEquals('select "root".ID as "pk_0", "root".FIRSTNAME as "firstName", "root".AGE as "age", "root".LASTNAME as "lastName" from personTable as "root" order by "root".LASTNAME', $result->sqlRemoveFormatting());
}

function <<test.Test>> meta::relational::tests::query::sort::testSortByLambdaMultiple():Boolean[1]
{
let result = execute(|Person.all()->sortBy(p | $p.lastName)->sortBy(p | $p.firstName), simpleRelationalMapping, testRuntime(), meta::relational::extension::relationalExtensions());
assertSize($result.values, 12);
assertEquals(['Anthony', 'David', 'Don', 'Elena', 'Fabrice', 'John', 'John', 'New', 'No address', 'No firm', 'Oliver', 'Peter'], $result.values.firstName);
assertEquals('select "root".ID as "pk_0", "root".FIRSTNAME as "firstName", "root".AGE as "age", "root".LASTNAME as "lastName" from personTable as "root" order by "root".FIRSTNAME', $result->sqlRemoveFormatting());
}

function <<test.Test>> meta::relational::tests::query::sort::testSortByLambdaColumnAddition():Boolean[1]
{
let result = execute(|Person.all()->sortBy(p | $p.lastName + '|' + $p.firstName), simpleRelationalMapping, testRuntime(), meta::relational::extension::relationalExtensions());
assertSize($result.values, 12);
assertEquals(['Allen|Anthony', 'Firm B|Elena', 'Harris|David', 'Hill|John', 'Hill|Oliver', 'Johnson|John', 'New York|Don', 'Roberts|Fabrice', 'Smith|No address', 'Smith|Peter', 'York|New', 'no Firm|No firm'], zip($result.values.lastName, $result.values.firstName)->map(pair | $pair.first + '|' + $pair.second));
assertEquals('select "root".ID as "pk_0", "root".FIRSTNAME as "firstName", "root".AGE as "age", "root".LASTNAME as "lastName" from personTable as "root" order by concat("root".LASTNAME, \'|\', "root".FIRSTNAME)', $result->sqlRemoveFormatting());
}

function <<test.Test>> meta::relational::tests::query::sort::testSortByLambdaWithIfElseValue():Boolean[1]
{
let result = execute(|Person.all()->filter(p|$p.lastName == 'Hill' || $p.firstName == 'John')->sortBy(p | if($p.lastName == 'Hill' && $p.firstName == 'John', | 0, |if ($p.lastName == 'Hill', | 10, | if($p.firstName == 'John',| 20, | 30)))), simpleRelationalMapping, testRuntime(), meta::relational::extension::relationalExtensions());
assertSize($result.values, 3);
assertEquals(['John Hill', 'Oliver Hill', 'John Johnson'], zip($result.values.firstName, $result.values.lastName)->map(p| $p.first + ' ' + $p.second));
assertEquals([0, 10, 20], zip($result.values.firstName, $result.values.lastName)->map(pair | if($pair.first == 'John' && $pair.second == 'Hill', |0, |if($pair.second == 'Hill', |10, |if($pair.first == 'John', |20, |30))) ));
assertEquals('select "root".ID as "pk_0", "root".FIRSTNAME as "firstName", "root".AGE as "age", "root".LASTNAME as "lastName" from personTable as "root" where ("root".LASTNAME = \'Hill\' or "root".FIRSTNAME = \'John\') order by case when ("root".LASTNAME = \'Hill\' and "root".FIRSTNAME = \'John\') then 0 else case when "root".LASTNAME = \'Hill\' then 10 else case when "root".FIRSTNAME = \'John\' then 20 else 30 end end end', $result->sqlRemoveFormatting());
}

function <<test.Test>> meta::relational::tests::query::sort::testSortByLambdaWIthIfElseColumn():Boolean[1]
{
let result = execute(|Person.all()->filter(p|$p.lastName == 'Hill' || $p.firstName == 'John')->sortBy(p | if($p.lastName == 'Hill', |$p.firstName, |$p.lastName)), simpleRelationalMapping, testRuntime(), meta::relational::extension::relationalExtensions());
assertSize($result.values, 3);
assertEquals(['John', 'Johnson', 'Oliver'], zip($result.values.firstName, $result.values.lastName)->map(pair | if($pair.second == 'Hill', | $pair.first, |$pair.second)));
assertEquals('select "root".ID as "pk_0", "root".FIRSTNAME as "firstName", "root".AGE as "age", "root".LASTNAME as "lastName" from personTable as "root" where ("root".LASTNAME = \'Hill\' or "root".FIRSTNAME = \'John\') order by case when "root".LASTNAME = \'Hill\' then "root".FIRSTNAME else "root".LASTNAME end', $result->sqlRemoveFormatting());
}

function <<test.Test>> meta::relational::tests::query::sort::testSortByLambdaDeepOptional():Boolean[1]
{
let result = execute(|Person.all()->sortBy(p | $p.address->toOne().name + '|' + $p.firstName + '|' + $p.lastName), simpleRelationalMapping, testRuntime(), meta::relational::extension::relationalExtensions());
assertSize($result.values, 12);
assertEquals(['Hoboken|Peter|Smith', 'Hong Kong|Oliver|Hill', 'New York|Anthony|Allen', 'New York|David|Harris', 'New York|Don|New York', 'New York|Elena|Firm B', 'New York|John|Hill', 'New York|John|Johnson', 'New York|New|York', 'New York|No firm|no Firm', 'San Fransisco|Fabrice|Roberts'], zip($result.values.address.name, zip($result.values.firstName, $result.values.lastName))->map(pair | $pair.first + '|' + $pair.second.first + '|' + $pair.second.second));
assertEquals('select "root".ID as "pk_0", "root".FIRSTNAME as "firstName", "root".AGE as "age", "root".LASTNAME as "lastName" from personTable as "root" left outer join addressTable as "addresstable_0" on ("addresstable_0".ID = "root".ADDRESSID) order by concat("addresstable_0".NAME, \'|\', "root".FIRSTNAME, \'|\', "root".LASTNAME)', $result->sqlRemoveFormatting());
}

function <<test.Test, test.AlloyOnly>> {meta::pure::executionPlan::profiles::serverVersion.start='vX_X_X'} meta::relational::tests::query::sort::testSortByLambdaAndGraphFetchDeep():Boolean[1]
{
let gft = #{
Person{
address
{
name
}
}
}#;
let result = execute(|Person.all()->sortBy(
p | $p.address->toOne().name
)->graphFetch(
$gft
)->serialize(
$gft
),
simpleRelationalMapping, testRuntime(), meta::relational::extension::relationalExtensions());

assertJsonStringsEqual('[{"address":null},{"address":{"name":"Hoboken"}},{"address":{"name":"Hong Kong"}},{"address":{"name":"New York"}},{"address":{"name":"New York"}},{"address":{"name":"New York"}},{"address":{"name":"New York"}},{"address":{"name":"New York"}},{"address":{"name":"New York"}},{"address":{"name":"New York"}},{"address":{"name":"New York"}},{"address":{"name":"San Fransisco"}}]', $result.values);
}


function <<test.Test>> meta::relational::tests::query::sort::testSortByLambda_QueryWithParameters_Plan():Boolean[1]
{
let rawPlan = meta::pure::executionPlan::executionPlan({firstName: String[1], lastName: String[1]|Person.all()->filter(p|$p.lastName == $lastName || $p.firstName == $firstName)->sortBy(p | if($p.firstName == $firstName && $p.lastName == $lastName, | 0, |if ($p.lastName == $lastName || $p.firstName == $firstName, | 10, | 20)))}, simpleRelationalMapping, testRuntime(), meta::relational::extension::relationalExtensions());
assertEquals('Sequence\n' +
'(\n' +
' type = Class[impls=(meta::relational::tests::model::simple::Person | simpleRelationalMappingInc.meta_relational_tests_model_simple_Person)]\n' +
' resultSizeRange = *\n' +
' (\n' +
' FunctionParametersValidationNode\n' +
' (\n' +
' functionParameters = [firstName:String[1], lastName:String[1]]\n' +
' )\n' +
' Relational\n' +
' (\n' +
' type = Class[impls=(meta::relational::tests::model::simple::Person | simpleRelationalMappingInc.meta_relational_tests_model_simple_Person)]\n' +
' resultSizeRange = *\n' +
' resultColumns = [("pk_0", INT), ("firstName", VARCHAR(200)), ("age", INT), ("lastName", VARCHAR(200))]\n' +
' sql = select "root".ID as "pk_0", "root".FIRSTNAME as "firstName", "root".AGE as "age", "root".LASTNAME as "lastName" from personTable as "root" where ("root".LASTNAME = \'${lastName?replace("\'", "\'\'")}\' or "root".FIRSTNAME = \'${firstName?replace("\'", "\'\'")}\') order by case when ("root".FIRSTNAME = \'${firstName?replace("\'", "\'\'")}\' and "root".LASTNAME = \'${lastName?replace("\'", "\'\'")}\') then 0 else case when ("root".LASTNAME = \'${lastName?replace("\'", "\'\'")}\' or "root".FIRSTNAME = \'${firstName?replace("\'", "\'\'")}\') then 10 else 20 end end\n' +
' connection = TestDatabaseConnection(type = "H2")\n' +
' )\n' +
' )\n' +
')\n',$rawPlan->planToString(meta::relational::extension::relationalExtensions()));
}
Original file line number Diff line number Diff line change
Expand Up @@ -2487,29 +2487,74 @@ function meta::relational::functions::pureToSqlQuery::processTDSSort(f:FunctionE

function meta::relational::functions::pureToSqlQuery::processSortBy(f:FunctionExpression[1], currentPropertyMapping:PropertyMapping[*], operation:SelectWithCursor[1], vars:Map<VariableExpression, ValueSpecification>[1], state:State[1], joinType:JoinType[1], nodeId:String[1], aggFromMap:List<ColumnGroup>[1], context:DebugContext[1], extensions:Extension[*]):RelationalOperationElement[1]
{
let mainQuery = processValueSpecification($f.parametersValues->at(0), $currentPropertyMapping, $operation, $vars, $state, $joinType, $nodeId, $aggFromMap, $context, $extensions)->toOne()->cast(@SelectWithCursor);
let mainSelect = $mainQuery.select->pushSavedFilteringOperation($extensions);

let sortByFunc = $f.parametersValues->evaluateAndDeactivate()->at(1)->byPassRouterInfo()->cast(@InstanceValue).values->at(0);
assert($sortByFunc->instanceOf(Path), 'sortBy can currently be converted to SQL only if the parameter is a Path.');
let path = $sortByFunc->cast(@Path<Nil,Any|*>);
let pathName = 'o_'+$path->buildColumnNameOutOfPath();
let joinResult = processPath($path, $pathName, '1', $mainQuery, $vars, ^$state(inFilter=false), $nodeId, $aggFromMap, $context->shift(), $extensions).element->cast(@SelectWithCursor).select;

let merge = $mainSelect->concatenate($joinResult)->mergeSQLQueryData($nodeId, $state, $context, $extensions);
let leftSidePure = $f.parametersValues->at(0);
let leftSideOp = processValueSpecificationReturnPropertyMapping($leftSidePure, $currentPropertyMapping, $operation, $vars, $state, $joinType, $nodeId, $aggFromMap, $context, $extensions)->toOne();
let mainQuery = $leftSideOp.element->cast(@SelectWithCursor);
let mainSelect = $mainQuery.select->pushSavedFilteringOperation($extensions);
let sortByFunc = $f.parametersValues->evaluateAndDeactivate()->at(1)->byPassRouterInfo()->cast(@InstanceValue).values->at(0);

$sortByFunc->match([
path: Path<Nil,Any|*>[1] |
let pathName = 'o_'+$path->buildColumnNameOutOfPath();
let joinResult = processPath($path, $pathName, '1', $mainQuery, $vars, ^$state(inFilter=false), $nodeId, $aggFromMap, $context->shift(), $extensions).element->cast(@SelectWithCursor).select;

let merge = $mainSelect->concatenate($joinResult)->mergeSQLQueryData($nodeId, $state, $context, $extensions);

^$operation(
currentTreeNode = $mainQuery.currentTreeNode->toOne()->findOneNode($mainQuery.select.data->toOne(), $merge.data->toOne()),
positionBeforeLastApplyJoinTreeNode = if ($mainQuery.positionBeforeLastApplyJoinTreeNode->isEmpty(),|[],|$mainQuery.positionBeforeLastApplyJoinTreeNode->toOne()->findOneNode($mainQuery.select.data->toOne(), $merge.data->toOne())),
select = ^SelectSQLQuery(
columns = $merge.columns,
data = $merge.data,
leftSideOfFilter = $merge.leftSideOfFilter,
filteringOperation = $merge.filteringOperation,
extraFilteringOperation = $merge.extraFilteringOperation,
orderBy = ^OrderBy(column=$joinResult.columns->at(0)->cast(@Alias).relationalElement, direction=meta::relational::metamodel::SortDirection.ASC)
)
);
^$operation(
currentTreeNode = $mainQuery.currentTreeNode->toOne()->findOneNode($mainQuery.select.data->toOne(), $merge.data->toOne()),
positionBeforeLastApplyJoinTreeNode = if ($mainQuery.positionBeforeLastApplyJoinTreeNode->isEmpty(),|[],|$mainQuery.positionBeforeLastApplyJoinTreeNode->toOne()->findOneNode($mainQuery.select.data->toOne(), $merge.data->toOne())),
select = ^SelectSQLQuery(
columns = $merge.columns,
data = $merge.data,
leftSideOfFilter = $merge.leftSideOfFilter,
filteringOperation = $merge.filteringOperation,
extraFilteringOperation = $merge.extraFilteringOperation,
orderBy = ^OrderBy(column=$joinResult.columns->at(0)->cast(@Alias).relationalElement, direction=meta::relational::metamodel::SortDirection.ASC)
)
);,
l: LambdaFunction<Any>[1] |
/*
- already have a mainQuery - select ...
- process the lambda function inside sortBy with the state to generate a sql query
select <sql_expr> as "generated_order_key" from ...
where <sql_expr> is a sql representation for lambda function and "generated_order_key" is a temporary alias introduced
- merge these two queries
- modify the merged query to move the sql_expr from select to order by
select ... order by <sql_expr>
*/

let generated_order_key = '"generated_order_key"';
let paramValue = $f.parametersValues->at(1)->match([e:StoreMappingRoutedValueSpecification[1]|$e.value,v:ValueSpecification[1]|$v]);
let updatedState = $state->updateFunctionParamScope($paramValue.genericType.typeArguments.rawType->toOne()->cast(@FunctionType),$mainQuery);
let lambdaFunction = $f->instanceValuesAtParameter(1, $vars, $updatedState.inScopeVars)->at(0)->cast(@FunctionDefinition<Any>);
let lambdaFunctionExpression = $lambdaFunction.expressionSequence->at(0);
let inScopeVarsWithPlaceholdersState = $lambdaFunction->addPlaceHoldersForLambdaOpenVariables($vars, $updatedState);
let embeddedMapping = if($leftSideOp.currentPropertyMapping->isEmpty(), | $currentPropertyMapping, | $leftSideOp.currentPropertyMapping);

let rightSide = processValueSpecification($lambdaFunctionExpression, $embeddedMapping, $mainQuery, $vars, ^$inScopeVarsWithPlaceholdersState(inFilter=false), JoinType.LEFT_OUTER, $nodeId, $aggFromMap, $context->shift(), $extensions)->toOne()->cast(@SelectWithCursor);
let rightSideSelect = $rightSide.select;
let rightSideModified = ^$rightSide(
select = ^$rightSideSelect(
columns = if($rightSideSelect.columns->isEmpty(), | [], |^Alias(name = $generated_order_key, relationalElement = $rightSideSelect.columns->toOne()))
)
);

let revisedLeftRightSelects = pair($mainSelect,$rightSideModified.select);
let merge = [$revisedLeftRightSelects.first, $revisedLeftRightSelects.second]->cast(@SelectSQLQuery)->mergeSQLQueryData($nodeId, $updatedState, $context, $extensions);
^$operation(
currentTreeNode = $mainQuery.currentTreeNode->toOne()->findOneNode($mainQuery.select.data->toOne(), $merge.data->toOne()),
positionBeforeLastApplyJoinTreeNode = if ($mainQuery.positionBeforeLastApplyJoinTreeNode->isEmpty(),|[],|$mainQuery.positionBeforeLastApplyJoinTreeNode->toOne()->findOneNode($mainQuery.select.data->toOne(), $merge.data->toOne())),
select = ^SelectSQLQuery(
columns = $merge.columns->filter(c|!$c->instanceOf(Alias) || $c->cast(@Alias).name != $generated_order_key),
data = $merge.data,
leftSideOfFilter = $merge.leftSideOfFilter,
filteringOperation = $merge.filteringOperation,
extraFilteringOperation = $merge.extraFilteringOperation,
orderBy = ^OrderBy(column=$rightSideModified.select.columns->toOne()->cast(@Alias).name->findAliasOrFail($merge).relationalElement, direction=meta::relational::metamodel::SortDirection.ASC)
)
);,
a: Any[1] | fail('Invalid parameter type to sortBy ' + $sortByFunc->type().name->orElse('unknown')); $operation;
]);
}

function meta::relational::functions::pureToSqlQuery::processSlice(f:FunctionExpression[1], currentPropertyMapping:PropertyMapping[*], operation:SelectWithCursor[1], vars:Map<VariableExpression, ValueSpecification>[1], state:State[1], joinType:JoinType[1], nodeId:String[1], aggFromMap:List<ColumnGroup>[1], context:DebugContext[1], extensions:Extension[*]):RelationalOperationElement[1]
Expand Down

0 comments on commit 1a6b3b0

Please sign in to comment.