// public static methods
        public static AstPipeline Translate(TranslationContext context, MethodCallExpression expression)
        {
            var method    = expression.Method;
            var arguments = expression.Arguments;

            if (method.Is(QueryableMethod.Select))
            {
                var sourceExpression = arguments[0];
                var pipeline         = ExpressionToPipelineTranslator.Translate(context, sourceExpression);
                var sourceSerializer = pipeline.OutputSerializer;

                var selectorLambda = ExpressionHelper.UnquoteLambda(arguments[1]);
                if (selectorLambda.Body == selectorLambda.Parameters[0])
                {
                    return(pipeline); // ignore identity projection: Select(x => x)
                }

                var selectorTranslation = ExpressionToAggregationExpressionTranslator.TranslateLambdaBody(context, selectorLambda, sourceSerializer, asRoot: true);
                var(projectStage, projectionSerializer) = ProjectionHelper.CreateProjectStage(selectorTranslation);
                pipeline = pipeline.AddStages(projectionSerializer, projectStage);

                return(pipeline);
            }

            throw new ExpressionNotSupportedException(expression);
        }
        // public static methods
        public static AstPipeline Translate(TranslationContext context, MethodCallExpression expression)
        {
            var method    = expression.Method;
            var arguments = expression.Arguments;

            if (method.Is(QueryableMethod.Join))
            {
                var outerExpression = arguments[0];
                var pipeline        = ExpressionToPipelineTranslator.Translate(context, outerExpression);
                var outerSerializer = pipeline.OutputSerializer;

                var wrapOuterStage = AstStage.Project(
                    AstProject.Set("_outer", AstExpression.Var("ROOT")),
                    AstProject.ExcludeId());
                var wrappedOuterSerializer = WrappedValueSerializer.Create("_outer", outerSerializer);

                var innerExpression = arguments[1];
                var(innerCollectionName, innerSerializer) = innerExpression.GetCollectionInfo(containerExpression: expression);

                var outerKeySelectorLambda = ExpressionHelper.UnquoteLambda(arguments[2]);
                var localFieldPath         = outerKeySelectorLambda.GetFieldPath(context, wrappedOuterSerializer);

                var innerKeySelectorLambda = ExpressionHelper.UnquoteLambda(arguments[3]);
                var foreignFieldPath       = innerKeySelectorLambda.GetFieldPath(context, innerSerializer);

                var lookupStage = AstStage.Lookup(
                    from: innerCollectionName,
                    match: new AstLookupStageEqualityMatch(localFieldPath, foreignFieldPath),
                    @as: "_inner");

                var unwindStage = AstStage.Unwind("_inner");

                var resultSelectorLambda = ExpressionHelper.UnquoteLambda(arguments[4]);
                var root                      = AstExpression.Var("ROOT", isCurrent: true);
                var outerParameter            = resultSelectorLambda.Parameters[0];
                var outerField                = AstExpression.GetField(root, "_outer");
                var outerSymbol               = context.CreateSymbol(outerParameter, outerField, outerSerializer);
                var innerParameter            = resultSelectorLambda.Parameters[1];
                var innerField                = AstExpression.GetField(root, "_inner");
                var innerSymbol               = context.CreateSymbol(innerParameter, innerField, innerSerializer);
                var resultSelectorContext     = context.WithSymbols(outerSymbol, innerSymbol);
                var resultSelectorTranslation = ExpressionToAggregationExpressionTranslator.Translate(resultSelectorContext, resultSelectorLambda.Body);
                var(projectStage, newOutputSerializer) = ProjectionHelper.CreateProjectStage(resultSelectorTranslation);

                pipeline = pipeline.AddStages(
                    newOutputSerializer,
                    wrapOuterStage,
                    lookupStage,
                    unwindStage,
                    projectStage);

                return(pipeline);
            }

            throw new ExpressionNotSupportedException(expression);
        }
        private static AstPipeline TranslateResultSelector(
            TranslationContext context,
            AstPipeline pipeline,
            ReadOnlyCollection <Expression> arguments,
            IBsonSerializer keySerializer,
            IBsonSerializer elementSerializer)
        {
            var resultSelectorLambda = ExpressionHelper.UnquoteLambda(arguments.Last());
            var root                      = AstExpression.Var("ROOT", isCurrent: true);
            var keyParameter              = resultSelectorLambda.Parameters[0];
            var keyField                  = AstExpression.GetField(root, "_id");
            var keySymbol                 = context.CreateSymbol(keyParameter, keyField, keySerializer);
            var elementsParameter         = resultSelectorLambda.Parameters[1];
            var elementsField             = AstExpression.GetField(root, "_elements");
            var elementsSerializer        = IEnumerableSerializer.Create(elementSerializer);
            var elementsSymbol            = context.CreateSymbol(elementsParameter, elementsField, elementsSerializer);
            var resultSelectContext       = context.WithSymbols(keySymbol, elementsSymbol);
            var resultSelectorTranslation = ExpressionToAggregationExpressionTranslator.Translate(resultSelectContext, resultSelectorLambda.Body);

            var(projectStage, projectionSerializer) = ProjectionHelper.CreateProjectStage(resultSelectorTranslation);
            return(pipeline.AddStages(projectionSerializer, projectStage));
        }