// Cannot implicitly shard accessed collections because unsupported use of sharded collection
// for target collection of $lookup and $graphLookup.
// @tags: [assumes_unsharded_collection]

/**
 * Tests that the $graphLookup stage respects the collation when matching between the
 * 'connectFromField' and the 'connectToField'.  $graphLookup should use the collation
 * set on the aggregation, or the default collation of the collection.
 */
(function() {
    "use strict";

    var res;
    const caseInsensitiveUS = {collation: {locale: "en_US", strength: 2}};
    const caseSensitiveUS = {collation: {locale: "en_US", strength: 3}};

    var coll = db.collation_graphlookup;
    var foreignColl = db.collation_graphlookup_foreign;

    // Test that $graphLookup respects the collation set on the aggregation pipeline. Case
    // insensitivity should mean that we find both "jeremy" and "jimmy" as friends.
    coll.drop();
    assert.writeOK(coll.insert({username: "erica", friends: ["jeremy", "jimmy"]}));
    assert.writeOK(coll.insert([{username: "JEREMY"}, {username: "JIMMY"}]));

    res = coll.aggregate(
                  [
                    {$match: {username: "erica"}},
                    {
                      $graphLookup: {
                          from: coll.getName(),
                          startWith: "$friends",
                          connectFromField: "friends",
                          connectToField: "username",
                          as: "friendUsers"
                      }
                    }
                  ],
                  caseInsensitiveUS)
              .toArray();
    assert.eq(1, res.length);
    assert.eq("erica", res[0].username);
    assert.eq(2, res[0].friendUsers.length);

    // Negative test: ensure that we don't find any friends when the collation is simple.
    res = coll.aggregate([
                  {$match: {username: "erica"}},
                  {
                    $graphLookup: {
                        from: coll.getName(),
                        startWith: "$friends",
                        connectFromField: "friends",
                        connectToField: "username",
                        as: "friendUsers"
                    }
                  }
              ])
              .toArray();
    assert.eq(1, res.length);
    assert.eq("erica", res[0].username);
    assert.eq(0, res[0].friendUsers.length);

    coll.drop();
    assert.commandWorked(db.createCollection(coll.getName(), caseInsensitiveUS));
    assert.writeOK(coll.insert({username: "erica", friends: ["jeremy", "jimmy"]}));
    foreignColl.drop();
    assert.commandWorked(db.createCollection(foreignColl.getName(), caseSensitiveUS));
    assert.writeOK(foreignColl.insert([{username: "JEREMY"}, {username: "JIMMY"}]));

    // Test that $graphLookup inherits the default collation of the collection on which it is run,
    // and that this collation is used instead of the default collation of the foreign collection.
    res = coll.aggregate([
                  {$match: {username: "erica"}},
                  {
                    $graphLookup: {
                        from: foreignColl.getName(),
                        startWith: "$friends",
                        connectFromField: "friends",
                        connectToField: "username",
                        as: "friendUsers"
                    }
                  }
              ])
              .toArray();
    assert.eq(1, res.length);
    assert.eq("erica", res[0].username);
    assert.eq(2, res[0].friendUsers.length);

    // Test that we don't use the collation to dedup string _id values. This would cause us to miss
    // nodes in the graph that have distinct _id values which compare equal under the collation.
    coll.drop();
    assert.writeOK(coll.insert({username: "erica", friends: ["jeremy"]}));
    assert.writeOK(coll.insert({_id: "foo", username: "JEREMY", friends: ["jimmy"]}));
    assert.writeOK(coll.insert({_id: "FOO", username: "jimmy", friends: []}));

    res = coll.aggregate(
                  [
                    {$match: {username: "erica"}},
                    {
                      $graphLookup: {
                          from: coll.getName(),
                          startWith: "$friends",
                          connectFromField: "friends",
                          connectToField: "username",
                          as: "friendUsers"
                      }
                    }
                  ],
                  caseInsensitiveUS)
              .toArray();
    assert.eq(1, res.length);
    assert.eq("erica", res[0].username);
    assert.eq(2, res[0].friendUsers.length);

    // Test that the result set is not deduplicated under the collation. If two documents are
    // entirely equal under the collation, they should still both get returned in the "as" field.
    coll.drop();
    assert.writeOK(coll.insert({username: "erica", friends: ["jeremy"]}));
    assert.writeOK(coll.insert({_id: "foo", username: "jeremy"}));
    assert.writeOK(coll.insert({_id: "FOO", username: "JEREMY"}));

    res = coll.aggregate(
                  [
                    {$match: {username: "erica"}},
                    {
                      $graphLookup: {
                          from: coll.getName(),
                          startWith: "$friends",
                          connectFromField: "friends",
                          connectToField: "username",
                          as: "friendUsers"
                      }
                    }
                  ],
                  caseInsensitiveUS)
              .toArray();
    assert.eq(1, res.length);
    assert.eq("erica", res[0].username);
    assert.eq(2, res[0].friendUsers.length);
})();
