Alexander Shabarshov 2a7a24c9e7 Initial contribution
2025-11-03 14:43:26 +00:00

699 lines
16 KiB
C#

/*
* dbMango
*
* Copyright 2025 Deutsche Bank AG
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Tests.Rms.Risk.Mango.Language;
[TestFixture]
public class JsonToScriptTests
{
private const string MatchStage =
"""
[{
"$match" : {
"$and" : [{
"$and" : [{
"COB" : { "$date" : "2025-04-25T00:00:00Z" }
}, {
"$or" : [{
"Department" : "FX Forwards Controlling"
}, {
"Department" : "FX Metals Controlling"
}, {
"Department" : "FX Options Controlling"
}]
}]
}]
}
}]
""";
private const string MatchScript =
"""
FROM "TestCollection" PIPELINE {
WHERE
COB == date( "2025-04-25T00:00:00Z" )
AND (Department == "FX Forwards Controlling"
OR Department == "FX Metals Controlling"
OR Department == "FX Options Controlling")
}
""";
private const string ProjectStage =
"""
[{
"$project" : {
"CurvePrefix" : 1,
"Data" : {
"$objectToArray" : "$RhoDetails"
}
}
}]
""";
private const string ProjectScript =
"""
FROM "TestCollection" PIPELINE {
PROJECT
CurvePrefix,
objectToArray( RhoDetails ) AS Data
}
""";
private const string ProjectWithComplexVarNamedStage =
"""
[{
"$project" : {
"Data" : "$Very.Complex.Var"
}
}]
""";
private const string ProjectWithComplexVarNamedScript =
"""
FROM "TestCollection" PIPELINE {
PROJECT
$Very.Complex.Var AS Data
}
""";
private const string ProjectWithComplexVarUnnamedStage =
"""
[{
"$project" : {
"$Very.Complex.Var" : 1
}
}]
""";
private const string ProjectWithComplexVarUnnamedScript =
"""
FROM "TestCollection" PIPELINE {
PROJECT
$Very.Complex.Var
}
""";
private const string ProjectExcludeStage =
"""
[{
"$project" : {
"CurvePrefix" : 0,
"Data" : 0
}
}]
""";
private const string ProjectExcludeScript =
"""
FROM "TestCollection" PIPELINE {
PROJECT EXCLUDE
CurvePrefix,
Data
}
""";
// https://www.mongodb.com/docs/manual/reference/operator/query/exists/
private const string MatchWithProjectionStage =
"""
[{
"$match" : {
"$or" : [{
"RhoDetails.RhoImpact" : {
"$exists" : true
}
}, {
"RhoDetails.Rho2NdOrderImpact" : {
"$exists" : true
}
}]
}
}]
""";
private const string MatchWithProjectionScript =
"""
FROM "TestCollection" PIPELINE {
WHERE
$RhoDetails.RhoImpact EXISTS
OR $RhoDetails.Rho2NdOrderImpact EXISTS
}
""";
private const string ProjectArrayStage =
"""
[{
"$project" : {
"CurveKey" : {
"$concat" : ["$CurvePrefix", "$Data.k"]
},
"Ccy" : 1,
"Curve" : 1,
"FundingName" : 1,
"Tenor" : "$Data.k",
"Order" : 1,
"Data" : [{
"Type" : "$Type",
"Value" : "$Data.v"
}]
}
}]
""";
private const string ProjectArrayScript =
"""
FROM "TestCollection" PIPELINE {
PROJECT
concat( CurvePrefix, $Data.k ) AS CurveKey,
Ccy,
Curve,
FundingName,
$Data.k AS Tenor,
Order,
[
{
Type,
$Data.v AS Value
}
] AS Data
}
""";
private const string ProjectArrayComplexStage =
"""
[{
"$project" : {
"Tenor" : 1,
"Value" : {
"$arrayToObject" : {
"$map" : {
"input" : "$Data",
"as" : "d",
"in" : ["$$d.Type", "$$d.Value"]
}
}
}
}
}]
""";
private const string ProjectArrayComplexScript =
"""
FROM "TestCollection" PIPELINE {
PROJECT
Tenor,
arrayToObject( map(
input: Data,
as: "d",
in: [
"$$d.Type",
"$$d.Value"
]
) ) AS Value
}
""";
private const string ProjectWithNamedArgsStage =
"""
[{
"$project" : {
"Value" : {
"$map" : {
"input" : "$Data",
"as" : "d",
"in" : ["$$d.Type", "$$d.Value"]
}
}
}
}]
""";
private const string ProjectWithNamedArgsScript =
"""
FROM "TestCollection" PIPELINE {
PROJECT
map(
input: Data,
as: "d",
in: [
"$$d.Type",
"$$d.Value"
]
) AS Value
}
""";
private const string ProjectNestedFuncStage =
"""
[ {
"$project" : {
"Value" : {
"$arrayToObject" : {
"$map" : {
"input" : "$Data",
"as" : "d",
"in" : ["$$d.Type", "$$d.Value"]
}
}
}
}
}]
""";
private const string ProjectNestedFuncScript =
"""
FROM "TestCollection" PIPELINE {
PROJECT
arrayToObject( map(
input: Data,
as: "d",
in: [
"$$d.Type",
"$$d.Value"
]
) ) AS Value
}
""";
private const string GroupStage =
"""
[{
"$group" : {
"_id" : {
"CurveKey" : "$CurveKey",
"Ccy" : "$Ccy",
"Curve" : "$Curve",
"FundingName" : "$FundingName",
"Tenor" : "$Tenor"
},
"Order" : {
"$max" : "$Order"
},
"Rho2ndOrderImpact" : {
"$sum" : "$Value.Rho2ndOrderImpact"
}
}
}]
""";
private const string GroupScript =
"""
FROM "TestCollection" PIPELINE {
GROUP BY
CurveKey,
Ccy,
Curve,
FundingName,
Tenor
LET
max( Order ) AS Order,
sum( $Value.Rho2ndOrderImpact ) AS Rho2ndOrderImpact
}
""";
private const string MatchUnaryExpressionsStage =
"""
[{
"$match" : {
"$or" : [{
"RhoImpact" : {
"$gt" : 1E-08
}
}]
}
}]
""";
private const string MatchUnaryExpressionsScript =
"""
FROM "TestCollection" PIPELINE {
WHERE
RhoImpact > 1E-08
}
""";
private const string LookupStage =
"""
[{
"$lookup" : {
"from" : "PnL-Market",
"localField" : "_id.CurveKey",
"foreignField" : "_id",
"as" : "Market"
}
}]
""";
private const string LookupScript =
"""
FROM "TestCollection" PIPELINE {
JOIN "PnL-Market" AS Market ON
$_id.CurveKey == _id
}
""";
private const string ProjectWithIdStage =
"""
[{
"$project" : {
"_id" : {
"Ccy" : 1,
"Curve" : 1
},
"OpeningCurve" : "$OpeningCurve"
}
}]
""";
private const string ProjectWithIdScript =
"""
FROM "TestCollection" PIPELINE {
PROJECT ID {
Ccy,
Curve
}
OpeningCurve
}
""";
private const string AddFieldsStage =
"""
[{
"$addFields" : {
"_id.Order" : "$Order",
"_id.Data" : [{
"k" : "OpeningCurve",
"v" : "$Market.Opening"
}, {
"k" : "CurveMove",
"v" : "$Market.Move"
}, {
"k" : "Value",
"v" : "$Value"
}]
}
}]
""";
private const string AddFieldsScript =
"""
FROM "TestCollection" PIPELINE {
ADD
Order AS "_id.Order",
[
{
"OpeningCurve" AS "k",
$Market.Opening AS "v"
},
{
"CurveMove" AS "k",
$Market.Move AS "v"
},
{
"Value" AS "k",
Value AS "v"
}
] AS "_id.Data"
}
""";
private const string SubtractStage =
"""
[{
"$project" : {
"RhoMove" : {
"$subtract" : ["$ClosingRho", "$OpeningRho"]
}
}
}]
""";
private const string SubtractScript =
"""
FROM "TestCollection" PIPELINE {
PROJECT
ClosingRho - OpeningRho AS RhoMove
}
""";
private const string BucketStage =
"""
[
{
"$bucket": {
"groupBy": {
"$divide": [
"$Field1",
100.1
]
},
"boundaries": [
1,
10,
100,
1000
],
"default": "Ignored",
"output": {
"Gain": {
"$divide": [
"$Field1",
100.1
]
}
}
}
}
]
""";
private const string BucketScript =
"""
FROM "TestCollection" PIPELINE {
BUCKET
Field1 / 100.1
BOUNDARIES 1, 10, 100, 1000
DEFAULT "Ignored"
LET
Field1 / 100.1 AS Gain
}
""";
private const string BucketAutoStage =
"""
[
{
"$bucketAuto": {
"groupBy": {
"$divide": [
"$Field1",
100.1
]
},
"buckets": 5,
"granularity": "POWERSOF2",
"output": {
"Gain": {
"$divide": [
"$Field1",
100.1
]
}
}
}
}
]
""";
private const string BucketAutoScript =
"""
FROM "TestCollection" PIPELINE {
BUCKET AUTO
Field1 / 100.1
BUCKETS 5
GRANULARITY "POWERSOF2"
LET
Field1 / 100.1 AS Gain
}
""";
private const string FacetStage =
"""
[
{
"$facet": {
"categorizedByTags": [
{ "$unwind": "$tags" },
{ "$sortByCount": "$tags" }
],
"categorizedByPrice": [
{ "$match": { "price": { "$exists": 1 } } },
{
"$bucket": {
"groupBy": "$price",
"boundaries": [ 0, 150, 200, 300, 400 ],
"default": "Other",
"output": {
"count": { "$sum": 1 },
"titles": { "$push": "$title" }
}
}
}
],
"categorizedByYears(Auto)": [
{
"$bucketAuto": {
"groupBy": "$year",
"buckets": 4
}
}
]
}
}
]
""";
private const string FacetScript =
"""
FROM "TestCollection" PIPELINE {
FACET
categorizedByTags PIPELINE {
UNWIND tags
DO
{
"$sortByCount": "$tags"
}
},
categorizedByPrice PIPELINE {
WHERE
price == exists( 1 )
BUCKET
price
BOUNDARIES 0, 150, 200, 300, 400
DEFAULT "Other"
LET
sum( 1 ) AS count,
push( title ) AS titles
},
'categorizedByYears(Auto)' PIPELINE {
BUCKET AUTO
year
BUCKETS 4
}
}
""";
private const string MatchHackExistsStage =
"""
[{
"$match": {
"$or": [
{ "RhoDetails.RhoImpact": { "$gt": {} } },
{ "RhoDetails.Rho2NdOrderImpact": { "$gt": {} } }
]
}
}]
""";
private const string MatchHackExistsScript =
"""
FROM "TestCollection" PIPELINE {
WHERE
$RhoDetails.RhoImpact == exists( true )
OR $RhoDetails.Rho2NdOrderImpact == exists( true )
}
""";
private const string MatchExtraFilterStage =
"""
[{
"$match": {
"$and" : [
{ "COB" : { "$eq" : { "$date" : "2025-06-02T00:00:00Z" } } },
{ "$or" : [
{ "Department" : { "$eq" : "CPM" } },
{ "Department" : { "$eq" : "FX Metals Controlling" } }
]}
]}
}]
""";
private const string MatchExtraFilterScript =
"""
FROM "TestCollection" PIPELINE {
WHERE
COB == date( "2025-06-02T00:00:00Z" )
AND (Department == "CPM"
OR Department == "FX Metals Controlling")
}
""";
private static void TestPipeline(string pipeline, string expectedScript)
{
var ast = LanguageParser.ParseAggregationJsonToAST("TestCollection", pipeline);
var text = ast.AsText();
Assert.Multiple( () =>
{
Assert.That( ast, Is.Not.Null );
Assert.That( ast.Pipeline, Is.Not.Null );
Assert.That( text.Replace("\r", ""), Is.EqualTo(expectedScript.Replace("\r", "")) );
});
}
[Test] public void FxVegaBreakdownByTenor() => TestPipeline(Data.FxVegaBreakdownByTenor.Pipeline, Data.FxVegaBreakdownByTenor.Script);
[Test] public void RhoBreakdownByTenor() => TestPipeline(Data.RhoBreakdownByTenor.Pipeline, Data.RhoBreakdownByTenor.Script);
[Test] public void Match() => TestPipeline(MatchStage, MatchScript);
[Test] public void MatchWithProjection() => TestPipeline(MatchWithProjectionStage, MatchWithProjectionScript);
[Test] public void Project() => TestPipeline(ProjectStage, ProjectScript);
[Test] public void ProjectWithComplexVarNamed() => TestPipeline(ProjectWithComplexVarNamedStage, ProjectWithComplexVarNamedScript);
[Test] public void ProjectWithComplexVarUnnamed() => TestPipeline(ProjectWithComplexVarUnnamedStage, ProjectWithComplexVarUnnamedScript);
[Test] public void ProjectExclude() => TestPipeline(ProjectExcludeStage, ProjectExcludeScript);
[Test] public void ProjectArray() => TestPipeline(ProjectArrayStage, ProjectArrayScript);
[Test] public void ProjectArrayComplex() => TestPipeline(ProjectArrayComplexStage, ProjectArrayComplexScript);
[Test] public void ProjectWithNamedArgs() => TestPipeline(ProjectWithNamedArgsStage, ProjectWithNamedArgsScript);
[Test] public void ProjectNestedFunc() => TestPipeline(ProjectNestedFuncStage, ProjectNestedFuncScript);
[Test] public void Group() => TestPipeline(GroupStage, GroupScript);
[Test] public void MatchUnaryExpressions() => TestPipeline(MatchUnaryExpressionsStage, MatchUnaryExpressionsScript);
[Test] public void Lookup() => TestPipeline(LookupStage, LookupScript);
[Test] public void ProjectWithId() => TestPipeline(ProjectWithIdStage, ProjectWithIdScript);
[Test] public void AddFields() => TestPipeline(AddFieldsStage, AddFieldsScript);
[Test] public void Subtract() => TestPipeline(SubtractStage, SubtractScript);
[Test] public void Bucket() => TestPipeline(BucketStage, BucketScript);
[Test] public void BucketAuto() => TestPipeline(BucketAutoStage, BucketAutoScript);
[Test] public void Facet() => TestPipeline(FacetStage, FacetScript);
[Test] public void MatchHackExists() => TestPipeline(MatchHackExistsStage, MatchHackExistsScript);
[Test] public void MatchExtraFilter() => TestPipeline(MatchExtraFilterStage, MatchExtraFilterScript);
}