|
14 | 14 | */
|
15 | 15 |
|
16 | 16 | using System;
|
| 17 | +using System.Collections.Generic; |
17 | 18 | using System.Linq;
|
18 | 19 | using MongoDB.Bson;
|
19 | 20 | using MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions;
|
@@ -449,6 +450,79 @@ public override AstNode VisitMapExpression(AstMapExpression node)
|
449 | 450 | }
|
450 | 451 | }
|
451 | 452 |
|
| 453 | + // { $map : { input : { $map : { input : <innerInput>, as : "inner", in : { A : <exprA>, B : <exprB>, ... } } }, as: "outer", in : { F : '$$outer.A', G : "$$outer.B", ... } } } |
| 454 | + // => { $map : { input : <innerInput>, as: "inner", in : { F : <exprA>, G : <exprB>, ... } } } |
| 455 | + if (node.Input is AstMapExpression innerMapExpression && |
| 456 | + node.As is var outerVar && |
| 457 | + node.In is AstComputedDocumentExpression outerComputedDocumentExpression && |
| 458 | + innerMapExpression.Input is var innerInput && |
| 459 | + innerMapExpression.As is var innerVar && |
| 460 | + innerMapExpression.In is AstComputedDocumentExpression innerComputedDocumentExpression && |
| 461 | + outerComputedDocumentExpression.Fields.All(outerField => |
| 462 | + outerField.Value is AstGetFieldExpression outerGetFieldExpression && |
| 463 | + outerGetFieldExpression.Input == outerVar && |
| 464 | + outerGetFieldExpression.FieldName is AstConstantExpression { Value : BsonString { Value : var matchingFieldName } } && |
| 465 | + innerComputedDocumentExpression.Fields.Any(innerField => innerField.Path == matchingFieldName))) |
| 466 | + { |
| 467 | + var rewrittenOuterFields = new List<AstComputedField>(); |
| 468 | + foreach (var outerField in outerComputedDocumentExpression.Fields) |
| 469 | + { |
| 470 | + var outerGetFieldExpression = (AstGetFieldExpression)outerField.Value; |
| 471 | + var matchingFieldName = ((AstConstantExpression)outerGetFieldExpression.FieldName).Value.AsString; |
| 472 | + var matchingInnerField = innerComputedDocumentExpression.Fields.Single(innerField => innerField.Path == matchingFieldName); |
| 473 | + var rewrittenOuterField = AstExpression.ComputedField(outerField.Path, matchingInnerField.Value); |
| 474 | + rewrittenOuterFields.Add(rewrittenOuterField); |
| 475 | + } |
| 476 | + |
| 477 | + var simplified = AstExpression.Map( |
| 478 | + input: innerInput, |
| 479 | + @as: innerVar, |
| 480 | + @in: AstExpression.ComputedDocument(rewrittenOuterFields)); |
| 481 | + |
| 482 | + return Visit(simplified); |
| 483 | + } |
| 484 | + |
| 485 | + // { $map : { input : [{ A : <exprA1>, B : <exprB1>, ... }, { A : <exprA2>, B : <exprB2>, ... }, ...], as : "item", in: { F : "$$item.A", G : "$$item.B", ... } } } |
| 486 | + // => [{ F : <exprA1>, G : <exprB1>", ... }, { F : <exprA2>, G : <exprB2>, ... }, ...] |
| 487 | + if (node.Input is AstComputedArrayExpression inputComputedArray && |
| 488 | + inputComputedArray.Items.Count >= 1 && |
| 489 | + inputComputedArray.Items[0] is AstComputedDocumentExpression firstComputedDocument && |
| 490 | + firstComputedDocument.Fields.Select(inputField => inputField.Path).ToArray() is var inputFieldNames && |
| 491 | + inputComputedArray.Items.Skip(1).All(otherItem => |
| 492 | + otherItem is AstComputedDocumentExpression otherComputedDocument && |
| 493 | + otherComputedDocument.Fields.Select(otherField => otherField.Path).SequenceEqual(inputFieldNames)) && |
| 494 | + node.As is var itemVar && |
| 495 | + node.In is AstComputedDocumentExpression mappedDocument && |
| 496 | + mappedDocument.Fields.All(mappedField => |
| 497 | + mappedField.Value is AstGetFieldExpression mappedGetField && |
| 498 | + mappedGetField.Input == itemVar && |
| 499 | + mappedGetField.FieldName is AstConstantExpression { Value : BsonString { Value : var matchingFieldName } } && |
| 500 | + inputFieldNames.Contains(matchingFieldName))) |
| 501 | + { |
| 502 | + var rewrittenItems = new List<AstExpression>(); |
| 503 | + foreach (var inputItem in inputComputedArray.Items) |
| 504 | + { |
| 505 | + var inputDocument = (AstComputedDocumentExpression)inputItem; |
| 506 | + |
| 507 | + var rewrittenFields = new List<AstComputedField>(); |
| 508 | + foreach (var mappedField in mappedDocument.Fields) |
| 509 | + { |
| 510 | + var mappedGetField = (AstGetFieldExpression)mappedField.Value; |
| 511 | + var matchingFieldName = ((AstConstantExpression)mappedGetField.FieldName).Value.AsString; |
| 512 | + var matchingInputField = inputDocument.Fields.Single(inputField => inputField.Path == matchingFieldName); |
| 513 | + var rewrittenField = AstExpression.ComputedField(mappedField.Path, matchingInputField.Value); |
| 514 | + rewrittenFields.Add(rewrittenField); |
| 515 | + } |
| 516 | + |
| 517 | + var rewrittenItem = AstExpression.ComputedDocument(rewrittenFields); |
| 518 | + rewrittenItems.Add(rewrittenItem); |
| 519 | + } |
| 520 | + |
| 521 | + var simplified = AstExpression.ComputedArray(rewrittenItems); |
| 522 | + |
| 523 | + return Visit(simplified); |
| 524 | + } |
| 525 | + |
452 | 526 | return base.VisitMapExpression(node);
|
453 | 527 |
|
454 | 528 | static AstExpression UltimateGetFieldInput(AstGetFieldExpression getField)
|
@@ -567,7 +641,32 @@ arg is AstBinaryExpression argBinaryExpression &&
|
567 | 641 | return AstExpression.Binary(oppositeComparisonOperator, argBinaryExpression.Arg1, argBinaryExpression.Arg2);
|
568 | 642 | }
|
569 | 643 |
|
| 644 | + // { $arrayToObject : [[{ k : 'A', v : <exprA> }, { k : 'B', v : <exprB> }, ...]] } => { A : <exprA>, B : <exprB>, ... } |
| 645 | + if (node.Operator == AstUnaryOperator.ArrayToObject && |
| 646 | + arg is AstComputedArrayExpression computedArrayExpression && |
| 647 | + computedArrayExpression.Items.All( |
| 648 | + item => |
| 649 | + item is AstComputedDocumentExpression computedDocumentExpression && |
| 650 | + computedDocumentExpression.Fields.Count == 2 && |
| 651 | + computedDocumentExpression.Fields[0].Path == "k" && |
| 652 | + computedDocumentExpression.Fields[1].Path == "v" && |
| 653 | + computedDocumentExpression.Fields[0].Value is AstConstantExpression { Value : { IsString : true } })) |
| 654 | + { |
| 655 | + var computedFields = computedArrayExpression.Items.Select(KeyValuePairDocumentToComputedField); |
| 656 | + return AstExpression.ComputedDocument(computedFields); |
| 657 | + } |
| 658 | + |
570 | 659 | return node.Update(arg);
|
| 660 | + |
| 661 | + static AstComputedField KeyValuePairDocumentToComputedField(AstExpression expression) |
| 662 | + { |
| 663 | + // caller has verified that expression is of the form: { k : <stringConstant>, v : <valueExpression> } |
| 664 | + var keyValuePairDocumentExpression = (AstComputedDocumentExpression)expression; |
| 665 | + var keyConstantExpression = (AstConstantExpression)keyValuePairDocumentExpression.Fields[0].Value; |
| 666 | + var valueExpression = keyValuePairDocumentExpression.Fields[1].Value; |
| 667 | + |
| 668 | + return AstExpression.ComputedField(keyConstantExpression.Value.AsString, valueExpression); |
| 669 | + } |
571 | 670 | }
|
572 | 671 | }
|
573 | 672 | }
|
0 commit comments