search_test.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583
  1. // Copyright 2012-2015 Oliver Eilhard. All rights reserved.
  2. // Use of this source code is governed by a MIT-license.
  3. // See http://olivere.mit-license.org/license.txt for details.
  4. package elastic
  5. import (
  6. "encoding/json"
  7. _ "net/http"
  8. "reflect"
  9. "testing"
  10. "time"
  11. )
  12. func TestSearchMatchAll(t *testing.T) {
  13. client := setupTestClientAndCreateIndexAndAddDocs(t)
  14. // Match all should return all documents
  15. all := NewMatchAllQuery()
  16. searchResult, err := client.Search().Index(testIndexName).Query(&all).Do()
  17. if err != nil {
  18. t.Fatal(err)
  19. }
  20. if searchResult.Hits == nil {
  21. t.Errorf("expected SearchResult.Hits != nil; got nil")
  22. }
  23. if searchResult.Hits.TotalHits != 3 {
  24. t.Errorf("expected SearchResult.Hits.TotalHits = %d; got %d", 3, searchResult.Hits.TotalHits)
  25. }
  26. if len(searchResult.Hits.Hits) != 3 {
  27. t.Errorf("expected len(SearchResult.Hits.Hits) = %d; got %d", 3, len(searchResult.Hits.Hits))
  28. }
  29. for _, hit := range searchResult.Hits.Hits {
  30. if hit.Index != testIndexName {
  31. t.Errorf("expected SearchResult.Hits.Hit.Index = %q; got %q", testIndexName, hit.Index)
  32. }
  33. item := make(map[string]interface{})
  34. err := json.Unmarshal(*hit.Source, &item)
  35. if err != nil {
  36. t.Fatal(err)
  37. }
  38. }
  39. }
  40. func BenchmarkSearchMatchAll(b *testing.B) {
  41. client := setupTestClientAndCreateIndexAndAddDocs(b)
  42. for n := 0; n < b.N; n++ {
  43. // Match all should return all documents
  44. all := NewMatchAllQuery()
  45. searchResult, err := client.Search().Index(testIndexName).Query(&all).Do()
  46. if err != nil {
  47. b.Fatal(err)
  48. }
  49. if searchResult.Hits == nil {
  50. b.Errorf("expected SearchResult.Hits != nil; got nil")
  51. }
  52. if searchResult.Hits.TotalHits != 3 {
  53. b.Errorf("expected SearchResult.Hits.TotalHits = %d; got %d", 3, searchResult.Hits.TotalHits)
  54. }
  55. if len(searchResult.Hits.Hits) != 3 {
  56. b.Errorf("expected len(SearchResult.Hits.Hits) = %d; got %d", 3, len(searchResult.Hits.Hits))
  57. }
  58. }
  59. }
  60. func TestSearchResultTotalHits(t *testing.T) {
  61. client := setupTestClientAndCreateIndexAndAddDocs(t)
  62. count, err := client.Count(testIndexName).Do()
  63. if err != nil {
  64. t.Fatal(err)
  65. }
  66. all := NewMatchAllQuery()
  67. searchResult, err := client.Search().Index(testIndexName).Query(&all).Do()
  68. if err != nil {
  69. t.Fatal(err)
  70. }
  71. got := searchResult.TotalHits()
  72. if got != count {
  73. t.Fatalf("expected %d hits; got: %d", count, got)
  74. }
  75. // No hits
  76. searchResult = &SearchResult{}
  77. got = searchResult.TotalHits()
  78. if got != 0 {
  79. t.Errorf("expected %d hits; got: %d", 0, got)
  80. }
  81. }
  82. func TestSearchResultEach(t *testing.T) {
  83. client := setupTestClientAndCreateIndexAndAddDocs(t)
  84. all := NewMatchAllQuery()
  85. searchResult, err := client.Search().Index(testIndexName).Query(&all).Do()
  86. if err != nil {
  87. t.Fatal(err)
  88. }
  89. // Iterate over non-ptr type
  90. var aTweet tweet
  91. count := 0
  92. for _, item := range searchResult.Each(reflect.TypeOf(aTweet)) {
  93. count++
  94. _, ok := item.(tweet)
  95. if !ok {
  96. t.Fatalf("expected hit to be serialized as tweet; got: %v", reflect.ValueOf(item))
  97. }
  98. }
  99. if count == 0 {
  100. t.Errorf("expected to find some hits; got: %d", count)
  101. }
  102. // Iterate over ptr-type
  103. count = 0
  104. var aTweetPtr *tweet
  105. for _, item := range searchResult.Each(reflect.TypeOf(aTweetPtr)) {
  106. count++
  107. tw, ok := item.(*tweet)
  108. if !ok {
  109. t.Fatalf("expected hit to be serialized as tweet; got: %v", reflect.ValueOf(item))
  110. }
  111. if tw == nil {
  112. t.Fatal("expected hit to not be nil")
  113. }
  114. }
  115. if count == 0 {
  116. t.Errorf("expected to find some hits; got: %d", count)
  117. }
  118. // Does not iterate when no hits are found
  119. searchResult = &SearchResult{Hits: nil}
  120. count = 0
  121. for _, item := range searchResult.Each(reflect.TypeOf(aTweet)) {
  122. count++
  123. _ = item
  124. }
  125. if count != 0 {
  126. t.Errorf("expected to not find any hits; got: %d", count)
  127. }
  128. searchResult = &SearchResult{Hits: &SearchHits{Hits: make([]*SearchHit, 0)}}
  129. count = 0
  130. for _, item := range searchResult.Each(reflect.TypeOf(aTweet)) {
  131. count++
  132. _ = item
  133. }
  134. if count != 0 {
  135. t.Errorf("expected to not find any hits; got: %d", count)
  136. }
  137. }
  138. func TestSearchSorting(t *testing.T) {
  139. client := setupTestClientAndCreateIndex(t)
  140. tweet1 := tweet{
  141. User: "olivere", Retweets: 108,
  142. Message: "Welcome to Golang and Elasticsearch.",
  143. Created: time.Date(2012, 12, 12, 17, 38, 34, 0, time.UTC),
  144. }
  145. tweet2 := tweet{
  146. User: "olivere", Retweets: 0,
  147. Message: "Another unrelated topic.",
  148. Created: time.Date(2012, 10, 10, 8, 12, 03, 0, time.UTC),
  149. }
  150. tweet3 := tweet{
  151. User: "sandrae", Retweets: 12,
  152. Message: "Cycling is fun.",
  153. Created: time.Date(2011, 11, 11, 10, 58, 12, 0, time.UTC),
  154. }
  155. // Add all documents
  156. _, err := client.Index().Index(testIndexName).Type("tweet").Id("1").BodyJson(&tweet1).Do()
  157. if err != nil {
  158. t.Fatal(err)
  159. }
  160. _, err = client.Index().Index(testIndexName).Type("tweet").Id("2").BodyJson(&tweet2).Do()
  161. if err != nil {
  162. t.Fatal(err)
  163. }
  164. _, err = client.Index().Index(testIndexName).Type("tweet").Id("3").BodyJson(&tweet3).Do()
  165. if err != nil {
  166. t.Fatal(err)
  167. }
  168. _, err = client.Flush().Index(testIndexName).Do()
  169. if err != nil {
  170. t.Fatal(err)
  171. }
  172. // Match all should return all documents
  173. all := NewMatchAllQuery()
  174. searchResult, err := client.Search().
  175. Index(testIndexName).
  176. Query(&all).
  177. Sort("created", false).
  178. Timeout("1s").
  179. Do()
  180. if err != nil {
  181. t.Fatal(err)
  182. }
  183. if searchResult.Hits == nil {
  184. t.Errorf("expected SearchResult.Hits != nil; got nil")
  185. }
  186. if searchResult.Hits.TotalHits != 3 {
  187. t.Errorf("expected SearchResult.Hits.TotalHits = %d; got %d", 3, searchResult.Hits.TotalHits)
  188. }
  189. if len(searchResult.Hits.Hits) != 3 {
  190. t.Errorf("expected len(SearchResult.Hits.Hits) = %d; got %d", 3, len(searchResult.Hits.Hits))
  191. }
  192. for _, hit := range searchResult.Hits.Hits {
  193. if hit.Index != testIndexName {
  194. t.Errorf("expected SearchResult.Hits.Hit.Index = %q; got %q", testIndexName, hit.Index)
  195. }
  196. item := make(map[string]interface{})
  197. err := json.Unmarshal(*hit.Source, &item)
  198. if err != nil {
  199. t.Fatal(err)
  200. }
  201. }
  202. }
  203. func TestSearchSortingBySorters(t *testing.T) {
  204. client := setupTestClientAndCreateIndex(t)
  205. tweet1 := tweet{
  206. User: "olivere", Retweets: 108,
  207. Message: "Welcome to Golang and Elasticsearch.",
  208. Created: time.Date(2012, 12, 12, 17, 38, 34, 0, time.UTC),
  209. }
  210. tweet2 := tweet{
  211. User: "olivere", Retweets: 0,
  212. Message: "Another unrelated topic.",
  213. Created: time.Date(2012, 10, 10, 8, 12, 03, 0, time.UTC),
  214. }
  215. tweet3 := tweet{
  216. User: "sandrae", Retweets: 12,
  217. Message: "Cycling is fun.",
  218. Created: time.Date(2011, 11, 11, 10, 58, 12, 0, time.UTC),
  219. }
  220. // Add all documents
  221. _, err := client.Index().Index(testIndexName).Type("tweet").Id("1").BodyJson(&tweet1).Do()
  222. if err != nil {
  223. t.Fatal(err)
  224. }
  225. _, err = client.Index().Index(testIndexName).Type("tweet").Id("2").BodyJson(&tweet2).Do()
  226. if err != nil {
  227. t.Fatal(err)
  228. }
  229. _, err = client.Index().Index(testIndexName).Type("tweet").Id("3").BodyJson(&tweet3).Do()
  230. if err != nil {
  231. t.Fatal(err)
  232. }
  233. _, err = client.Flush().Index(testIndexName).Do()
  234. if err != nil {
  235. t.Fatal(err)
  236. }
  237. // Match all should return all documents
  238. all := NewMatchAllQuery()
  239. searchResult, err := client.Search().
  240. Index(testIndexName).
  241. Query(&all).
  242. SortBy(NewFieldSort("created").Desc(), NewScoreSort()).
  243. Timeout("1s").
  244. Do()
  245. if err != nil {
  246. t.Fatal(err)
  247. }
  248. if searchResult.Hits == nil {
  249. t.Errorf("expected SearchResult.Hits != nil; got nil")
  250. }
  251. if searchResult.Hits.TotalHits != 3 {
  252. t.Errorf("expected SearchResult.Hits.TotalHits = %d; got %d", 3, searchResult.Hits.TotalHits)
  253. }
  254. if len(searchResult.Hits.Hits) != 3 {
  255. t.Errorf("expected len(SearchResult.Hits.Hits) = %d; got %d", 3, len(searchResult.Hits.Hits))
  256. }
  257. for _, hit := range searchResult.Hits.Hits {
  258. if hit.Index != testIndexName {
  259. t.Errorf("expected SearchResult.Hits.Hit.Index = %q; got %q", testIndexName, hit.Index)
  260. }
  261. item := make(map[string]interface{})
  262. err := json.Unmarshal(*hit.Source, &item)
  263. if err != nil {
  264. t.Fatal(err)
  265. }
  266. }
  267. }
  268. func TestSearchSpecificFields(t *testing.T) {
  269. client := setupTestClientAndCreateIndex(t)
  270. tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
  271. tweet2 := tweet{User: "olivere", Message: "Another unrelated topic."}
  272. tweet3 := tweet{User: "sandrae", Message: "Cycling is fun."}
  273. // Add all documents
  274. _, err := client.Index().Index(testIndexName).Type("tweet").Id("1").BodyJson(&tweet1).Do()
  275. if err != nil {
  276. t.Fatal(err)
  277. }
  278. _, err = client.Index().Index(testIndexName).Type("tweet").Id("2").BodyJson(&tweet2).Do()
  279. if err != nil {
  280. t.Fatal(err)
  281. }
  282. _, err = client.Index().Index(testIndexName).Type("tweet").Id("3").BodyJson(&tweet3).Do()
  283. if err != nil {
  284. t.Fatal(err)
  285. }
  286. _, err = client.Flush().Index(testIndexName).Do()
  287. if err != nil {
  288. t.Fatal(err)
  289. }
  290. // Match all should return all documents
  291. all := NewMatchAllQuery()
  292. searchResult, err := client.Search().
  293. Index(testIndexName).
  294. Query(&all).
  295. Fields("message").
  296. Do()
  297. if err != nil {
  298. t.Fatal(err)
  299. }
  300. if searchResult.Hits == nil {
  301. t.Errorf("expected SearchResult.Hits != nil; got nil")
  302. }
  303. if searchResult.Hits.TotalHits != 3 {
  304. t.Errorf("expected SearchResult.Hits.TotalHits = %d; got %d", 3, searchResult.Hits.TotalHits)
  305. }
  306. if len(searchResult.Hits.Hits) != 3 {
  307. t.Errorf("expected len(SearchResult.Hits.Hits) = %d; got %d", 3, len(searchResult.Hits.Hits))
  308. }
  309. for _, hit := range searchResult.Hits.Hits {
  310. if hit.Index != testIndexName {
  311. t.Errorf("expected SearchResult.Hits.Hit.Index = %q; got %q", testIndexName, hit.Index)
  312. }
  313. if hit.Source != nil {
  314. t.Fatalf("expected SearchResult.Hits.Hit.Source to be nil; got: %q", hit.Source)
  315. }
  316. if hit.Fields == nil {
  317. t.Fatal("expected SearchResult.Hits.Hit.Fields to be != nil")
  318. }
  319. field, found := hit.Fields["message"]
  320. if !found {
  321. t.Errorf("expected SearchResult.Hits.Hit.Fields[%s] to be found", "message")
  322. }
  323. fields, ok := field.([]interface{})
  324. if !ok {
  325. t.Errorf("expected []interface{}; got: %v", reflect.TypeOf(fields))
  326. }
  327. if len(fields) != 1 {
  328. t.Errorf("expected a field with 1 entry; got: %d", len(fields))
  329. }
  330. message, ok := fields[0].(string)
  331. if !ok {
  332. t.Errorf("expected a string; got: %v", reflect.TypeOf(fields[0]))
  333. }
  334. if message == "" {
  335. t.Errorf("expected a message; got: %q", message)
  336. }
  337. }
  338. }
  339. func TestSearchExplain(t *testing.T) {
  340. client := setupTestClientAndCreateIndex(t)
  341. tweet1 := tweet{
  342. User: "olivere", Retweets: 108,
  343. Message: "Welcome to Golang and Elasticsearch.",
  344. Created: time.Date(2012, 12, 12, 17, 38, 34, 0, time.UTC),
  345. }
  346. tweet2 := tweet{
  347. User: "olivere", Retweets: 0,
  348. Message: "Another unrelated topic.",
  349. Created: time.Date(2012, 10, 10, 8, 12, 03, 0, time.UTC),
  350. }
  351. tweet3 := tweet{
  352. User: "sandrae", Retweets: 12,
  353. Message: "Cycling is fun.",
  354. Created: time.Date(2011, 11, 11, 10, 58, 12, 0, time.UTC),
  355. }
  356. // Add all documents
  357. _, err := client.Index().Index(testIndexName).Type("tweet").Id("1").BodyJson(&tweet1).Do()
  358. if err != nil {
  359. t.Fatal(err)
  360. }
  361. _, err = client.Index().Index(testIndexName).Type("tweet").Id("2").BodyJson(&tweet2).Do()
  362. if err != nil {
  363. t.Fatal(err)
  364. }
  365. _, err = client.Index().Index(testIndexName).Type("tweet").Id("3").BodyJson(&tweet3).Do()
  366. if err != nil {
  367. t.Fatal(err)
  368. }
  369. _, err = client.Flush().Index(testIndexName).Do()
  370. if err != nil {
  371. t.Fatal(err)
  372. }
  373. // Match all should return all documents
  374. all := NewMatchAllQuery()
  375. searchResult, err := client.Search().
  376. Index(testIndexName).
  377. Query(&all).
  378. Explain(true).
  379. Timeout("1s").
  380. // Pretty(true).
  381. Do()
  382. if err != nil {
  383. t.Fatal(err)
  384. }
  385. if searchResult.Hits == nil {
  386. t.Errorf("expected SearchResult.Hits != nil; got nil")
  387. }
  388. if searchResult.Hits.TotalHits != 3 {
  389. t.Errorf("expected SearchResult.Hits.TotalHits = %d; got %d", 3, searchResult.Hits.TotalHits)
  390. }
  391. if len(searchResult.Hits.Hits) != 3 {
  392. t.Errorf("expected len(SearchResult.Hits.Hits) = %d; got %d", 3, len(searchResult.Hits.Hits))
  393. }
  394. for _, hit := range searchResult.Hits.Hits {
  395. if hit.Index != testIndexName {
  396. t.Errorf("expected SearchResult.Hits.Hit.Index = %q; got %q", testIndexName, hit.Index)
  397. }
  398. if hit.Explanation == nil {
  399. t.Fatal("expected search explanation")
  400. }
  401. if hit.Explanation.Value <= 0.0 {
  402. t.Errorf("expected explanation value to be > 0.0; got: %v", hit.Explanation.Value)
  403. }
  404. if hit.Explanation.Description == "" {
  405. t.Errorf("expected explanation description != %q; got: %q", "", hit.Explanation.Description)
  406. }
  407. }
  408. }
  409. func TestSearchSource(t *testing.T) {
  410. client := setupTestClientAndCreateIndex(t)
  411. tweet1 := tweet{
  412. User: "olivere", Retweets: 108,
  413. Message: "Welcome to Golang and Elasticsearch.",
  414. Created: time.Date(2012, 12, 12, 17, 38, 34, 0, time.UTC),
  415. }
  416. tweet2 := tweet{
  417. User: "olivere", Retweets: 0,
  418. Message: "Another unrelated topic.",
  419. Created: time.Date(2012, 10, 10, 8, 12, 03, 0, time.UTC),
  420. }
  421. tweet3 := tweet{
  422. User: "sandrae", Retweets: 12,
  423. Message: "Cycling is fun.",
  424. Created: time.Date(2011, 11, 11, 10, 58, 12, 0, time.UTC),
  425. }
  426. // Add all documents
  427. _, err := client.Index().Index(testIndexName).Type("tweet").Id("1").BodyJson(&tweet1).Do()
  428. if err != nil {
  429. t.Fatal(err)
  430. }
  431. _, err = client.Index().Index(testIndexName).Type("tweet").Id("2").BodyJson(&tweet2).Do()
  432. if err != nil {
  433. t.Fatal(err)
  434. }
  435. _, err = client.Index().Index(testIndexName).Type("tweet").Id("3").BodyJson(&tweet3).Do()
  436. if err != nil {
  437. t.Fatal(err)
  438. }
  439. _, err = client.Flush().Index(testIndexName).Do()
  440. if err != nil {
  441. t.Fatal(err)
  442. }
  443. // Set up the request JSON manually to pass to the search service via Source()
  444. source := map[string]interface{}{
  445. "query": map[string]interface{}{
  446. "match_all": map[string]interface{}{},
  447. },
  448. }
  449. searchResult, err := client.Search().
  450. Index(testIndexName).
  451. Source(source). // sets the JSON request
  452. Do()
  453. if err != nil {
  454. t.Fatal(err)
  455. }
  456. if searchResult.Hits == nil {
  457. t.Errorf("expected SearchResult.Hits != nil; got nil")
  458. }
  459. if searchResult.Hits.TotalHits != 3 {
  460. t.Errorf("expected SearchResult.Hits.TotalHits = %d; got %d", 3, searchResult.Hits.TotalHits)
  461. }
  462. }
  463. func TestSearchSearchSource(t *testing.T) {
  464. client := setupTestClientAndCreateIndex(t)
  465. tweet1 := tweet{
  466. User: "olivere", Retweets: 108,
  467. Message: "Welcome to Golang and Elasticsearch.",
  468. Created: time.Date(2012, 12, 12, 17, 38, 34, 0, time.UTC),
  469. }
  470. tweet2 := tweet{
  471. User: "olivere", Retweets: 0,
  472. Message: "Another unrelated topic.",
  473. Created: time.Date(2012, 10, 10, 8, 12, 03, 0, time.UTC),
  474. }
  475. tweet3 := tweet{
  476. User: "sandrae", Retweets: 12,
  477. Message: "Cycling is fun.",
  478. Created: time.Date(2011, 11, 11, 10, 58, 12, 0, time.UTC),
  479. }
  480. // Add all documents
  481. _, err := client.Index().Index(testIndexName).Type("tweet").Id("1").BodyJson(&tweet1).Do()
  482. if err != nil {
  483. t.Fatal(err)
  484. }
  485. _, err = client.Index().Index(testIndexName).Type("tweet").Id("2").BodyJson(&tweet2).Do()
  486. if err != nil {
  487. t.Fatal(err)
  488. }
  489. _, err = client.Index().Index(testIndexName).Type("tweet").Id("3").BodyJson(&tweet3).Do()
  490. if err != nil {
  491. t.Fatal(err)
  492. }
  493. _, err = client.Flush().Index(testIndexName).Do()
  494. if err != nil {
  495. t.Fatal(err)
  496. }
  497. // Set up the search source manually and pass it to the search service via SearchSource()
  498. ss := NewSearchSource().Query(NewMatchAllQuery()).From(0).Size(2)
  499. // One can use ss.Source() to get to the raw interface{} that will be used
  500. // as the search request JSON by the SearchService.
  501. searchResult, err := client.Search().
  502. Index(testIndexName).
  503. SearchSource(ss). // sets the SearchSource
  504. Do()
  505. if err != nil {
  506. t.Fatal(err)
  507. }
  508. if searchResult.Hits == nil {
  509. t.Errorf("expected SearchResult.Hits != nil; got nil")
  510. }
  511. if searchResult.Hits.TotalHits != 3 {
  512. t.Errorf("expected SearchResult.Hits.TotalHits = %d; got %d", 3, searchResult.Hits.TotalHits)
  513. }
  514. if len(searchResult.Hits.Hits) != 2 {
  515. t.Errorf("expected len(SearchResult.Hits.Hits) = %d; got %d", 2, len(searchResult.Hits.Hits))
  516. }
  517. }