Here's a proposal:
join takes 2 arguments:
- left_keythat can be a string or a function (it's the same thing as what eqJoin require for the first argument
- other_table: the second sequence is a table.
The command has the following options:
- index - <string>: The name of the index used on- other_tableto perform the join, default to the primary key.
- group - <string>: If this option is undefined, the join command would operate as it does now. If it's set to a string, it would group the right values into an array and inject in the field (or just inject the document is- uniqueis set to- true(the value for- group). The default is- undefined.
- outer_join - <boolean>whether the join should behave like an outer join or not. By default- true. The value is always false when- groupis- false. Edge case: the joined value should be an empty array, or null if no document can be joined.
- multi - <boolean>: if set to true, the join is performed on each value of the left key (supposing that the left key maps to an array). Default to false. If the value is not an array, the join should not be performed:
- unique - <boolean>: If more than one document is found, we should return an error.
- compose - <function>: compose the joined sequence/object with a method. The function provides takes one argument which is the sequence/object returned by the join operation.
- overwrite - <boolean>replace the document with the joined one.
Examples:
- Simple join:
r.table("posts").join("id", r.table("comments"), {index: "postId", group: "comments"}
// equivalent to
r.table("posts").merge(function(post) {
    return post.merge({
        comments: r.table("comments").getAll(post("id"), {index: "postId"}).coerceTo("ARRAY")
    })
})- With outerJoin: false
r.table("posts").join("id", r.table("comments"), {index: "postId", group: "comments", outerJoin: false}
// equivalent to
r.table("posts").concatMap(function(post) {
    return r.table("comments").getAll(post("id"), {index: "postId"}).coerceTo("ARRAY").do(function(sequence) {
        r.branch(
            sequence.isEmpty(),
            [],
            [post.merge({ comments: sequence })]
        )
    })
})- Compose a joined sequence with an orderBy
r.table("posts").join("id", r.table("comments"), {
    index: "postId",
    group: "comments",
    compose: function(sequence) {
        return sequence.orderBy("date")
    }
}
// equivalent to
r.table("posts").merge(function(post) {
    return post.merge({
        comments: r.table("comments").getAll(post("id"), {index: "postId"})
            .orderBy("date").coerceTo("ARRAY") // the coerce here is optional
    })
})- Nested joins
r.table("posts").join("id", r.table("comments"), {
    index: "postId",
    group: "comments",
    compose: function(sequence) {
        return sequence.join("authorId", r.table("author"), {unique: true, group: "author"}
    }
}
// equivalent to
r.table("posts").merge(function(post) {
    return post.merge({
        comments: r.table("comments").getAll(post("id"), {index: "postId"})
            .merge(function(comment) {
               return { author: r.table("author").get(comment("authorId") }
            }).coerceTo("ARRAY") // the coerce here is optional
    })
})- Nested joined on a non primary key
r.table("posts").join("id", r.table("comments"), {
    index: "postId",
    group: "comments",
    compose: function(sequence) {
        return sequence.join("authorId", r.table("author"), {unique: true, group: "author", index: "uniqueId"}
    }
}
// equivalent to
r.table("posts").merge(function(post) {
    return post.merge({
        comments: r.table("comments").getAll(post("id"), {index: "postId"})
            .merge(function(comment) {
                 return { 
                    author: r.do(
                        r.table("author").getAll(comment("authorId") , {index: "uniqueId"}).coerceTo("ARRAY"),
                        function(sequence) {
                            r.branch(
                                sequence.count().gt(1),
                                r.error("More than one document found")
                                sequence.nth(0).default(null)
                            )
                        })
                 }
            }).coerceTo("ARRAY") // the coerce here is optional
    })
})- Many to many relations where the link between two documents is not returned.
r.table("students").join("id", r.table("student_class"), {
    index: "studentId",
    group: "classes",
    compose: function(sequence) {
        return sequence.join("classId", r.table("class"), {unique: true, overwrite: true}
    }
}
// equivalent to
r.table("students").merge(function(student) {
    return {
        classes: r.table("student_class").getAll(student("id"), {index: "studentId"}).map(function(link) {
            return r.table("class").get(link("classId"))
        })
    }
})