Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sort alphabets of languages for non indexed fields. #4260

Merged
merged 6 commits into from
Nov 21, 2019
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions gql/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -2599,6 +2599,10 @@ func getRoot(it *lex.ItemIterator) (gq *GraphQuery, rerr error) {
return nil, it.Errorf("Sorting by an attribute: [%s] can only be done once", val)
}
attr, langs := attrAndLang(val)
if len(langs) > 1 {
return nil, it.Errorf("Sorting by an attribute: [%s] "+
"can only be done on one language", val)
}
gq.Order = append(gq.Order,
&pb.Order{Attr: attr, Desc: key == "orderdesc", Langs: langs})
order[val] = true
Expand Down Expand Up @@ -3011,6 +3015,10 @@ func godeep(it *lex.ItemIterator, gq *GraphQuery) error {
"can only be done once", p.Val)
}
attr, langs := attrAndLang(p.Val)
if len(langs) > 1 {
return it.Errorf("Sorting by an attribute: [%s] "+
"can only be done on one language", p.Val)
}
curp.Order = append(curp.Order,
&pb.Order{Attr: attr, Desc: p.Key == "orderdesc", Langs: langs})
order[p.Val] = true
Expand Down
21 changes: 17 additions & 4 deletions gql/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3753,7 +3753,7 @@ func TestParseQueryWithAttrLang(t *testing.T) {
{
me(func: uid(0x1)) {
name
friend(first:5, orderasc: name@en:fr) {
friend(first:5, orderasc: name@en) {
name@en
}
}
Expand All @@ -3764,7 +3764,7 @@ func TestParseQueryWithAttrLang(t *testing.T) {
require.NotNil(t, res.Query)
require.Equal(t, 1, len(res.Query))
require.Equal(t, "name", res.Query[0].Children[1].Order[0].Attr)
require.Equal(t, []string{"en", "fr"}, res.Query[0].Children[1].Order[0].Langs)
require.Equal(t, []string{"en"}, res.Query[0].Children[1].Order[0].Langs)
}

func TestParseQueryWithAttrLang2(t *testing.T) {
Expand Down Expand Up @@ -4476,10 +4476,23 @@ func TestInvalidValUsage(t *testing.T) {
require.Contains(t, err.Error(), "Query syntax invalid.")
}

func TestOrderWithMultipleLangFail(t *testing.T) {
query := `
{
me(func: uid(0x1), orderasc: name@en:fr, orderdesc: lastname@ci, orderasc: salary) {
name
}
}
`
_, err := Parse(Request{Str: query})
require.Error(t, err)
require.Contains(t, err.Error(), "Sorting by an attribute: [name@en:fr] can only be done on one language")
}

func TestOrderWithLang(t *testing.T) {
query := `
{
me(func: uid(0x1), orderasc: name@en:fr:., orderdesc: lastname@ci, orderasc: salary) {
me(func: uid(0x1), orderasc: name@en, orderdesc: lastname@ci, orderasc: salary) {
name
}
}
Expand All @@ -4490,7 +4503,7 @@ func TestOrderWithLang(t *testing.T) {
require.Equal(t, 1, len(res.Query))
orders := res.Query[0].Order
require.Equal(t, "name", orders[0].Attr)
require.Equal(t, []string{"en", "fr", "."}, orders[0].Langs)
require.Equal(t, []string{"en"}, orders[0].Langs)
require.Equal(t, "lastname", orders[1].Attr)
require.Equal(t, []string{"ci"}, orders[1].Langs)
require.Equal(t, "salary", orders[2].Attr)
Expand Down
9 changes: 8 additions & 1 deletion query/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,8 @@ type Node {
}

name : string @index(term, exact, trigram) @count @lang .
name_lang : string @lang .
lang_type : string @index(exact) .
alt_name : [string] @index(term, exact, trigram) @count .
alias : string @index(exact, term, fulltext) .
abbr : string .
Expand Down Expand Up @@ -348,7 +350,12 @@ func populateCluster() {
<10005> <name> "Bob" .
<10006> <name> "Colin" .
<10007> <name> "Elizabeth" .

<10101> <name_lang> "zon"@sv .
<10101> <name_lang> "öffnen"@de .
<10101> <lang_type> "Test" .
<10102> <name_lang> "öppna"@sv .
<10102> <name_lang> "zumachen"@de .
<10102> <lang_type> "Test" .
<11000> <name> "Baz Luhrmann"@en .
<11001> <name> "Strictly Ballroom"@en .
<11002> <name> "Puccini: La boheme (Sydney Opera)"@en .
Expand Down
5 changes: 3 additions & 2 deletions query/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -2325,7 +2325,7 @@ func (sg *SubGraph) sortAndPaginateUsingFacet(ctx context.Context) error {
continue
}
if err := types.SortWithFacet(values, &pb.List{Uids: uids},
facetList, []bool{sg.Params.FacetOrderDesc}); err != nil {
facetList, []bool{sg.Params.FacetOrderDesc}, ""); err != nil {
return err
}
sg.uidMatrix[i].Uids = uids
Expand Down Expand Up @@ -2372,7 +2372,8 @@ func (sg *SubGraph) sortAndPaginateUsingVar(ctx context.Context) error {
if len(values) == 0 {
continue
}
if err := types.Sort(values, &pb.List{Uids: uids}, []bool{sg.Params.Order[0].Desc}); err != nil {
if err := types.Sort(values, &pb.List{Uids: uids},
[]bool{sg.Params.Order[0].Desc}, ""); err != nil {
return err
}
sg.uidMatrix[i].Uids = uids
Expand Down
2 changes: 1 addition & 1 deletion query/query1_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ func TestToFastJSONOrderLang(t *testing.T) {
query := `
{
me(func: uid(0x01)) {
friend(first:2, orderdesc: alias@en:de:.) {
friend(first:2, orderdesc: alias@en) {
alias
}
}
Expand Down
52 changes: 52 additions & 0 deletions query/query2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,58 @@ func TestToFastJSONOrderDesc2(t *testing.T) {
js)
}

func TestLanguageOrderNonIndexed1(t *testing.T) {
query := `
{
q(func:eq(lang_type, "Test"), orderasc: name_lang@de) {
name_lang@de
name_lang@sv
}
}
`

js := processQueryNoErr(t, query)
require.JSONEq(t,
`{
"data": {
"q": [{
"name_lang@de": "öffnen",
"name_lang@sv": "zon"
}, {
"name_lang@de": "zumachen",
"name_lang@sv": "öppna"
}]
}
}`,
js)
}

func TestLanguageOrderNonIndexed2(t *testing.T) {
query := `
{
q(func:eq(lang_type, "Test"), orderasc: name_lang@sv) {
name_lang@de
name_lang@sv
}
}
`

js := processQueryNoErr(t, query)
require.JSONEq(t,
`{
"data": {
"q": [{
"name_lang@de": "öffnen",
"name_lang@sv": "zon"
}, {
"name_lang@de": "zumachen",
"name_lang@sv": "öppna"
}]
}
}`,
js)
}

// Test sorting / ordering by dob.
func TestToFastJSONOrderDesc_pawan(t *testing.T) {

Expand Down
34 changes: 25 additions & 9 deletions types/sort.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,16 @@ import (
"github.com/dgraph-io/dgraph/protos/pb"
"github.com/dgraph-io/dgraph/x"
"github.com/pkg/errors"
"golang.org/x/text/collate"
"golang.org/x/text/language"
)

type sortBase struct {
values [][]Val // Each uid could have multiple values which we need to sort it by.
desc []bool // Sort orders for different values.
ul *pb.List
o []*pb.Facets
cl *collate.Collator // Compares Unicode strings according to the given collation order.
}

// Len returns size of vector.
Expand Down Expand Up @@ -70,7 +73,7 @@ func (s byValue) Less(i, j int) bool {
}

// Its either less or greater.
less := less(first[vidx], second[vidx])
less := less(first[vidx], second[vidx], s.cl)
if s.desc[vidx] {
return !less
}
Expand All @@ -81,7 +84,7 @@ func (s byValue) Less(i, j int) bool {

// SortWithFacet sorts the given array in-place and considers the given facets to calculate
// the proper ordering.
func SortWithFacet(v [][]Val, ul *pb.List, l []*pb.Facets, desc []bool) error {
func SortWithFacet(v [][]Val, ul *pb.List, l []*pb.Facets, desc []bool, lang string) error {
if len(v) == 0 || len(v[0]) == 0 {
return nil
}
Expand All @@ -93,16 +96,25 @@ func SortWithFacet(v [][]Val, ul *pb.List, l []*pb.Facets, desc []bool) error {
default:
return errors.Errorf("Value of type: %s isn't sortable", typ.Name())
}
var toBeSorted sort.Interface
b := sortBase{v, desc, ul, l}
toBeSorted = byValue{b}

var cl *collate.Collator
if lang != "" {
// Collator is nil if we are unable to parse the language.
// We default to bytewise comparison in that case.
if langTag, err := language.Parse(lang); err == nil {
cl = collate.New(langTag)
}
}

b := sortBase{v, desc, ul, l, cl}
toBeSorted := byValue{b}
sort.Sort(toBeSorted)
return nil
}

// Sort sorts the given array in-place.
func Sort(v [][]Val, ul *pb.List, desc []bool) error {
return SortWithFacet(v, ul, nil, desc)
func Sort(v [][]Val, ul *pb.List, desc []bool, lang string) error {
return SortWithFacet(v, ul, nil, desc, lang)
}

// Less returns true if a is strictly less than b.
Expand All @@ -117,10 +129,10 @@ func Less(a, b Val) (bool, error) {
default:
return false, errors.Errorf("Compare not supported for type: %v", a.Tid)
}
return less(a, b), nil
return less(a, b, nil), nil
}

func less(a, b Val) bool {
func less(a, b Val, cl *collate.Collator) bool {
if a.Tid != b.Tid {
return mismatchedLess(a, b)
}
Expand All @@ -134,6 +146,10 @@ func less(a, b Val) bool {
case UidID:
return (a.Value.(uint64) < b.Value.(uint64))
case StringID, DefaultID:
// Use language comparator.
if cl != nil {
return cl.CompareString(a.Safe().(string), b.Safe().(string)) < 0
}
return (a.Safe().(string)) < (b.Safe().(string))
}
return false
Expand Down
33 changes: 26 additions & 7 deletions types/sort_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,35 @@ func getUIDList(n int) *pb.List {
func TestSortStrings(t *testing.T) {
list := getInput(t, StringID, []string{"bb", "aaa", "aa", "bab"})
ul := getUIDList(4)
require.NoError(t, Sort(list, ul, []bool{false}))
require.NoError(t, Sort(list, ul, []bool{false}, ""))
require.EqualValues(t, []uint64{300, 200, 400, 100}, ul.Uids)
require.EqualValues(t, []string{"aa", "aaa", "bab", "bb"},
toString(t, list, StringID))
}

func TestSortLanguage(t *testing.T) {
// Sorting strings of german language.
list := getInput(t, StringID, []string{"öffnen", "zumachen"})
ul := getUIDList(2)

require.NoError(t, Sort(list, ul, []bool{false}, "de"))
require.EqualValues(t, []uint64{100, 200}, ul.Uids)
require.EqualValues(t, []string{"öffnen", "zumachen"},
toString(t, list, StringID))

// Sorting strings of swedish language.
list = getInput(t, StringID, []string{"öppna", "zon"})

require.NoError(t, Sort(list, ul, []bool{false}, "sv"))
require.EqualValues(t, []uint64{200, 100}, ul.Uids)
require.EqualValues(t, []string{"zon", "öppna"},
toString(t, list, StringID))
}

func TestSortInts(t *testing.T) {
list := getInput(t, IntID, []string{"22", "111", "11", "212"})
ul := getUIDList(4)
require.NoError(t, Sort(list, ul, []bool{false}))
require.NoError(t, Sort(list, ul, []bool{false}, ""))
require.EqualValues(t, []uint64{300, 100, 200, 400}, ul.Uids)
require.EqualValues(t, []string{"11", "22", "111", "212"},
toString(t, list, IntID))
Expand All @@ -73,7 +92,7 @@ func TestSortInts(t *testing.T) {
func TestSortFloats(t *testing.T) {
list := getInput(t, FloatID, []string{"22.2", "11.2", "11.5", "2.12"})
ul := getUIDList(4)
require.NoError(t, Sort(list, ul, []bool{false}))
require.NoError(t, Sort(list, ul, []bool{false}, ""))
require.EqualValues(t, []uint64{400, 200, 300, 100}, ul.Uids)
require.EqualValues(t,
[]string{"2.12", "11.2", "11.5", "22.2"},
Expand All @@ -83,7 +102,7 @@ func TestSortFloats(t *testing.T) {
func TestSortFloatsDesc(t *testing.T) {
list := getInput(t, FloatID, []string{"22.2", "11.2", "11.5", "2.12"})
ul := getUIDList(4)
require.NoError(t, Sort(list, ul, []bool{true}))
require.NoError(t, Sort(list, ul, []bool{true}, ""))
require.EqualValues(t, []uint64{100, 300, 200, 400}, ul.Uids)
require.EqualValues(t,
[]string{"22.2", "11.5", "11.2", "2.12"},
Expand All @@ -99,7 +118,7 @@ func TestSortDateTimes(t *testing.T) {
}
list := getInput(t, DateTimeID, in)
ul := getUIDList(4)
require.NoError(t, Sort(list, ul, []bool{false}))
require.NoError(t, Sort(list, ul, []bool{false}, ""))
require.EqualValues(t, []uint64{400, 200, 300, 100}, ul.Uids)
require.EqualValues(t,
[]string{"2006-01-02T15:04:01Z", "2006-01-02T15:04:05Z",
Expand All @@ -114,7 +133,7 @@ func TestSortIntAndFloat(t *testing.T) {
{{Tid: IntID, Value: int64(100)}},
}
ul := getUIDList(3)
require.NoError(t, Sort(list, ul, []bool{false}))
require.NoError(t, Sort(list, ul, []bool{false}, ""))
require.EqualValues(t, []uint64{200, 100, 300}, ul.Uids)
require.EqualValues(t,
[]string{"21.5", "55", "100"},
Expand Down Expand Up @@ -169,7 +188,7 @@ func TestSortMismatchedTypes(t *testing.T) {
{{Tid: FloatID, Value: 33.33}},
}
ul := getUIDList(7)
require.NoError(t, Sort(list, ul, []bool{false}))
require.NoError(t, Sort(list, ul, []bool{false}, ""))

// Don't care about relative ordering between types. However, like types
// should be sorted with each other.
Expand Down
12 changes: 10 additions & 2 deletions worker/sort.go
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ func multiSort(ctx context.Context, r *sortresult, ts *pb.SortMessage) error {
x.AssertTrue(idx >= 0)
vals[j] = sortVals[idx]
}
if err := types.Sort(vals, ul, desc); err != nil {
if err := types.Sort(vals, ul, desc, ""); err != nil {
return err
}
// Paginate
Expand Down Expand Up @@ -686,6 +686,14 @@ func sortByValue(ctx context.Context, ts *pb.SortMessage, ul *pb.List,
values := make([][]types.Val, 0, lenList)
multiSortVals := make([]types.Val, 0, lenList)
order := ts.Order[0]

var lang string
if langCount := len(order.Langs); langCount == 1 {
lang = order.Langs[0]
} else if langCount > 1 {
return nil, errors.Errorf("Sorting on multiple language is not supported.")
}

for i := 0; i < lenList; i++ {
select {
case <-ctx.Done():
Expand All @@ -703,7 +711,7 @@ func sortByValue(ctx context.Context, ts *pb.SortMessage, ul *pb.List,
values = append(values, []types.Val{val})
}
}
err := types.Sort(values, &pb.List{Uids: uids}, []bool{order.Desc})
err := types.Sort(values, &pb.List{Uids: uids}, []bool{order.Desc}, lang)
ul.Uids = uids
if len(ts.Order) > 1 {
for _, v := range values {
Expand Down