From cc098c7823ce2a6dece3ce75105dfd60669d54dd Mon Sep 17 00:00:00 2001 From: hiranya911 Date: Thu, 22 Feb 2018 22:21:13 -0800 Subject: [PATCH] Introducing the QueryNode interface to handle ordered query results --- db/query.go | 90 +++++------ db/query_test.go | 290 +++++++++++++++++++++-------------- integration/db/query_test.go | 210 +++++++++++++------------ 3 files changed, 337 insertions(+), 253 deletions(-) diff --git a/db/query.go b/db/query.go index c4753a35..c6013483 100644 --- a/db/query.go +++ b/db/query.go @@ -18,7 +18,6 @@ import ( "encoding/json" "fmt" "net/http" - "reflect" "sort" "strconv" "strings" @@ -28,6 +27,12 @@ import ( "golang.org/x/net/context" ) +// QueryNode represents a data node retrieved from an ordered query. +type QueryNode interface { + Key() string + Unmarshal(v interface{}) error +} + // Query represents a complex query that can be executed on a Ref. // // Complex queries can consist of up to 2 components: a required ordering constraint, and an @@ -112,40 +117,23 @@ func (q *Query) Get(ctx context.Context, v interface{}) error { return resp.Unmarshal(http.StatusOK, v) } -// GetOrdered executes the Query and provides the results as an ordered list. -// -// v must be a pointer to an array or a slice. Only the child values returned by the query are -// unmarshalled into v. Top-level keys are not returned. Although if the Query was created using -// OrderByKey(), the returned values will still be ordered based on their keys. -func (q *Query) GetOrdered(ctx context.Context, v interface{}) error { - rv := reflect.ValueOf(v) - if rv.Kind() != reflect.Ptr || rv.IsNil() { - return fmt.Errorf("nil or not a pointer") - } - if rv.Elem().Kind() != reflect.Slice && rv.Elem().Kind() != reflect.Array { - return fmt.Errorf("non-array non-slice pointer") - } - +// GetOrdered executes the Query and returns the results as an ordered slice. +func (q *Query) GetOrdered(ctx context.Context) ([]QueryNode, error) { var temp interface{} if err := q.Get(ctx, &temp); err != nil { - return err + return nil, err } - if temp == nil { - return nil + return nil, nil } - sr := newSortableQueryResult(temp, q.order) - sort.Sort(sr) - var values []interface{} - for _, val := range sr { - values = append(values, val.Value) - } - b, err := json.Marshal(values) - if err != nil { - return err + sn := newSortableNodes(temp, q.order) + sort.Sort(sn) + result := make([]QueryNode, len(sn)) + for i, v := range sn { + result[i] = v } - return json.Unmarshal(b, v) + return result, nil } // OrderByChild returns a Query that orders data by child values before applying filters. @@ -307,14 +295,30 @@ func newComparableKey(v interface{}) *comparableKey { return &comparableKey{Num: &f} } -type queryResult struct { - Key *comparableKey +type queryNodeImpl struct { + CompKey *comparableKey Value interface{} Index interface{} IndexType int } -func newQueryResult(key, val interface{}, order orderBy) *queryResult { +func (q *queryNodeImpl) Key() string { + if q.CompKey.Str != nil { + return *q.CompKey.Str + } + // Numeric keys in queryNodeImpl are always array indices, and can be safely coverted into int. + return strconv.Itoa(int(*q.CompKey.Num)) +} + +func (q *queryNodeImpl) Unmarshal(v interface{}) error { + b, err := json.Marshal(q.Value) + if err != nil { + return err + } + return json.Unmarshal(b, v) +} + +func newQueryNode(key, val interface{}, order orderBy) *queryNodeImpl { var index interface{} if prop, ok := order.(orderByProperty); ok { if prop == "$value" { @@ -326,25 +330,25 @@ func newQueryResult(key, val interface{}, order orderBy) *queryResult { path := order.(orderByChild) index = extractChildValue(val, string(path)) } - return &queryResult{ - Key: newComparableKey(key), + return &queryNodeImpl{ + CompKey: newComparableKey(key), Value: val, Index: index, IndexType: getIndexType(index), } } -type sortableQueryResult []*queryResult +type sortableNodes []*queryNodeImpl -func (s sortableQueryResult) Len() int { +func (s sortableNodes) Len() int { return len(s) } -func (s sortableQueryResult) Swap(i, j int) { +func (s sortableNodes) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -func (s sortableQueryResult) Less(i, j int) bool { +func (s sortableNodes) Less(i, j int) bool { a, b := s[i], s[j] var aKey, bKey *comparableKey if a.IndexType == b.IndexType { @@ -353,7 +357,7 @@ func (s sortableQueryResult) Less(i, j int) bool { if (a.IndexType == typeNumeric || a.IndexType == typeString) && a.Index != b.Index { aKey, bKey = newComparableKey(a.Index), newComparableKey(b.Index) } else { - aKey, bKey = a.Key, b.Key + aKey, bKey = a.CompKey, b.CompKey } } else { // If the indices are of different types, use the type ordering of Firebase. @@ -363,18 +367,18 @@ func (s sortableQueryResult) Less(i, j int) bool { return aKey.Compare(bKey) < 0 } -func newSortableQueryResult(values interface{}, order orderBy) sortableQueryResult { - var entries sortableQueryResult +func newSortableNodes(values interface{}, order orderBy) sortableNodes { + var entries sortableNodes if m, ok := values.(map[string]interface{}); ok { for key, val := range m { - entries = append(entries, newQueryResult(key, val, order)) + entries = append(entries, newQueryNode(key, val, order)) } } else if l, ok := values.([]interface{}); ok { for key, val := range l { - entries = append(entries, newQueryResult(key, val, order)) + entries = append(entries, newQueryNode(key, val, order)) } } else { - entries = append(entries, newQueryResult(0, values, order)) + entries = append(entries, newQueryNode(0, values, order)) } return entries } diff --git a/db/query_test.go b/db/query_test.go index 234764e5..4473daff 100644 --- a/db/query_test.go +++ b/db/query_test.go @@ -30,55 +30,67 @@ var sortableKeysResp = map[string]interface{}{ } var sortableValuesResp = []struct { - resp map[string]interface{} - want []interface{} + resp map[string]interface{} + want []interface{} + wantKeys []string }{ { - resp: map[string]interface{}{"k1": 1, "k2": 2, "k3": 3}, - want: []interface{}{1.0, 2.0, 3.0}, + resp: map[string]interface{}{"k1": 1, "k2": 2, "k3": 3}, + want: []interface{}{1.0, 2.0, 3.0}, + wantKeys: []string{"k1", "k2", "k3"}, }, { - resp: map[string]interface{}{"k1": 3, "k2": 2, "k3": 1}, - want: []interface{}{1.0, 2.0, 3.0}, + resp: map[string]interface{}{"k1": 3, "k2": 2, "k3": 1}, + want: []interface{}{1.0, 2.0, 3.0}, + wantKeys: []string{"k3", "k2", "k1"}, }, { - resp: map[string]interface{}{"k1": 3, "k2": 1, "k3": 2}, - want: []interface{}{1.0, 2.0, 3.0}, + resp: map[string]interface{}{"k1": 3, "k2": 1, "k3": 2}, + want: []interface{}{1.0, 2.0, 3.0}, + wantKeys: []string{"k2", "k3", "k1"}, }, { - resp: map[string]interface{}{"k1": 1, "k2": 2, "k3": 1}, - want: []interface{}{1.0, 1.0, 2.0}, + resp: map[string]interface{}{"k1": 1, "k2": 2, "k3": 1}, + want: []interface{}{1.0, 1.0, 2.0}, + wantKeys: []string{"k1", "k3", "k2"}, }, { - resp: map[string]interface{}{"k1": 1, "k2": 1, "k3": 2}, - want: []interface{}{1.0, 1.0, 2.0}, + resp: map[string]interface{}{"k1": 1, "k2": 1, "k3": 2}, + want: []interface{}{1.0, 1.0, 2.0}, + wantKeys: []string{"k1", "k2", "k3"}, }, { - resp: map[string]interface{}{"k1": 2, "k2": 1, "k3": 1}, - want: []interface{}{1.0, 1.0, 2.0}, + resp: map[string]interface{}{"k1": 2, "k2": 1, "k3": 1}, + want: []interface{}{1.0, 1.0, 2.0}, + wantKeys: []string{"k2", "k3", "k1"}, }, { - resp: map[string]interface{}{"k1": "foo", "k2": "bar", "k3": "baz"}, - want: []interface{}{"bar", "baz", "foo"}, + resp: map[string]interface{}{"k1": "foo", "k2": "bar", "k3": "baz"}, + want: []interface{}{"bar", "baz", "foo"}, + wantKeys: []string{"k2", "k3", "k1"}, }, { - resp: map[string]interface{}{"k1": "foo", "k2": "bar", "k3": 10}, - want: []interface{}{10.0, "bar", "foo"}, + resp: map[string]interface{}{"k1": "foo", "k2": "bar", "k3": 10}, + want: []interface{}{10.0, "bar", "foo"}, + wantKeys: []string{"k3", "k2", "k1"}, }, { - resp: map[string]interface{}{"k1": "foo", "k2": "bar", "k3": nil}, - want: []interface{}{nil, "bar", "foo"}, + resp: map[string]interface{}{"k1": "foo", "k2": "bar", "k3": nil}, + want: []interface{}{nil, "bar", "foo"}, + wantKeys: []string{"k3", "k2", "k1"}, }, { - resp: map[string]interface{}{"k1": 5, "k2": "bar", "k3": nil}, - want: []interface{}{nil, 5.0, "bar"}, + resp: map[string]interface{}{"k1": 5, "k2": "bar", "k3": nil}, + want: []interface{}{nil, 5.0, "bar"}, + wantKeys: []string{"k3", "k1", "k2"}, }, { resp: map[string]interface{}{ "k1": true, "k2": 0, "k3": "foo", "k4": "foo", "k5": false, "k6": map[string]interface{}{"k1": true}, }, - want: []interface{}{false, true, 0.0, "foo", "foo", map[string]interface{}{"k1": true}}, + want: []interface{}{false, true, 0.0, "foo", "foo", map[string]interface{}{"k1": true}}, + wantKeys: []string{"k5", "k1", "k2", "k3", "k4", "k6"}, }, { resp: map[string]interface{}{ @@ -90,6 +102,7 @@ var sortableValuesResp = []struct { nil, false, true, 0.0, "foo", "foo", map[string]interface{}{"k1": true}, map[string]interface{}{"k0": true}, }, + wantKeys: []string{"k7", "k5", "k1", "k2", "k3", "k4", "k6", "k8"}, }, } @@ -406,29 +419,6 @@ func TestAllParamsQuery(t *testing.T) { }) } -func TestInvalidGetOrdered(t *testing.T) { - q := testref.OrderByKey() - - want := "nil or not a pointer" - var p *[]person // nil - err := q.GetOrdered(context.Background(), p) - if err == nil || err.Error() != want { - t.Errorf("GetOrdered(interface) = %v; want = %v", err, want) - } - - var i interface{} // not a pointer - err = q.GetOrdered(context.Background(), i) - if err == nil || err.Error() != want { - t.Errorf("GetOrdered(interface) = %v; want = %v", err, want) - } - - want = "non-array non-slice pointer" - err = q.GetOrdered(context.Background(), &i) // pointer to a non-array value - if err == nil || err.Error() != want { - t.Errorf("GetOrdered(interface) = %v; want = %v", err, want) - } -} - func TestChildQueryGetOrdered(t *testing.T) { mock := &mockServer{Resp: sortableKeysResp} srv := mock.Start(client) @@ -444,9 +434,9 @@ func TestChildQueryGetOrdered(t *testing.T) { } var reqs []*testReq - for _, tc := range cases { - var result []person - if err := testref.OrderByChild(tc.child).GetOrdered(context.Background(), &result); err != nil { + for idx, tc := range cases { + result, err := testref.OrderByChild(tc.child).GetOrdered(context.Background()) + if err != nil { t.Fatal(err) } reqs = append(reqs, &testReq{ @@ -455,12 +445,20 @@ func TestChildQueryGetOrdered(t *testing.T) { Query: map[string]string{"orderBy": fmt.Sprintf("%q", tc.child)}, }) - var got []string + var gotKeys, gotVals []string for _, r := range result { - got = append(got, r.Name) + var p person + if err := r.Unmarshal(&p); err != nil { + t.Fatal(err) + } + gotKeys = append(gotKeys, r.Key()) + gotVals = append(gotVals, p.Name) } - if !reflect.DeepEqual(tc.want, got) { - t.Errorf("GetOrdered(child: %q) = %v; want = %v", tc.child, got, tc.want) + if !reflect.DeepEqual(tc.want, gotKeys) { + t.Errorf("[%d] GetOrdered(child: %q) = %v; want = %v", idx, tc.child, gotKeys, tc.want) + } + if !reflect.DeepEqual(tc.want, gotVals) { + t.Errorf("[%d] GetOrdered(child: %q) = %v; want = %v", idx, tc.child, gotVals, tc.want) } } checkAllRequests(t, mock.Reqs, reqs) @@ -476,15 +474,15 @@ func TestImmediateChildQueryGetOrdered(t *testing.T) { } var reqs []*testReq - for _, tc := range sortableValuesResp { + for idx, tc := range sortableValuesResp { resp := map[string]interface{}{} for k, v := range tc.resp { resp[k] = map[string]interface{}{"child": v} } mock.Resp = resp - var result []parsedMap - if err := testref.OrderByChild("child").GetOrdered(context.Background(), &result); err != nil { + result, err := testref.OrderByChild("child").GetOrdered(context.Background()) + if err != nil { t.Fatal(err) } reqs = append(reqs, &testReq{ @@ -493,12 +491,21 @@ func TestImmediateChildQueryGetOrdered(t *testing.T) { Query: map[string]string{"orderBy": "\"child\""}, }) - var got []interface{} + var gotKeys []string + var gotVals []interface{} for _, r := range result { - got = append(got, r.Child) + var p parsedMap + if err := r.Unmarshal(&p); err != nil { + t.Fatal(err) + } + gotKeys = append(gotKeys, r.Key()) + gotVals = append(gotVals, p.Child) + } + if !reflect.DeepEqual(tc.wantKeys, gotKeys) { + t.Errorf("[%d] GetOrdered(child: %q) = %v; want = %v", idx, "child", gotKeys, tc.wantKeys) } - if !reflect.DeepEqual(tc.want, got) { - t.Errorf("GetOrdered(child: %q) = %v; want = %v", "child", got, tc.want) + if !reflect.DeepEqual(tc.want, gotVals) { + t.Errorf("[%d] GetOrdered(child: %q) = %v; want = %v", idx, "child", gotVals, tc.want) } } checkAllRequests(t, mock.Reqs, reqs) @@ -517,16 +524,16 @@ func TestNestedChildQueryGetOrdered(t *testing.T) { } var reqs []*testReq - for _, tc := range sortableValuesResp { + for idx, tc := range sortableValuesResp { resp := map[string]interface{}{} for k, v := range tc.resp { resp[k] = map[string]interface{}{"child": map[string]interface{}{"grandchild": v}} } mock.Resp = resp - var result []parsedMap q := testref.OrderByChild("child/grandchild") - if err := q.GetOrdered(context.Background(), &result); err != nil { + result, err := q.GetOrdered(context.Background()) + if err != nil { t.Fatal(err) } reqs = append(reqs, &testReq{ @@ -535,12 +542,21 @@ func TestNestedChildQueryGetOrdered(t *testing.T) { Query: map[string]string{"orderBy": "\"child/grandchild\""}, }) - var got []interface{} + var gotKeys []string + var gotVals []interface{} for _, r := range result { - got = append(got, r.Child.GrandChild) + var p parsedMap + if err := r.Unmarshal(&p); err != nil { + t.Fatal(err) + } + gotKeys = append(gotKeys, r.Key()) + gotVals = append(gotVals, p.Child.GrandChild) } - if !reflect.DeepEqual(tc.want, got) { - t.Errorf("GetOrdered(child: %q) = %v; want = %v", "child/grandchild", got, tc.want) + if !reflect.DeepEqual(tc.wantKeys, gotKeys) { + t.Errorf("[%d] GetOrdered(child: %q) = %v; want = %v", idx, "child/grandchild", gotKeys, tc.wantKeys) + } + if !reflect.DeepEqual(tc.want, gotVals) { + t.Errorf("[%d] GetOrdered(child: %q) = %v; want = %v", idx, "child/grandchild", gotVals, tc.want) } } checkAllRequests(t, mock.Reqs, reqs) @@ -551,8 +567,8 @@ func TestKeyQueryGetOrdered(t *testing.T) { srv := mock.Start(client) defer srv.Close() - var result []person - if err := testref.OrderByKey().GetOrdered(context.Background(), &result); err != nil { + result, err := testref.OrderByKey().GetOrdered(context.Background()) + if err != nil { t.Fatal(err) } req := &testReq{ @@ -561,14 +577,22 @@ func TestKeyQueryGetOrdered(t *testing.T) { Query: map[string]string{"orderBy": "\"$key\""}, } - var got []string + var gotKeys, gotVals []string for _, r := range result { - got = append(got, r.Name) + var p person + if err := r.Unmarshal(&p); err != nil { + t.Fatal(err) + } + gotKeys = append(gotKeys, r.Key()) + gotVals = append(gotVals, p.Name) } want := []string{"alice", "bob", "charlie", "dave", "ernie"} - if !reflect.DeepEqual(want, got) { - t.Errorf("GetOrdered(key) = %v; want = %v", got, want) + if !reflect.DeepEqual(want, gotKeys) { + t.Errorf("GetOrdered(key) = %v; want = %v", gotKeys, want) + } + if !reflect.DeepEqual(want, gotVals) { + t.Errorf("GetOrdered(key) = %v; want = %v", gotVals, want) } checkOnlyRequest(t, mock.Reqs, req) } @@ -579,11 +603,11 @@ func TestValueQueryGetOrdered(t *testing.T) { defer srv.Close() var reqs []*testReq - for _, tc := range sortableValuesResp { + for idx, tc := range sortableValuesResp { mock.Resp = tc.resp - var got []interface{} - if err := testref.OrderByValue().GetOrdered(context.Background(), &got); err != nil { + result, err := testref.OrderByValue().GetOrdered(context.Background()) + if err != nil { t.Fatal(err) } reqs = append(reqs, &testReq{ @@ -592,8 +616,22 @@ func TestValueQueryGetOrdered(t *testing.T) { Query: map[string]string{"orderBy": "\"$value\""}, }) - if !reflect.DeepEqual(tc.want, got) { - t.Errorf("GetOrdered(value) = %v; want = %v", got, tc.want) + var gotKeys []string + var gotVals []interface{} + for _, r := range result { + var v interface{} + if err := r.Unmarshal(&v); err != nil { + t.Fatal(err) + } + gotKeys = append(gotKeys, r.Key()) + gotVals = append(gotVals, v) + } + + if !reflect.DeepEqual(tc.wantKeys, gotKeys) { + t.Errorf("[%d] GetOrdered(value) = %v; want = %v", idx, gotKeys, tc.wantKeys) + } + if !reflect.DeepEqual(tc.want, gotVals) { + t.Errorf("[%d] GetOrdered(value) = %v; want = %v", idx, gotVals, tc.want) } } checkAllRequests(t, mock.Reqs, reqs) @@ -601,36 +639,44 @@ func TestValueQueryGetOrdered(t *testing.T) { func TestValueQueryGetOrderedWithList(t *testing.T) { cases := []struct { - resp []interface{} - want []interface{} + resp []interface{} + want []interface{} + wantKeys []string }{ { - resp: []interface{}{1, 2, 3}, - want: []interface{}{1.0, 2.0, 3.0}, + resp: []interface{}{1, 2, 3}, + want: []interface{}{1.0, 2.0, 3.0}, + wantKeys: []string{"0", "1", "2"}, }, { - resp: []interface{}{3, 2, 1}, - want: []interface{}{1.0, 2.0, 3.0}, + resp: []interface{}{3, 2, 1}, + want: []interface{}{1.0, 2.0, 3.0}, + wantKeys: []string{"2", "1", "0"}, }, { - resp: []interface{}{1, 3, 2}, - want: []interface{}{1.0, 2.0, 3.0}, + resp: []interface{}{1, 3, 2}, + want: []interface{}{1.0, 2.0, 3.0}, + wantKeys: []string{"0", "2", "1"}, }, { - resp: []interface{}{1, 3, 3}, - want: []interface{}{1.0, 3.0, 3.0}, + resp: []interface{}{1, 3, 3}, + want: []interface{}{1.0, 3.0, 3.0}, + wantKeys: []string{"0", "1", "2"}, }, { - resp: []interface{}{1, 2, 1}, - want: []interface{}{1.0, 1.0, 2.0}, + resp: []interface{}{1, 2, 1}, + want: []interface{}{1.0, 1.0, 2.0}, + wantKeys: []string{"0", "2", "1"}, }, { - resp: []interface{}{"foo", "bar", "baz"}, - want: []interface{}{"bar", "baz", "foo"}, + resp: []interface{}{"foo", "bar", "baz"}, + want: []interface{}{"bar", "baz", "foo"}, + wantKeys: []string{"1", "2", "0"}, }, { - resp: []interface{}{"foo", 1, false, nil, 0, true}, - want: []interface{}{nil, false, true, 0.0, 1.0, "foo"}, + resp: []interface{}{"foo", 1, false, nil, 0, true}, + want: []interface{}{nil, false, true, 0.0, 1.0, "foo"}, + wantKeys: []string{"3", "2", "5", "4", "1", "0"}, }, } @@ -642,8 +688,8 @@ func TestValueQueryGetOrderedWithList(t *testing.T) { for _, tc := range cases { mock.Resp = tc.resp - var got []interface{} - if err := testref.OrderByValue().GetOrdered(context.Background(), &got); err != nil { + result, err := testref.OrderByValue().GetOrdered(context.Background()) + if err != nil { t.Fatal(err) } reqs = append(reqs, &testReq{ @@ -652,8 +698,22 @@ func TestValueQueryGetOrderedWithList(t *testing.T) { Query: map[string]string{"orderBy": "\"$value\""}, }) - if !reflect.DeepEqual(tc.want, got) { - t.Errorf("GetOrdered(value) = %v; want = %v", got, tc.want) + var gotKeys []string + var gotVals []interface{} + for _, r := range result { + var v interface{} + if err := r.Unmarshal(&v); err != nil { + t.Fatal(err) + } + gotKeys = append(gotKeys, r.Key()) + gotVals = append(gotVals, v) + } + + if !reflect.DeepEqual(tc.wantKeys, gotKeys) { + t.Errorf("GetOrdered(value) = %v; want = %v", gotKeys, tc.wantKeys) + } + if !reflect.DeepEqual(tc.want, gotVals) { + t.Errorf("GetOrdered(value) = %v; want = %v", gotVals, tc.want) } } checkAllRequests(t, mock.Reqs, reqs) @@ -664,12 +724,12 @@ func TestGetOrderedWithNilResult(t *testing.T) { srv := mock.Start(client) defer srv.Close() - var got []interface{} - if err := testref.OrderByChild("child").GetOrdered(context.Background(), &got); err != nil { + result, err := testref.OrderByChild("child").GetOrdered(context.Background()) + if err != nil { t.Fatal(err) } - if got != nil { - t.Errorf("GetOrdered(value) = %v; want = nil", got) + if result != nil { + t.Errorf("GetOrdered(value) = %v; want = nil", result) } } @@ -678,14 +738,23 @@ func TestGetOrderedWithLeafNode(t *testing.T) { srv := mock.Start(client) defer srv.Close() - var got []interface{} - if err := testref.OrderByChild("child").GetOrdered(context.Background(), &got); err != nil { + result, err := testref.OrderByChild("child").GetOrdered(context.Background()) + if err != nil { t.Fatal(err) } + if len(result) != 1 { + t.Fatalf("GetOrdered(chid) = %d; want = 1", len(result)) + } + if result[0].Key() != "0" { + t.Errorf("GetOrdered(value).Key() = %v; want = %q", result[0].Key(), 0) + } - want := []interface{}{"foo"} - if !reflect.DeepEqual(want, got) { - t.Errorf("GetOrdered(value) = %v; want = %v", got, want) + var v interface{} + if err := result[0].Unmarshal(&v); err != nil { + t.Fatal(err) + } + if v != "foo" { + t.Errorf("GetOrdered(value) = %v; want = %v", v, "foo") } } @@ -695,12 +764,11 @@ func TestQueryHttpError(t *testing.T) { defer srv.Close() want := "http error status: 500; reason: test error" - var got []string - err := testref.OrderByChild("child").GetOrdered(context.Background(), &got) + result, err := testref.OrderByChild("child").GetOrdered(context.Background()) if err == nil || err.Error() != want { t.Errorf("GetOrdered() = %v; want = %v", err, want) } - if got != nil { - t.Errorf("GetOrdered() = %v; want = nil", got) + if result != nil { + t.Errorf("GetOrdered() = %v; want = nil", result) } } diff --git a/integration/db/query_test.go b/integration/db/query_test.go index 72b2e507..6573d915 100644 --- a/integration/db/query_test.go +++ b/integration/db/query_test.go @@ -17,6 +17,10 @@ package db import ( "testing" + "firebase.google.com/go/db" + + "reflect" + "golang.org/x/net/context" ) @@ -25,190 +29,171 @@ var heightSorted = []string{ "triceratops", "stegosaurus", "bruhathkayosaurus", } -func min(i, j int) int { - if i < j { - return i - } - return j -} - func TestLimitToFirst(t *testing.T) { for _, tc := range []int{2, 10} { - var d []Dinosaur - if err := dinos.OrderByChild("height"). - LimitToFirst(tc). - GetOrdered(context.Background(), &d); err != nil { + results, err := dinos.OrderByChild("height").LimitToFirst(tc).GetOrdered(context.Background()) + if err != nil { t.Fatal(err) } wl := min(tc, len(heightSorted)) want := heightSorted[:wl] - if len(d) != wl { - t.Errorf("LimitToFirst() = %d; want = %d", len(d), wl) + if len(results) != wl { + t.Errorf("LimitToFirst() = %d; want = %d", len(results), wl) } - for i, w := range want { - if d[i] != parsedTestData[w] { - t.Errorf("[%d] LimitToFirst() = %v; want = %v", i, d[i], parsedTestData[w]) - } + got := getNames(results) + if !reflect.DeepEqual(got, want) { + t.Errorf("LimitToLast() = %v; want = %v", got, want) } + compareValues(t, results) } } func TestLimitToLast(t *testing.T) { for _, tc := range []int{2, 10} { - var d []Dinosaur - if err := dinos.OrderByChild("height"). - LimitToLast(tc). - GetOrdered(context.Background(), &d); err != nil { + results, err := dinos.OrderByChild("height").LimitToLast(tc).GetOrdered(context.Background()) + if err != nil { t.Fatal(err) } wl := min(tc, len(heightSorted)) want := heightSorted[len(heightSorted)-wl:] - if len(d) != wl { - t.Errorf("LimitToLast() = %d; want = %d", len(d), wl) + if len(results) != wl { + t.Errorf("LimitToLast() = %d; want = %d", len(results), wl) } - for i, w := range want { - if d[i] != parsedTestData[w] { - t.Errorf("[%d] LimitToLast() = %v; want = %v", i, d[i], parsedTestData[w]) - } + got := getNames(results) + if !reflect.DeepEqual(got, want) { + t.Errorf("LimitToLast() = %v; want = %v", got, want) } + compareValues(t, results) } } func TestStartAt(t *testing.T) { - var d []Dinosaur - if err := dinos.OrderByChild("height"). - StartAt(3.5). - GetOrdered(context.Background(), &d); err != nil { + results, err := dinos.OrderByChild("height").StartAt(3.5).GetOrdered(context.Background()) + if err != nil { t.Fatal(err) } want := heightSorted[len(heightSorted)-2:] - if len(d) != len(want) { - t.Errorf("StartAt() = %d; want = %d", len(d), len(want)) + if len(results) != len(want) { + t.Errorf("StartAt() = %d; want = %d", len(results), len(want)) } - for i, w := range want { - if d[i] != parsedTestData[w] { - t.Errorf("[%d] StartAt() = %v; want = %v", i, d[i], parsedTestData[w]) - } + got := getNames(results) + if !reflect.DeepEqual(got, want) { + t.Errorf("LimitToLast() = %v; want = %v", got, want) } + compareValues(t, results) } func TestEndAt(t *testing.T) { - var d []Dinosaur - if err := dinos.OrderByChild("height"). - EndAt(3.5). - GetOrdered(context.Background(), &d); err != nil { + results, err := dinos.OrderByChild("height").EndAt(3.5).GetOrdered(context.Background()) + if err != nil { t.Fatal(err) } want := heightSorted[:4] - if len(d) != len(want) { - t.Errorf("StartAt() = %d; want = %d", len(d), len(want)) + if len(results) != len(want) { + t.Errorf("StartAt() = %d; want = %d", len(results), len(want)) } - for i, w := range want { - if d[i] != parsedTestData[w] { - t.Errorf("[%d] EndAt() = %v; want = %v", i, d[i], parsedTestData[w]) - } + got := getNames(results) + if !reflect.DeepEqual(got, want) { + t.Errorf("LimitToLast() = %v; want = %v", got, want) } + compareValues(t, results) } func TestStartAndEndAt(t *testing.T) { - var d []Dinosaur - if err := dinos.OrderByChild("height"). - StartAt(2.5). - EndAt(5). - GetOrdered(context.Background(), &d); err != nil { + results, err := dinos.OrderByChild("height").StartAt(2.5).EndAt(5).GetOrdered(context.Background()) + if err != nil { t.Fatal(err) } want := heightSorted[len(heightSorted)-3 : len(heightSorted)-1] - if len(d) != len(want) { - t.Errorf("StartAt(), EndAt() = %d; want = %d", len(d), len(want)) + if len(results) != len(want) { + t.Errorf("StartAt(), EndAt() = %d; want = %d", len(results), len(want)) } - for i, w := range want { - if d[i] != parsedTestData[w] { - t.Errorf("[%d] StartAt(), EndAt() = %v; want = %v", i, d[i], parsedTestData[w]) - } + got := getNames(results) + if !reflect.DeepEqual(got, want) { + t.Errorf("LimitToLast() = %v; want = %v", got, want) } + compareValues(t, results) } func TestEqualTo(t *testing.T) { - var d []Dinosaur - if err := dinos.OrderByChild("height"). - EqualTo(0.6). - GetOrdered(context.Background(), &d); err != nil { + results, err := dinos.OrderByChild("height").EqualTo(0.6).GetOrdered(context.Background()) + if err != nil { t.Fatal(err) } want := heightSorted[:2] - if len(d) != len(want) { - t.Errorf("EqualTo() = %d; want = %d", len(d), len(want)) + if len(results) != len(want) { + t.Errorf("EqualTo() = %d; want = %d", len(results), len(want)) } - for i, w := range want { - if d[i] != parsedTestData[w] { - t.Errorf("[%d] EqualTo() = %v; want = %v", i, d[i], parsedTestData[w]) - } + got := getNames(results) + if !reflect.DeepEqual(got, want) { + t.Errorf("LimitToLast() = %v; want = %v", got, want) } + compareValues(t, results) } func TestOrderByNestedChild(t *testing.T) { - var d []Dinosaur - if err := dinos.OrderByChild("ratings/pos"). - StartAt(4). - GetOrdered(context.Background(), &d); err != nil { + results, err := dinos.OrderByChild("ratings/pos").StartAt(4).GetOrdered(context.Background()) + if err != nil { t.Fatal(err) } want := []string{"pterodactyl", "stegosaurus", "triceratops"} - if len(d) != len(want) { - t.Errorf("OrderByChild(ratings/pos) = %d; want = %d", len(d), len(want)) + if len(results) != len(want) { + t.Errorf("OrderByChild(ratings/pos) = %d; want = %d", len(results), len(want)) } - for i, w := range want { - if d[i] != parsedTestData[w] { - t.Errorf("[%d] OrderByChild(ratings/pos) = %v; want = %v", i, d[i], parsedTestData[w]) - } + got := getNames(results) + if !reflect.DeepEqual(got, want) { + t.Errorf("LimitToLast() = %v; want = %v", got, want) } + compareValues(t, results) } func TestOrderByKey(t *testing.T) { - var d []Dinosaur - if err := dinos.OrderByKey(). - LimitToFirst(2). - GetOrdered(context.Background(), &d); err != nil { + results, err := dinos.OrderByKey().LimitToFirst(2).GetOrdered(context.Background()) + if err != nil { t.Fatal(err) } want := []string{"bruhathkayosaurus", "lambeosaurus"} - if len(d) != len(want) { - t.Errorf("OrderByKey() = %d; want = %d", len(d), len(want)) + if len(results) != len(want) { + t.Errorf("OrderByKey() = %d; want = %d", len(results), len(want)) } - for i, w := range want { - if d[i] != parsedTestData[w] { - t.Errorf("[%d] OrderByKey() = %v; want = %v", i, d[i], parsedTestData[w]) - } + got := getNames(results) + if !reflect.DeepEqual(got, want) { + t.Errorf("LimitToLast() = %v; want = %v", got, want) } + compareValues(t, results) } func TestOrderByValue(t *testing.T) { scores := ref.Child("scores") - var s []int - if err := scores.OrderByValue(). - LimitToLast(2). - GetOrdered(context.Background(), &s); err != nil { + results, err := scores.OrderByValue().LimitToLast(2).GetOrdered(context.Background()) + if err != nil { t.Fatal(err) } want := []string{"linhenykus", "pterodactyl"} - if len(s) != len(want) { - t.Errorf("OrderByValue() = %d; want = %d", len(s), len(want)) + if len(results) != len(want) { + t.Errorf("OrderByValue() = %d; want = %d", len(results), len(want)) } - scoresData := testData["scores"].(map[string]interface{}) - for i, w := range want { - ws := int(scoresData[w].(float64)) - if s[i] != ws { - t.Errorf("[%d] OrderByValue() = %d; want = %d", i, s[i], ws) + got := getNames(results) + if !reflect.DeepEqual(got, want) { + t.Errorf("LimitToLast() = %v; want = %v", got, want) + } + wantScores := []int{80, 93} + for i, r := range results { + var val int + if err := r.Unmarshal(&val); err != nil { + t.Fatalf("queryNode.Unmarshal() = %v", err) + } + if val != wantScores[i] { + t.Errorf("queryNode.Unmarshal() = %d; want = %d", val, wantScores[i]) } } } @@ -252,3 +237,30 @@ func TestUnorderedQuery(t *testing.T) { } } } + +func min(i, j int) int { + if i < j { + return i + } + return j +} + +func getNames(results []db.QueryNode) []string { + s := make([]string, len(results)) + for i, v := range results { + s[i] = v.Key() + } + return s +} + +func compareValues(t *testing.T, results []db.QueryNode) { + for _, r := range results { + var d Dinosaur + if err := r.Unmarshal(&d); err != nil { + t.Fatalf("queryNode.Unmarshal(%q) = %v", r.Key(), err) + } + if !reflect.DeepEqual(d, parsedTestData[r.Key()]) { + t.Errorf("queryNode.Unmarshal(%q) = %v; want = %v", r.Key(), d, parsedTestData[r.Key()]) + } + } +}