You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

718 lines
21 KiB

  1. // Copyright 2017 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package firestore
  15. import (
  16. "math"
  17. "sort"
  18. "testing"
  19. "golang.org/x/net/context"
  20. "cloud.google.com/go/internal/pretty"
  21. pb "google.golang.org/genproto/googleapis/firestore/v1beta1"
  22. tspb "github.com/golang/protobuf/ptypes/timestamp"
  23. "github.com/golang/protobuf/ptypes/wrappers"
  24. )
  25. func TestFilterToProto(t *testing.T) {
  26. for _, test := range []struct {
  27. in filter
  28. want *pb.StructuredQuery_Filter
  29. }{
  30. {
  31. filter{[]string{"a"}, ">", 1},
  32. &pb.StructuredQuery_Filter{FilterType: &pb.StructuredQuery_Filter_FieldFilter{
  33. FieldFilter: &pb.StructuredQuery_FieldFilter{
  34. Field: &pb.StructuredQuery_FieldReference{FieldPath: "a"},
  35. Op: pb.StructuredQuery_FieldFilter_GREATER_THAN,
  36. Value: intval(1),
  37. },
  38. }},
  39. },
  40. {
  41. filter{[]string{"a"}, "==", nil},
  42. &pb.StructuredQuery_Filter{FilterType: &pb.StructuredQuery_Filter_UnaryFilter{
  43. UnaryFilter: &pb.StructuredQuery_UnaryFilter{
  44. OperandType: &pb.StructuredQuery_UnaryFilter_Field{
  45. Field: &pb.StructuredQuery_FieldReference{FieldPath: "a"},
  46. },
  47. Op: pb.StructuredQuery_UnaryFilter_IS_NULL,
  48. },
  49. }},
  50. },
  51. {
  52. filter{[]string{"a"}, "==", math.NaN()},
  53. &pb.StructuredQuery_Filter{FilterType: &pb.StructuredQuery_Filter_UnaryFilter{
  54. UnaryFilter: &pb.StructuredQuery_UnaryFilter{
  55. OperandType: &pb.StructuredQuery_UnaryFilter_Field{
  56. Field: &pb.StructuredQuery_FieldReference{FieldPath: "a"},
  57. },
  58. Op: pb.StructuredQuery_UnaryFilter_IS_NAN,
  59. },
  60. }},
  61. },
  62. } {
  63. got, err := test.in.toProto()
  64. if err != nil {
  65. t.Fatal(err)
  66. }
  67. if !testEqual(got, test.want) {
  68. t.Errorf("%+v:\ngot\n%v\nwant\n%v", test.in, pretty.Value(got), pretty.Value(test.want))
  69. }
  70. }
  71. }
  72. func TestQueryToProto(t *testing.T) {
  73. filtr := func(path []string, op string, val interface{}) *pb.StructuredQuery_Filter {
  74. f, err := filter{path, op, val}.toProto()
  75. if err != nil {
  76. t.Fatal(err)
  77. }
  78. return f
  79. }
  80. c := &Client{projectID: "P", databaseID: "DB"}
  81. coll := c.Collection("C")
  82. q := coll.Query
  83. type S struct {
  84. A int `firestore:"a"`
  85. }
  86. docsnap := &DocumentSnapshot{
  87. Ref: coll.Doc("D"),
  88. proto: &pb.Document{
  89. Fields: map[string]*pb.Value{"a": intval(7), "b": intval(8)},
  90. },
  91. }
  92. for _, test := range []struct {
  93. desc string
  94. in Query
  95. want *pb.StructuredQuery
  96. }{
  97. {
  98. desc: "q.Select()",
  99. in: q.Select(),
  100. want: &pb.StructuredQuery{
  101. Select: &pb.StructuredQuery_Projection{
  102. Fields: []*pb.StructuredQuery_FieldReference{fref1("__name__")},
  103. },
  104. },
  105. },
  106. {
  107. desc: `q.Select("a", "b")`,
  108. in: q.Select("a", "b"),
  109. want: &pb.StructuredQuery{
  110. Select: &pb.StructuredQuery_Projection{
  111. Fields: []*pb.StructuredQuery_FieldReference{fref1("a"), fref1("b")},
  112. },
  113. },
  114. },
  115. {
  116. desc: `q.Select("a", "b").Select("c")`,
  117. in: q.Select("a", "b").Select("c"), // last wins
  118. want: &pb.StructuredQuery{
  119. Select: &pb.StructuredQuery_Projection{
  120. Fields: []*pb.StructuredQuery_FieldReference{fref1("c")},
  121. },
  122. },
  123. },
  124. {
  125. desc: `q.SelectPaths([]string{"*"}, []string{"/"})`,
  126. in: q.SelectPaths([]string{"*"}, []string{"/"}),
  127. want: &pb.StructuredQuery{
  128. Select: &pb.StructuredQuery_Projection{
  129. Fields: []*pb.StructuredQuery_FieldReference{fref1("*"), fref1("/")},
  130. },
  131. },
  132. },
  133. {
  134. desc: `q.Where("a", ">", 5)`,
  135. in: q.Where("a", ">", 5),
  136. want: &pb.StructuredQuery{Where: filtr([]string{"a"}, ">", 5)},
  137. },
  138. {
  139. desc: `q.Where("a", "==", NaN)`,
  140. in: q.Where("a", "==", float32(math.NaN())),
  141. want: &pb.StructuredQuery{Where: filtr([]string{"a"}, "==", math.NaN())},
  142. },
  143. {
  144. desc: `q.Where("a", ">", 5).Where("b", "<", "foo")`,
  145. in: q.Where("a", ">", 5).Where("b", "<", "foo"),
  146. want: &pb.StructuredQuery{
  147. Where: &pb.StructuredQuery_Filter{
  148. FilterType: &pb.StructuredQuery_Filter_CompositeFilter{
  149. &pb.StructuredQuery_CompositeFilter{
  150. Op: pb.StructuredQuery_CompositeFilter_AND,
  151. Filters: []*pb.StructuredQuery_Filter{
  152. filtr([]string{"a"}, ">", 5), filtr([]string{"b"}, "<", "foo"),
  153. },
  154. },
  155. },
  156. },
  157. },
  158. },
  159. {
  160. desc: ` q.WherePath([]string{"/", "*"}, ">", 5)`,
  161. in: q.WherePath([]string{"/", "*"}, ">", 5),
  162. want: &pb.StructuredQuery{Where: filtr([]string{"/", "*"}, ">", 5)},
  163. },
  164. {
  165. desc: `q.OrderBy("b", Asc).OrderBy("a", Desc).OrderByPath([]string{"~"}, Asc)`,
  166. in: q.OrderBy("b", Asc).OrderBy("a", Desc).OrderByPath([]string{"~"}, Asc),
  167. want: &pb.StructuredQuery{
  168. OrderBy: []*pb.StructuredQuery_Order{
  169. {Field: fref1("b"), Direction: pb.StructuredQuery_ASCENDING},
  170. {Field: fref1("a"), Direction: pb.StructuredQuery_DESCENDING},
  171. {Field: fref1("~"), Direction: pb.StructuredQuery_ASCENDING},
  172. },
  173. },
  174. },
  175. {
  176. desc: `q.Offset(2).Limit(3)`,
  177. in: q.Offset(2).Limit(3),
  178. want: &pb.StructuredQuery{
  179. Offset: 2,
  180. Limit: &wrappers.Int32Value{Value: 3},
  181. },
  182. },
  183. {
  184. desc: `q.Offset(2).Limit(3).Limit(4).Offset(5)`,
  185. in: q.Offset(2).Limit(3).Limit(4).Offset(5), // last wins
  186. want: &pb.StructuredQuery{
  187. Offset: 5,
  188. Limit: &wrappers.Int32Value{Value: 4},
  189. },
  190. },
  191. {
  192. desc: `q.OrderBy("a", Asc).StartAt(7).EndBefore(9)`,
  193. in: q.OrderBy("a", Asc).StartAt(7).EndBefore(9),
  194. want: &pb.StructuredQuery{
  195. OrderBy: []*pb.StructuredQuery_Order{
  196. {Field: fref1("a"), Direction: pb.StructuredQuery_ASCENDING},
  197. },
  198. StartAt: &pb.Cursor{
  199. Values: []*pb.Value{intval(7)},
  200. Before: true,
  201. },
  202. EndAt: &pb.Cursor{
  203. Values: []*pb.Value{intval(9)},
  204. Before: true,
  205. },
  206. },
  207. },
  208. {
  209. desc: `q.OrderBy("a", Asc).StartAt(7).EndAt(9)`,
  210. in: q.OrderBy("a", Asc).StartAt(7).EndAt(9),
  211. want: &pb.StructuredQuery{
  212. OrderBy: []*pb.StructuredQuery_Order{
  213. {Field: fref1("a"), Direction: pb.StructuredQuery_ASCENDING},
  214. },
  215. StartAt: &pb.Cursor{
  216. Values: []*pb.Value{intval(7)},
  217. Before: true,
  218. },
  219. EndAt: &pb.Cursor{
  220. Values: []*pb.Value{intval(9)},
  221. Before: false,
  222. },
  223. },
  224. },
  225. {
  226. desc: `q.OrderBy("a", Asc).StartAfter(7).EndAt(9)`,
  227. in: q.OrderBy("a", Asc).StartAfter(7).EndAt(9),
  228. want: &pb.StructuredQuery{
  229. OrderBy: []*pb.StructuredQuery_Order{
  230. {Field: fref1("a"), Direction: pb.StructuredQuery_ASCENDING},
  231. },
  232. StartAt: &pb.Cursor{
  233. Values: []*pb.Value{intval(7)},
  234. Before: false,
  235. },
  236. EndAt: &pb.Cursor{
  237. Values: []*pb.Value{intval(9)},
  238. Before: false,
  239. },
  240. },
  241. },
  242. {
  243. desc: `q.OrderBy(DocumentID, Asc).StartAfter("foo").EndBefore("bar")`,
  244. in: q.OrderBy(DocumentID, Asc).StartAfter("foo").EndBefore("bar"),
  245. want: &pb.StructuredQuery{
  246. OrderBy: []*pb.StructuredQuery_Order{
  247. {Field: fref1("__name__"), Direction: pb.StructuredQuery_ASCENDING},
  248. },
  249. StartAt: &pb.Cursor{
  250. Values: []*pb.Value{refval(coll.parentPath + "/documents/C/foo")},
  251. Before: false,
  252. },
  253. EndAt: &pb.Cursor{
  254. Values: []*pb.Value{refval(coll.parentPath + "/documents/C/bar")},
  255. Before: true,
  256. },
  257. },
  258. },
  259. {
  260. desc: `q.OrderBy("a", Asc).OrderBy("b", Desc).StartAfter(7, 8).EndAt(9, 10)`,
  261. in: q.OrderBy("a", Asc).OrderBy("b", Desc).StartAfter(7, 8).EndAt(9, 10),
  262. want: &pb.StructuredQuery{
  263. OrderBy: []*pb.StructuredQuery_Order{
  264. {Field: fref1("a"), Direction: pb.StructuredQuery_ASCENDING},
  265. {Field: fref1("b"), Direction: pb.StructuredQuery_DESCENDING},
  266. },
  267. StartAt: &pb.Cursor{
  268. Values: []*pb.Value{intval(7), intval(8)},
  269. Before: false,
  270. },
  271. EndAt: &pb.Cursor{
  272. Values: []*pb.Value{intval(9), intval(10)},
  273. Before: false,
  274. },
  275. },
  276. },
  277. {
  278. // last of StartAt/After wins, same for End
  279. desc: `q.OrderBy("a", Asc).StartAfter(1).StartAt(2).EndAt(3).EndBefore(4)`,
  280. in: q.OrderBy("a", Asc).
  281. StartAfter(1).StartAt(2).
  282. EndAt(3).EndBefore(4),
  283. want: &pb.StructuredQuery{
  284. OrderBy: []*pb.StructuredQuery_Order{
  285. {Field: fref1("a"), Direction: pb.StructuredQuery_ASCENDING},
  286. },
  287. StartAt: &pb.Cursor{
  288. Values: []*pb.Value{intval(2)},
  289. Before: true,
  290. },
  291. EndAt: &pb.Cursor{
  292. Values: []*pb.Value{intval(4)},
  293. Before: true,
  294. },
  295. },
  296. },
  297. // Start/End with DocumentSnapshot
  298. // These tests are from the "Document Snapshot Cursors" doc.
  299. {
  300. desc: `q.StartAt(docsnap)`,
  301. in: q.StartAt(docsnap),
  302. want: &pb.StructuredQuery{
  303. OrderBy: []*pb.StructuredQuery_Order{
  304. {Field: fref1("__name__"), Direction: pb.StructuredQuery_ASCENDING},
  305. },
  306. StartAt: &pb.Cursor{
  307. Values: []*pb.Value{refval(coll.parentPath + "/documents/C/D")},
  308. Before: true,
  309. },
  310. },
  311. },
  312. {
  313. desc: `q.OrderBy("a", Asc).StartAt(docsnap)`,
  314. in: q.OrderBy("a", Asc).StartAt(docsnap),
  315. want: &pb.StructuredQuery{
  316. OrderBy: []*pb.StructuredQuery_Order{
  317. {Field: fref1("a"), Direction: pb.StructuredQuery_ASCENDING},
  318. {Field: fref1("__name__"), Direction: pb.StructuredQuery_ASCENDING},
  319. },
  320. StartAt: &pb.Cursor{
  321. Values: []*pb.Value{intval(7), refval(coll.parentPath + "/documents/C/D")},
  322. Before: true,
  323. },
  324. },
  325. },
  326. {
  327. desc: `q.OrderBy("a", Desc).StartAt(docsnap)`,
  328. in: q.OrderBy("a", Desc).StartAt(docsnap),
  329. want: &pb.StructuredQuery{
  330. OrderBy: []*pb.StructuredQuery_Order{
  331. {Field: fref1("a"), Direction: pb.StructuredQuery_DESCENDING},
  332. {Field: fref1("__name__"), Direction: pb.StructuredQuery_DESCENDING},
  333. },
  334. StartAt: &pb.Cursor{
  335. Values: []*pb.Value{intval(7), refval(coll.parentPath + "/documents/C/D")},
  336. Before: true,
  337. },
  338. },
  339. },
  340. {
  341. desc: `q.OrderBy("a", Desc).OrderBy("b", Asc).StartAt(docsnap)`,
  342. in: q.OrderBy("a", Desc).OrderBy("b", Asc).StartAt(docsnap),
  343. want: &pb.StructuredQuery{
  344. OrderBy: []*pb.StructuredQuery_Order{
  345. {Field: fref1("a"), Direction: pb.StructuredQuery_DESCENDING},
  346. {Field: fref1("b"), Direction: pb.StructuredQuery_ASCENDING},
  347. {Field: fref1("__name__"), Direction: pb.StructuredQuery_ASCENDING},
  348. },
  349. StartAt: &pb.Cursor{
  350. Values: []*pb.Value{intval(7), intval(8), refval(coll.parentPath + "/documents/C/D")},
  351. Before: true,
  352. },
  353. },
  354. },
  355. {
  356. desc: `q.Where("a", "==", 3).StartAt(docsnap)`,
  357. in: q.Where("a", "==", 3).StartAt(docsnap),
  358. want: &pb.StructuredQuery{
  359. Where: filtr([]string{"a"}, "==", 3),
  360. OrderBy: []*pb.StructuredQuery_Order{
  361. {Field: fref1("__name__"), Direction: pb.StructuredQuery_ASCENDING},
  362. },
  363. StartAt: &pb.Cursor{
  364. Values: []*pb.Value{refval(coll.parentPath + "/documents/C/D")},
  365. Before: true,
  366. },
  367. },
  368. },
  369. {
  370. desc: `q.Where("a", "<", 3).StartAt(docsnap)`,
  371. in: q.Where("a", "<", 3).StartAt(docsnap),
  372. want: &pb.StructuredQuery{
  373. Where: filtr([]string{"a"}, "<", 3),
  374. OrderBy: []*pb.StructuredQuery_Order{
  375. {Field: fref1("a"), Direction: pb.StructuredQuery_ASCENDING},
  376. {Field: fref1("__name__"), Direction: pb.StructuredQuery_ASCENDING},
  377. },
  378. StartAt: &pb.Cursor{
  379. Values: []*pb.Value{intval(7), refval(coll.parentPath + "/documents/C/D")},
  380. Before: true,
  381. },
  382. },
  383. },
  384. {
  385. desc: `q.Where("b", "==", 1).Where("a", "<", 3).StartAt(docsnap)`,
  386. in: q.Where("b", "==", 1).Where("a", "<", 3).StartAt(docsnap),
  387. want: &pb.StructuredQuery{
  388. Where: &pb.StructuredQuery_Filter{
  389. FilterType: &pb.StructuredQuery_Filter_CompositeFilter{
  390. &pb.StructuredQuery_CompositeFilter{
  391. Op: pb.StructuredQuery_CompositeFilter_AND,
  392. Filters: []*pb.StructuredQuery_Filter{
  393. filtr([]string{"b"}, "==", 1),
  394. filtr([]string{"a"}, "<", 3),
  395. },
  396. },
  397. },
  398. },
  399. OrderBy: []*pb.StructuredQuery_Order{
  400. {Field: fref1("a"), Direction: pb.StructuredQuery_ASCENDING},
  401. {Field: fref1("__name__"), Direction: pb.StructuredQuery_ASCENDING},
  402. },
  403. StartAt: &pb.Cursor{
  404. Values: []*pb.Value{intval(7), refval(coll.parentPath + "/documents/C/D")},
  405. Before: true,
  406. },
  407. },
  408. },
  409. } {
  410. got, err := test.in.toProto()
  411. if err != nil {
  412. t.Errorf("%s: %v", test.desc, err)
  413. continue
  414. }
  415. test.want.From = []*pb.StructuredQuery_CollectionSelector{{CollectionId: "C"}}
  416. if !testEqual(got, test.want) {
  417. t.Errorf("%s:\ngot\n%v\nwant\n%v", test.desc, pretty.Value(got), pretty.Value(test.want))
  418. }
  419. }
  420. }
  421. func fref1(s string) *pb.StructuredQuery_FieldReference {
  422. return fref([]string{s})
  423. }
  424. func TestQueryToProtoErrors(t *testing.T) {
  425. st := map[string]interface{}{"a": ServerTimestamp}
  426. del := map[string]interface{}{"a": Delete}
  427. c := &Client{projectID: "P", databaseID: "DB"}
  428. coll := c.Collection("C")
  429. docsnap := &DocumentSnapshot{
  430. Ref: coll.Doc("D"),
  431. proto: &pb.Document{
  432. Fields: map[string]*pb.Value{"a": intval(7)},
  433. },
  434. }
  435. q := coll.Query
  436. for _, query := range []Query{
  437. {}, // no collection ID
  438. q.Where("x", "!=", 1), // invalid operator
  439. q.Where("~", ">", 1), // invalid path
  440. q.WherePath([]string{"*", ""}, ">", 1), // invalid path
  441. q.StartAt(1), // no OrderBy
  442. q.StartAt(2).OrderBy("x", Asc).OrderBy("y", Desc), // wrong # OrderBy
  443. q.Select("*"), // invalid path
  444. q.SelectPaths([]string{"/", "", "~"}), // invalid path
  445. q.OrderBy("[", Asc), // invalid path
  446. q.OrderByPath([]string{""}, Desc), // invalid path
  447. q.Where("x", "==", st), // ServerTimestamp in filter
  448. q.OrderBy("a", Asc).StartAt(st), // ServerTimestamp in Start
  449. q.OrderBy("a", Asc).EndAt(st), // ServerTimestamp in End
  450. q.Where("x", "==", del), // Delete in filter
  451. q.OrderBy("a", Asc).StartAt(del), // Delete in Start
  452. q.OrderBy("a", Asc).EndAt(del), // Delete in End
  453. q.OrderBy(DocumentID, Asc).StartAt(7), // wrong type for __name__
  454. q.OrderBy(DocumentID, Asc).EndAt(7), // wrong type for __name__
  455. q.OrderBy("b", Asc).StartAt(docsnap), // doc snapshot does not have order-by field
  456. q.StartAt(docsnap).EndAt("x"), // mixed doc snapshot and fields
  457. q.StartAfter("x").EndBefore(docsnap), // mixed doc snapshot and fields
  458. } {
  459. _, err := query.toProto()
  460. if err == nil {
  461. t.Errorf("%+v: got nil, want error", query)
  462. }
  463. }
  464. }
  465. func TestQueryMethodsDoNotModifyReceiver(t *testing.T) {
  466. var empty Query
  467. q := Query{}
  468. _ = q.Select("a", "b")
  469. if !testEqual(q, empty) {
  470. t.Errorf("got %+v, want empty", q)
  471. }
  472. q = Query{}
  473. q1 := q.Where("a", ">", 3)
  474. if !testEqual(q, empty) {
  475. t.Errorf("got %+v, want empty", q)
  476. }
  477. // Extra check because Where appends to a slice.
  478. q1before := q.Where("a", ">", 3) // same as q1
  479. _ = q1.Where("b", "<", "foo")
  480. if !testEqual(q1, q1before) {
  481. t.Errorf("got %+v, want %+v", q1, q1before)
  482. }
  483. q = Query{}
  484. q1 = q.OrderBy("a", Asc)
  485. if !testEqual(q, empty) {
  486. t.Errorf("got %+v, want empty", q)
  487. }
  488. // Extra check because Where appends to a slice.
  489. q1before = q.OrderBy("a", Asc) // same as q1
  490. _ = q1.OrderBy("b", Desc)
  491. if !testEqual(q1, q1before) {
  492. t.Errorf("got %+v, want %+v", q1, q1before)
  493. }
  494. q = Query{}
  495. _ = q.Offset(5)
  496. if !testEqual(q, empty) {
  497. t.Errorf("got %+v, want empty", q)
  498. }
  499. q = Query{}
  500. _ = q.Limit(5)
  501. if !testEqual(q, empty) {
  502. t.Errorf("got %+v, want empty", q)
  503. }
  504. q = Query{}
  505. _ = q.StartAt(5)
  506. if !testEqual(q, empty) {
  507. t.Errorf("got %+v, want empty", q)
  508. }
  509. q = Query{}
  510. _ = q.StartAfter(5)
  511. if !testEqual(q, empty) {
  512. t.Errorf("got %+v, want empty", q)
  513. }
  514. q = Query{}
  515. _ = q.EndAt(5)
  516. if !testEqual(q, empty) {
  517. t.Errorf("got %+v, want empty", q)
  518. }
  519. q = Query{}
  520. _ = q.EndBefore(5)
  521. if !testEqual(q, empty) {
  522. t.Errorf("got %+v, want empty", q)
  523. }
  524. }
  525. func TestQueryFromCollectionRef(t *testing.T) {
  526. c := &Client{}
  527. coll := c.Collection("C")
  528. got := coll.Select("x").Offset(8)
  529. want := Query{
  530. c: c,
  531. parentPath: c.path(),
  532. collectionID: "C",
  533. selection: []FieldPath{{"x"}},
  534. offset: 8,
  535. }
  536. if !testEqual(got, want) {
  537. t.Fatalf("got %+v, want %+v", got, want)
  538. }
  539. }
  540. func TestQueryGetAll(t *testing.T) {
  541. // This implicitly tests DocumentIterator as well.
  542. const dbPath = "projects/projectID/databases/(default)"
  543. ctx := context.Background()
  544. c, srv := newMock(t)
  545. docNames := []string{"C/a", "C/b"}
  546. wantPBDocs := []*pb.Document{
  547. {
  548. Name: dbPath + "/documents/" + docNames[0],
  549. CreateTime: aTimestamp,
  550. UpdateTime: aTimestamp,
  551. Fields: map[string]*pb.Value{"f": intval(2)},
  552. },
  553. {
  554. Name: dbPath + "/documents/" + docNames[1],
  555. CreateTime: aTimestamp2,
  556. UpdateTime: aTimestamp3,
  557. Fields: map[string]*pb.Value{"f": intval(1)},
  558. },
  559. }
  560. wantReadTimes := []*tspb.Timestamp{aTimestamp, aTimestamp2}
  561. srv.addRPC(nil, []interface{}{
  562. &pb.RunQueryResponse{Document: wantPBDocs[0], ReadTime: aTimestamp},
  563. &pb.RunQueryResponse{Document: wantPBDocs[1], ReadTime: aTimestamp2},
  564. })
  565. gotDocs, err := c.Collection("C").Documents(ctx).GetAll()
  566. if err != nil {
  567. t.Fatal(err)
  568. }
  569. if got, want := len(gotDocs), len(wantPBDocs); got != want {
  570. t.Errorf("got %d docs, wanted %d", got, want)
  571. }
  572. for i, got := range gotDocs {
  573. want, err := newDocumentSnapshot(c.Doc(docNames[i]), wantPBDocs[i], c, wantReadTimes[i])
  574. if err != nil {
  575. t.Fatal(err)
  576. }
  577. if !testEqual(got, want) {
  578. // avoid writing a cycle
  579. got.c = nil
  580. want.c = nil
  581. t.Errorf("#%d: got %+v, want %+v", i, pretty.Value(got), pretty.Value(want))
  582. }
  583. }
  584. }
  585. func TestQueryCompareFunc(t *testing.T) {
  586. mv := func(fields ...interface{}) map[string]*pb.Value {
  587. m := map[string]*pb.Value{}
  588. for i := 0; i < len(fields); i += 2 {
  589. m[fields[i].(string)] = fields[i+1].(*pb.Value)
  590. }
  591. return m
  592. }
  593. snap := func(ref *DocumentRef, fields map[string]*pb.Value) *DocumentSnapshot {
  594. return &DocumentSnapshot{Ref: ref, proto: &pb.Document{Fields: fields}}
  595. }
  596. c := &Client{}
  597. coll := c.Collection("C")
  598. doc1 := coll.Doc("doc1")
  599. doc2 := coll.Doc("doc2")
  600. doc3 := coll.Doc("doc3")
  601. doc4 := coll.Doc("doc4")
  602. for _, test := range []struct {
  603. q Query
  604. in []*DocumentSnapshot
  605. want []*DocumentSnapshot
  606. }{
  607. {
  608. q: coll.OrderBy("foo", Asc),
  609. in: []*DocumentSnapshot{
  610. snap(doc3, mv("foo", intval(2))),
  611. snap(doc4, mv("foo", intval(1))),
  612. snap(doc2, mv("foo", intval(2))),
  613. },
  614. want: []*DocumentSnapshot{
  615. snap(doc4, mv("foo", intval(1))),
  616. snap(doc2, mv("foo", intval(2))),
  617. snap(doc3, mv("foo", intval(2))),
  618. },
  619. },
  620. {
  621. q: coll.OrderBy("foo", Desc),
  622. in: []*DocumentSnapshot{
  623. snap(doc3, mv("foo", intval(2))),
  624. snap(doc4, mv("foo", intval(1))),
  625. snap(doc2, mv("foo", intval(2))),
  626. },
  627. want: []*DocumentSnapshot{
  628. snap(doc3, mv("foo", intval(2))),
  629. snap(doc2, mv("foo", intval(2))),
  630. snap(doc4, mv("foo", intval(1))),
  631. },
  632. },
  633. {
  634. q: coll.OrderBy("foo.bar", Asc),
  635. in: []*DocumentSnapshot{
  636. snap(doc1, mv("foo", mapval(mv("bar", intval(1))))),
  637. snap(doc2, mv("foo", mapval(mv("bar", intval(2))))),
  638. snap(doc3, mv("foo", mapval(mv("bar", intval(2))))),
  639. },
  640. want: []*DocumentSnapshot{
  641. snap(doc1, mv("foo", mapval(mv("bar", intval(1))))),
  642. snap(doc2, mv("foo", mapval(mv("bar", intval(2))))),
  643. snap(doc3, mv("foo", mapval(mv("bar", intval(2))))),
  644. },
  645. },
  646. {
  647. q: coll.OrderBy("foo.bar", Desc),
  648. in: []*DocumentSnapshot{
  649. snap(doc1, mv("foo", mapval(mv("bar", intval(1))))),
  650. snap(doc2, mv("foo", mapval(mv("bar", intval(2))))),
  651. snap(doc3, mv("foo", mapval(mv("bar", intval(2))))),
  652. },
  653. want: []*DocumentSnapshot{
  654. snap(doc3, mv("foo", mapval(mv("bar", intval(2))))),
  655. snap(doc2, mv("foo", mapval(mv("bar", intval(2))))),
  656. snap(doc1, mv("foo", mapval(mv("bar", intval(1))))),
  657. },
  658. },
  659. } {
  660. got := append([]*DocumentSnapshot(nil), test.in...)
  661. sort.Sort(byQuery{test.q.compareFunc(), got})
  662. if diff := testDiff(got, test.want); diff != "" {
  663. t.Errorf("%+v: %s", test.q, diff)
  664. }
  665. }
  666. // Want error on missing field.
  667. q := coll.OrderBy("bar", Asc)
  668. if q.err != nil {
  669. t.Fatalf("bad query: %v", q.err)
  670. }
  671. cf := q.compareFunc()
  672. s := snap(doc1, mv("foo", intval(1)))
  673. if _, err := cf(s, s); err == nil {
  674. t.Error("got nil, want error")
  675. }
  676. }
  677. type byQuery struct {
  678. compare func(d1, d2 *DocumentSnapshot) (int, error)
  679. docs []*DocumentSnapshot
  680. }
  681. func (b byQuery) Len() int { return len(b.docs) }
  682. func (b byQuery) Swap(i, j int) { b.docs[i], b.docs[j] = b.docs[j], b.docs[i] }
  683. func (b byQuery) Less(i, j int) bool {
  684. c, err := b.compare(b.docs[i], b.docs[j])
  685. if err != nil {
  686. panic(err)
  687. }
  688. return c < 0
  689. }