Search code examples
mongodbgoaggregation-frameworkmongo-go

incorrectly drafted MongoDB aggregation pipeline $match stage


I'm trying to make a query in golang language (below I've attached working code of pure mongodb query) using go.mongodb.org/mongo-driver/mongo library, below is golang query code. I can't get matchStage to work correctly, I've tried many variants and I'm sure I'm just very inattentive or just don't understand

How can I use $match, $expr, $and and $lte at once to make a correct matchStage?

func (r *Mongo) ChatHistory(ctx context.Context, chatID string, f *Filter) ([]*Message, error) {
    matchStage := bson.D{
        primitive.E{
            Key: "$match",
            Value: bson.D{
                primitive.E{Key: "$expr", Value: bson.D{
                    primitive.E{Key: "$and", Value: bson.A{
                        bson.D{
                            primitive.E{Key: "$lte", Value: bson.D{
                                primitive.E{
                                    Key:   "$create_date",
                                    Value: f.Date, // int64
                                },
                            }},
                        },
                    }},
                }},
            },
        },
    }
    sortStage := bson.D{
        {
            Key: "$sort", Value: bson.D{
                primitive.E{Key: "create_date", Value: -1},
            },
        },
    }
    limitStage := bson.D{primitive.E{Key: "$limit", Value: f.Count}}

    cursor, err := r.colMessage.Aggregate(ctx, mongo.Pipeline{matchStage, sortStage, limitStage})
    if err != nil {
        l.Error().Err(err).Msg("failed find")
        return nil, err
    }

    var res []*Message
    if err = cursor.All(ctx, &res); err != nil {
        l.Error().Err(err).Msg("failed find all documents")
        return nil, err
    }

    if err = cursor.Close(ctx); err != nil {
        l.Error().Err(err).Msg("failed close cursor")
        return nil, err
    }

    return res, nil
}

Error: (InvalidPipelineOperator) Unrecognized expression '$create_date'

MongoDB playground link


Solution

  • Value of $lte must be an array not a document:

    matchStage := bson.D{
        primitive.E{
            Key: "$match",
            Value: bson.D{
                primitive.E{Key: "$expr", Value: bson.D{
                    primitive.E{Key: "$and", Value: bson.A{
                        bson.D{
                            primitive.E{Key: "$lte", Value: bson.A{
                                "$create_date",
                                f.Date, // int64
                            }},
                        },
                    }},
                }},
            },
        },
    }
    

    Also note that you can leave out the primitive.E type from the composite literal:

    matchStage := bson.D{
        {
            Key: "$match",
            Value: bson.D{
                {Key: "$expr", Value: bson.D{
                    {Key: "$and", Value: bson.A{
                        bson.D{
                            {Key: "$lte", Value: bson.A{
                                "$create_date",
                                f.Date, // int64
                            }},
                        },
                    }},
                }},
            },
        },
    }
    

    But note that your expression on the mongo playground is incorrect. Quoting from the doc:

    $match takes a document that specifies the query conditions. The query syntax is identical to the read operation query syntax ...

    When using $expr, you have to use $eq, for example:

    matchStage := bson.D{
        {
            Key: "$match",
            Value: bson.D{
                {Key: "$expr", Value: bson.D{
                    {Key: "$and", Value: bson.A{
                        bson.D{
                            {Key: "$eq", Value: bson.A{
                                "$chat_id",
                                chatID,
                            }},
                        },
                        bson.D{
                            {Key: "$lte", Value: bson.A{
                                "$create_date",
                                f.Date, // int64
                            }},
                        },
                    }},
                }},
            },
        },
    }
    

    Try it here: https://mongoplayground.net/p/SBEJD-Fyhjl

    You should use a normal query document in $match.

    See this equivalent, much simpler solution:

    matchStage := bson.D{
        {
            Key: "$match",
            Value: bson.D{
                {Key: "chat_id", Value: chatID},
                {Key: "create_date", Value: bson.D{
                    {
                        Key:   "$lte",
                        Value: f.Date, // int64
                    }},
                },
            },
        },
    }
    

    And even much-much simpler if you use bson.M instead of bson.D:

    matchStage := bson.M{
        "$match": bson.M{
            "chat_id":     chatID,
            "create_date": bson.M{"$lte": f.Date},
        },
    }
    

    Of course in this last case you can't use mongo.Pipeline for the pipeline, but []any or []bson.M would also do.