thinking-sphinx 2.1.0 → 3.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (352) hide show
  1. data/.gitignore +8 -0
  2. data/.travis.yml +13 -0
  3. data/Appraisals +7 -0
  4. data/Gemfile +10 -0
  5. data/HISTORY +2 -267
  6. data/LICENCE +1 -1
  7. data/README.textile +194 -226
  8. data/Rakefile +24 -0
  9. data/gemfiles/.gitignore +1 -0
  10. data/gemfiles/rails_3_1.gemfile +11 -0
  11. data/gemfiles/rails_3_2.gemfile +11 -0
  12. data/lib/thinking-sphinx.rb +1 -1
  13. data/lib/thinking_sphinx.rb +34 -292
  14. data/lib/thinking_sphinx/active_record.rb +22 -383
  15. data/lib/thinking_sphinx/active_record/association.rb +9 -0
  16. data/lib/thinking_sphinx/active_record/association_proxy.rb +68 -0
  17. data/lib/thinking_sphinx/active_record/associations.rb +68 -0
  18. data/lib/thinking_sphinx/active_record/attribute.rb +20 -0
  19. data/lib/thinking_sphinx/active_record/attribute/sphinx_presenter.rb +32 -0
  20. data/lib/thinking_sphinx/active_record/attribute/type.rb +79 -0
  21. data/lib/thinking_sphinx/active_record/attribute/values.rb +18 -0
  22. data/lib/thinking_sphinx/active_record/base.rb +36 -0
  23. data/lib/thinking_sphinx/active_record/callbacks/delete_callbacks.rb +31 -0
  24. data/lib/thinking_sphinx/active_record/callbacks/delta_callbacks.rb +55 -0
  25. data/lib/thinking_sphinx/active_record/callbacks/update_callbacks.rb +59 -0
  26. data/lib/thinking_sphinx/active_record/column.rb +30 -0
  27. data/lib/thinking_sphinx/active_record/database_adapters.rb +51 -0
  28. data/lib/thinking_sphinx/active_record/database_adapters/abstract_adapter.rb +13 -0
  29. data/lib/thinking_sphinx/active_record/database_adapters/mysql_adapter.rb +23 -0
  30. data/lib/thinking_sphinx/active_record/database_adapters/postgresql_adapter.rb +25 -0
  31. data/lib/thinking_sphinx/active_record/field.rb +11 -0
  32. data/lib/thinking_sphinx/active_record/index.rb +55 -0
  33. data/lib/thinking_sphinx/active_record/interpreter.rb +47 -0
  34. data/lib/thinking_sphinx/active_record/log_subscriber.rb +10 -58
  35. data/lib/thinking_sphinx/active_record/property.rb +28 -0
  36. data/lib/thinking_sphinx/active_record/property_sql_presenter.rb +60 -0
  37. data/lib/thinking_sphinx/active_record/sql_builder.rb +159 -0
  38. data/lib/thinking_sphinx/active_record/sql_source.rb +138 -0
  39. data/lib/thinking_sphinx/active_record/sql_source/template.rb +46 -0
  40. data/lib/thinking_sphinx/batched_search.rb +26 -0
  41. data/lib/thinking_sphinx/callbacks.rb +15 -0
  42. data/lib/thinking_sphinx/configuration.rb +80 -331
  43. data/lib/thinking_sphinx/configuration/consistent_ids.rb +31 -0
  44. data/lib/thinking_sphinx/configuration/defaults.rb +5 -0
  45. data/lib/thinking_sphinx/core.rb +6 -0
  46. data/lib/thinking_sphinx/core/index.rb +68 -0
  47. data/lib/thinking_sphinx/core/interpreter.rb +19 -0
  48. data/lib/thinking_sphinx/deltas.rb +35 -26
  49. data/lib/thinking_sphinx/deltas/default_delta.rb +56 -56
  50. data/lib/thinking_sphinx/excerpter.rb +23 -21
  51. data/lib/thinking_sphinx/facet.rb +22 -127
  52. data/lib/thinking_sphinx/facet_search.rb +95 -162
  53. data/lib/thinking_sphinx/index.rb +39 -143
  54. data/lib/thinking_sphinx/index_set.rb +51 -0
  55. data/lib/thinking_sphinx/masks.rb +8 -0
  56. data/lib/thinking_sphinx/masks/group_enumerators_mask.rb +23 -0
  57. data/lib/thinking_sphinx/masks/pagination_mask.rb +60 -0
  58. data/lib/thinking_sphinx/masks/scopes_mask.rb +35 -0
  59. data/lib/thinking_sphinx/masks/weight_enumerator_mask.rb +11 -0
  60. data/lib/thinking_sphinx/middlewares.rb +36 -0
  61. data/lib/thinking_sphinx/middlewares/active_record_translator.rb +73 -0
  62. data/lib/thinking_sphinx/middlewares/geographer.rb +53 -0
  63. data/lib/thinking_sphinx/middlewares/glazier.rb +39 -0
  64. data/lib/thinking_sphinx/middlewares/ids_only.rb +13 -0
  65. data/lib/thinking_sphinx/middlewares/inquirer.rb +62 -0
  66. data/lib/thinking_sphinx/middlewares/middleware.rb +9 -0
  67. data/lib/thinking_sphinx/middlewares/sphinxql.rb +149 -0
  68. data/lib/thinking_sphinx/middlewares/stale_id_checker.rb +45 -0
  69. data/lib/thinking_sphinx/middlewares/stale_id_filter.rb +46 -0
  70. data/lib/thinking_sphinx/panes.rb +8 -0
  71. data/lib/thinking_sphinx/panes/attributes_pane.rb +9 -0
  72. data/lib/thinking_sphinx/panes/distance_pane.rb +13 -0
  73. data/lib/thinking_sphinx/panes/excerpts_pane.rb +37 -0
  74. data/lib/thinking_sphinx/panes/weight_pane.rb +9 -0
  75. data/lib/thinking_sphinx/railtie.rb +6 -40
  76. data/lib/thinking_sphinx/rake_interface.rb +47 -0
  77. data/lib/thinking_sphinx/real_time.rb +11 -0
  78. data/lib/thinking_sphinx/real_time/attribute.rb +5 -0
  79. data/lib/thinking_sphinx/real_time/callbacks/real_time_callbacks.rb +48 -0
  80. data/lib/thinking_sphinx/real_time/field.rb +3 -0
  81. data/lib/thinking_sphinx/real_time/index.rb +47 -0
  82. data/lib/thinking_sphinx/real_time/index/template.rb +33 -0
  83. data/lib/thinking_sphinx/real_time/interpreter.rb +23 -0
  84. data/lib/thinking_sphinx/real_time/property.rb +16 -0
  85. data/lib/thinking_sphinx/scopes.rb +22 -0
  86. data/lib/thinking_sphinx/search.rb +90 -1028
  87. data/lib/thinking_sphinx/search/batch_inquirer.rb +27 -0
  88. data/lib/thinking_sphinx/search/context.rb +26 -0
  89. data/lib/thinking_sphinx/search/glaze.rb +32 -0
  90. data/lib/thinking_sphinx/search/merger.rb +24 -0
  91. data/lib/thinking_sphinx/search/query.rb +43 -0
  92. data/lib/thinking_sphinx/search/stale_ids_exception.rb +11 -0
  93. data/lib/thinking_sphinx/search/translator.rb +50 -0
  94. data/lib/thinking_sphinx/tasks.rb +22 -125
  95. data/lib/thinking_sphinx/test.rb +9 -19
  96. data/sketchpad.rb +58 -0
  97. data/spec/acceptance/association_scoping_spec.rb +23 -0
  98. data/spec/acceptance/attribute_access_spec.rb +39 -0
  99. data/spec/acceptance/attribute_updates_spec.rb +16 -0
  100. data/spec/acceptance/batch_searching_spec.rb +21 -0
  101. data/spec/acceptance/big_integers_spec.rb +27 -0
  102. data/spec/acceptance/excerpts_spec.rb +14 -0
  103. data/spec/acceptance/facets_spec.rb +122 -0
  104. data/spec/acceptance/geosearching_spec.rb +39 -0
  105. data/spec/acceptance/grouping_by_attributes_spec.rb +77 -0
  106. data/spec/acceptance/paginating_search_results_spec.rb +24 -0
  107. data/spec/acceptance/remove_deleted_records_spec.rb +23 -0
  108. data/spec/acceptance/search_counts_spec.rb +18 -0
  109. data/spec/acceptance/search_for_just_ids_spec.rb +19 -0
  110. data/spec/acceptance/searching_across_models_spec.rb +28 -0
  111. data/spec/acceptance/searching_on_fields_spec.rb +56 -0
  112. data/spec/acceptance/searching_with_filters_spec.rb +109 -0
  113. data/spec/acceptance/searching_with_sti_spec.rb +55 -0
  114. data/spec/acceptance/searching_within_a_model_spec.rb +52 -0
  115. data/spec/acceptance/sorting_search_results_spec.rb +41 -0
  116. data/spec/acceptance/spec_helper.rb +4 -0
  117. data/spec/acceptance/specifying_sql_spec.rb +62 -0
  118. data/spec/acceptance/sphinx_scopes_spec.rb +49 -0
  119. data/spec/acceptance/sql_deltas_spec.rb +43 -0
  120. data/spec/acceptance/support/database_cleaner.rb +11 -0
  121. data/spec/acceptance/support/sphinx_controller.rb +39 -0
  122. data/spec/acceptance/support/sphinx_helpers.rb +24 -0
  123. data/spec/acceptance/suspended_deltas_spec.rb +20 -0
  124. data/spec/internal/.gitignore +1 -0
  125. data/spec/internal/app/indices/animal_index.rb +3 -0
  126. data/spec/internal/app/indices/article_index.rb +24 -0
  127. data/spec/internal/app/indices/book_index.rb +8 -0
  128. data/spec/internal/app/indices/city_index.rb +6 -0
  129. data/spec/internal/app/indices/product_index.rb +3 -0
  130. data/spec/internal/app/indices/tee_index.rb +4 -0
  131. data/spec/internal/app/indices/user_index.rb +5 -0
  132. data/spec/internal/app/models/animal.rb +2 -0
  133. data/spec/internal/app/models/article.rb +5 -0
  134. data/spec/internal/app/models/bird.rb +2 -0
  135. data/spec/internal/app/models/book.rb +11 -0
  136. data/spec/internal/app/models/city.rb +2 -0
  137. data/spec/internal/app/models/colour.rb +3 -0
  138. data/spec/internal/app/models/flightless_bird.rb +2 -0
  139. data/spec/internal/app/models/mammal.rb +2 -0
  140. data/spec/internal/app/models/product.rb +3 -0
  141. data/spec/internal/app/models/tag.rb +4 -0
  142. data/{features/thinking_sphinx → spec/internal/app}/models/tagging.rb +1 -1
  143. data/spec/internal/app/models/tee.rb +3 -0
  144. data/spec/internal/app/models/tweet.rb +3 -0
  145. data/spec/internal/app/models/user.rb +3 -0
  146. data/spec/internal/config/database.yml +5 -0
  147. data/spec/internal/db/schema.rb +65 -0
  148. data/spec/internal/log/.gitignore +1 -0
  149. data/spec/spec_helper.rb +8 -49
  150. data/spec/support/sphinx_yaml_helpers.rb +9 -0
  151. data/spec/thinking_sphinx/active_record/association_spec.rb +12 -0
  152. data/spec/thinking_sphinx/active_record/associations_spec.rb +184 -0
  153. data/spec/thinking_sphinx/active_record/attribute/type_spec.rb +147 -0
  154. data/spec/thinking_sphinx/active_record/base_spec.rb +61 -0
  155. data/spec/thinking_sphinx/active_record/callbacks/delete_callbacks_spec.rb +80 -0
  156. data/spec/thinking_sphinx/active_record/callbacks/delta_callbacks_spec.rb +147 -0
  157. data/spec/thinking_sphinx/active_record/callbacks/update_callbacks_spec.rb +69 -0
  158. data/spec/thinking_sphinx/active_record/column_spec.rb +47 -0
  159. data/spec/thinking_sphinx/active_record/database_adapters/abstract_adapter_spec.rb +31 -0
  160. data/spec/thinking_sphinx/active_record/database_adapters/mysql_adapter_spec.rb +43 -0
  161. data/spec/thinking_sphinx/active_record/database_adapters/postgresql_adapter_spec.rb +45 -0
  162. data/spec/thinking_sphinx/active_record/database_adapters_spec.rb +108 -0
  163. data/spec/thinking_sphinx/active_record/field_spec.rb +36 -0
  164. data/spec/thinking_sphinx/active_record/index_spec.rb +208 -0
  165. data/spec/thinking_sphinx/active_record/interpreter_spec.rb +293 -0
  166. data/spec/thinking_sphinx/active_record/property_sql_presenter_spec.rb +162 -0
  167. data/spec/thinking_sphinx/active_record/sql_builder_spec.rb +666 -0
  168. data/spec/thinking_sphinx/active_record/sql_source_spec.rb +401 -0
  169. data/spec/thinking_sphinx/configuration_spec.rb +264 -171
  170. data/spec/thinking_sphinx/deltas/default_delta_spec.rb +116 -0
  171. data/spec/thinking_sphinx/deltas_spec.rb +58 -0
  172. data/spec/thinking_sphinx/excerpter_spec.rb +40 -38
  173. data/spec/thinking_sphinx/facet_search_spec.rb +49 -151
  174. data/spec/thinking_sphinx/index_set_spec.rb +68 -0
  175. data/spec/thinking_sphinx/index_spec.rb +91 -155
  176. data/spec/thinking_sphinx/masks/pagination_mask_spec.rb +121 -0
  177. data/spec/thinking_sphinx/masks/scopes_mask_spec.rb +68 -0
  178. data/spec/thinking_sphinx/middlewares/active_record_translator_spec.rb +132 -0
  179. data/spec/thinking_sphinx/middlewares/geographer_spec.rb +89 -0
  180. data/spec/thinking_sphinx/middlewares/glazier_spec.rb +62 -0
  181. data/spec/thinking_sphinx/middlewares/inquirer_spec.rb +55 -0
  182. data/spec/thinking_sphinx/middlewares/sphinxql_spec.rb +271 -0
  183. data/spec/thinking_sphinx/middlewares/stale_id_checker_spec.rb +47 -0
  184. data/spec/thinking_sphinx/middlewares/stale_id_filter_spec.rb +91 -0
  185. data/spec/thinking_sphinx/panes/attributes_pane_spec.rb +21 -0
  186. data/spec/thinking_sphinx/panes/distance_pane_spec.rb +41 -0
  187. data/spec/thinking_sphinx/panes/excerpts_pane_spec.rb +53 -0
  188. data/spec/thinking_sphinx/panes/weight_pane_spec.rb +20 -0
  189. data/spec/thinking_sphinx/rake_interface_spec.rb +147 -0
  190. data/spec/thinking_sphinx/real_time/attribute_spec.rb +62 -0
  191. data/spec/thinking_sphinx/real_time/callbacks/real_time_callbacks_spec.rb +76 -0
  192. data/spec/thinking_sphinx/real_time/field_spec.rb +54 -0
  193. data/spec/thinking_sphinx/real_time/index_spec.rb +154 -0
  194. data/spec/thinking_sphinx/real_time/interpreter_spec.rb +147 -0
  195. data/spec/thinking_sphinx/scopes_spec.rb +38 -0
  196. data/spec/thinking_sphinx/search/glaze_spec.rb +55 -0
  197. data/spec/thinking_sphinx/search/query_spec.rb +46 -0
  198. data/spec/thinking_sphinx/search_spec.rb +65 -1357
  199. data/spec/thinking_sphinx_spec.rb +19 -182
  200. data/thinking-sphinx.gemspec +33 -0
  201. metadata +318 -431
  202. data/features/abstract_inheritance.feature +0 -10
  203. data/features/alternate_primary_key.feature +0 -27
  204. data/features/attribute_transformation.feature +0 -22
  205. data/features/attribute_updates.feature +0 -79
  206. data/features/deleting_instances.feature +0 -70
  207. data/features/direct_attributes.feature +0 -11
  208. data/features/excerpts.feature +0 -21
  209. data/features/extensible_delta_indexing.feature +0 -9
  210. data/features/facets.feature +0 -88
  211. data/features/facets_across_model.feature +0 -29
  212. data/features/field_sorting.feature +0 -18
  213. data/features/handling_edits.feature +0 -97
  214. data/features/retry_stale_indexes.feature +0 -24
  215. data/features/searching_across_models.feature +0 -20
  216. data/features/searching_by_index.feature +0 -41
  217. data/features/searching_by_model.feature +0 -175
  218. data/features/searching_with_find_arguments.feature +0 -56
  219. data/features/sphinx_detection.feature +0 -25
  220. data/features/sphinx_scopes.feature +0 -68
  221. data/features/step_definitions/alpha_steps.rb +0 -16
  222. data/features/step_definitions/beta_steps.rb +0 -7
  223. data/features/step_definitions/common_steps.rb +0 -205
  224. data/features/step_definitions/extensible_delta_indexing_steps.rb +0 -7
  225. data/features/step_definitions/facet_steps.rb +0 -96
  226. data/features/step_definitions/find_arguments_steps.rb +0 -36
  227. data/features/step_definitions/gamma_steps.rb +0 -15
  228. data/features/step_definitions/scope_steps.rb +0 -19
  229. data/features/step_definitions/search_steps.rb +0 -94
  230. data/features/step_definitions/sphinx_steps.rb +0 -35
  231. data/features/sti_searching.feature +0 -19
  232. data/features/support/env.rb +0 -24
  233. data/features/support/lib/generic_delta_handler.rb +0 -8
  234. data/features/thinking_sphinx/database.example.yml +0 -3
  235. data/features/thinking_sphinx/db/.gitignore +0 -1
  236. data/features/thinking_sphinx/db/fixtures/alphas.rb +0 -8
  237. data/features/thinking_sphinx/db/fixtures/authors.rb +0 -1
  238. data/features/thinking_sphinx/db/fixtures/betas.rb +0 -11
  239. data/features/thinking_sphinx/db/fixtures/boxes.rb +0 -9
  240. data/features/thinking_sphinx/db/fixtures/categories.rb +0 -1
  241. data/features/thinking_sphinx/db/fixtures/cats.rb +0 -3
  242. data/features/thinking_sphinx/db/fixtures/comments.rb +0 -24
  243. data/features/thinking_sphinx/db/fixtures/developers.rb +0 -31
  244. data/features/thinking_sphinx/db/fixtures/dogs.rb +0 -3
  245. data/features/thinking_sphinx/db/fixtures/extensible_betas.rb +0 -10
  246. data/features/thinking_sphinx/db/fixtures/foxes.rb +0 -3
  247. data/features/thinking_sphinx/db/fixtures/gammas.rb +0 -10
  248. data/features/thinking_sphinx/db/fixtures/music.rb +0 -4
  249. data/features/thinking_sphinx/db/fixtures/people.rb +0 -1001
  250. data/features/thinking_sphinx/db/fixtures/post_keywords.txt +0 -1
  251. data/features/thinking_sphinx/db/fixtures/posts.rb +0 -10
  252. data/features/thinking_sphinx/db/fixtures/robots.rb +0 -8
  253. data/features/thinking_sphinx/db/fixtures/tags.rb +0 -27
  254. data/features/thinking_sphinx/db/migrations/create_alphas.rb +0 -8
  255. data/features/thinking_sphinx/db/migrations/create_animals.rb +0 -5
  256. data/features/thinking_sphinx/db/migrations/create_authors.rb +0 -3
  257. data/features/thinking_sphinx/db/migrations/create_authors_posts.rb +0 -6
  258. data/features/thinking_sphinx/db/migrations/create_betas.rb +0 -5
  259. data/features/thinking_sphinx/db/migrations/create_boxes.rb +0 -5
  260. data/features/thinking_sphinx/db/migrations/create_categories.rb +0 -3
  261. data/features/thinking_sphinx/db/migrations/create_comments.rb +0 -10
  262. data/features/thinking_sphinx/db/migrations/create_developers.rb +0 -7
  263. data/features/thinking_sphinx/db/migrations/create_extensible_betas.rb +0 -5
  264. data/features/thinking_sphinx/db/migrations/create_gammas.rb +0 -3
  265. data/features/thinking_sphinx/db/migrations/create_genres.rb +0 -3
  266. data/features/thinking_sphinx/db/migrations/create_music.rb +0 -6
  267. data/features/thinking_sphinx/db/migrations/create_people.rb +0 -13
  268. data/features/thinking_sphinx/db/migrations/create_posts.rb +0 -6
  269. data/features/thinking_sphinx/db/migrations/create_robots.rb +0 -4
  270. data/features/thinking_sphinx/db/migrations/create_taggings.rb +0 -5
  271. data/features/thinking_sphinx/db/migrations/create_tags.rb +0 -4
  272. data/features/thinking_sphinx/models/alpha.rb +0 -23
  273. data/features/thinking_sphinx/models/andrew.rb +0 -17
  274. data/features/thinking_sphinx/models/animal.rb +0 -5
  275. data/features/thinking_sphinx/models/author.rb +0 -3
  276. data/features/thinking_sphinx/models/beta.rb +0 -13
  277. data/features/thinking_sphinx/models/box.rb +0 -8
  278. data/features/thinking_sphinx/models/cat.rb +0 -3
  279. data/features/thinking_sphinx/models/category.rb +0 -4
  280. data/features/thinking_sphinx/models/comment.rb +0 -10
  281. data/features/thinking_sphinx/models/developer.rb +0 -21
  282. data/features/thinking_sphinx/models/dog.rb +0 -3
  283. data/features/thinking_sphinx/models/extensible_beta.rb +0 -9
  284. data/features/thinking_sphinx/models/fox.rb +0 -5
  285. data/features/thinking_sphinx/models/gamma.rb +0 -5
  286. data/features/thinking_sphinx/models/genre.rb +0 -3
  287. data/features/thinking_sphinx/models/medium.rb +0 -5
  288. data/features/thinking_sphinx/models/music.rb +0 -10
  289. data/features/thinking_sphinx/models/person.rb +0 -24
  290. data/features/thinking_sphinx/models/post.rb +0 -22
  291. data/features/thinking_sphinx/models/robot.rb +0 -12
  292. data/features/thinking_sphinx/models/tag.rb +0 -3
  293. data/lib/cucumber/thinking_sphinx/external_world.rb +0 -12
  294. data/lib/cucumber/thinking_sphinx/internal_world.rb +0 -137
  295. data/lib/cucumber/thinking_sphinx/sql_logger.rb +0 -28
  296. data/lib/thinking_sphinx/action_controller.rb +0 -31
  297. data/lib/thinking_sphinx/active_record/attribute_updates.rb +0 -54
  298. data/lib/thinking_sphinx/active_record/collection_proxy.rb +0 -47
  299. data/lib/thinking_sphinx/active_record/collection_proxy_with_scopes.rb +0 -27
  300. data/lib/thinking_sphinx/active_record/delta.rb +0 -67
  301. data/lib/thinking_sphinx/active_record/has_many_association.rb +0 -44
  302. data/lib/thinking_sphinx/active_record/has_many_association_with_scopes.rb +0 -21
  303. data/lib/thinking_sphinx/active_record/scopes.rb +0 -110
  304. data/lib/thinking_sphinx/adapters/abstract_adapter.rb +0 -94
  305. data/lib/thinking_sphinx/adapters/mysql_adapter.rb +0 -62
  306. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +0 -188
  307. data/lib/thinking_sphinx/association.rb +0 -230
  308. data/lib/thinking_sphinx/attribute.rb +0 -405
  309. data/lib/thinking_sphinx/auto_version.rb +0 -40
  310. data/lib/thinking_sphinx/bundled_search.rb +0 -40
  311. data/lib/thinking_sphinx/class_facet.rb +0 -20
  312. data/lib/thinking_sphinx/connection.rb +0 -71
  313. data/lib/thinking_sphinx/context.rb +0 -81
  314. data/lib/thinking_sphinx/core/string.rb +0 -15
  315. data/lib/thinking_sphinx/deltas/delete_job.rb +0 -16
  316. data/lib/thinking_sphinx/deltas/index_job.rb +0 -17
  317. data/lib/thinking_sphinx/deploy/capistrano.rb +0 -99
  318. data/lib/thinking_sphinx/field.rb +0 -98
  319. data/lib/thinking_sphinx/index/builder.rb +0 -315
  320. data/lib/thinking_sphinx/index/faux_column.rb +0 -118
  321. data/lib/thinking_sphinx/join.rb +0 -37
  322. data/lib/thinking_sphinx/property.rb +0 -187
  323. data/lib/thinking_sphinx/search_methods.rb +0 -439
  324. data/lib/thinking_sphinx/sinatra.rb +0 -7
  325. data/lib/thinking_sphinx/source.rb +0 -194
  326. data/lib/thinking_sphinx/source/internal_properties.rb +0 -51
  327. data/lib/thinking_sphinx/source/sql.rb +0 -174
  328. data/spec/fixtures/data.sql +0 -32
  329. data/spec/fixtures/database.yml.default +0 -3
  330. data/spec/fixtures/models.rb +0 -164
  331. data/spec/fixtures/structure.sql +0 -146
  332. data/spec/sphinx_helper.rb +0 -60
  333. data/spec/support/rails.rb +0 -25
  334. data/spec/thinking_sphinx/active_record/delta_spec.rb +0 -123
  335. data/spec/thinking_sphinx/active_record/has_many_association_spec.rb +0 -173
  336. data/spec/thinking_sphinx/active_record/scopes_spec.rb +0 -177
  337. data/spec/thinking_sphinx/active_record_spec.rb +0 -573
  338. data/spec/thinking_sphinx/adapters/abstract_adapter_spec.rb +0 -163
  339. data/spec/thinking_sphinx/association_spec.rb +0 -250
  340. data/spec/thinking_sphinx/attribute_spec.rb +0 -552
  341. data/spec/thinking_sphinx/auto_version_spec.rb +0 -103
  342. data/spec/thinking_sphinx/connection_spec.rb +0 -77
  343. data/spec/thinking_sphinx/context_spec.rb +0 -127
  344. data/spec/thinking_sphinx/core/array_spec.rb +0 -9
  345. data/spec/thinking_sphinx/core/string_spec.rb +0 -9
  346. data/spec/thinking_sphinx/facet_spec.rb +0 -359
  347. data/spec/thinking_sphinx/field_spec.rb +0 -127
  348. data/spec/thinking_sphinx/index/builder_spec.rb +0 -532
  349. data/spec/thinking_sphinx/index/faux_column_spec.rb +0 -36
  350. data/spec/thinking_sphinx/search_methods_spec.rb +0 -156
  351. data/spec/thinking_sphinx/source_spec.rb +0 -267
  352. data/spec/thinking_sphinx/test_spec.rb +0 -20
@@ -0,0 +1,5 @@
1
+ class ThinkingSphinx::RealTime::Attribute < ThinkingSphinx::RealTime::Property
2
+ def type
3
+ @options[:type]
4
+ end
5
+ end
@@ -0,0 +1,48 @@
1
+ class ThinkingSphinx::RealTime::Callbacks::RealTimeCallbacks <
2
+ ThinkingSphinx::Callbacks
3
+
4
+ callbacks :after_save
5
+
6
+ def after_save
7
+ return unless real_time_indices?
8
+
9
+ real_time_indices.each do |index|
10
+ columns, values = ['id'], [index.document_id_for_key(instance.id)]
11
+ (index.fields + index.attributes).each do |property|
12
+ columns << property.name
13
+ values << property.translate(instance)
14
+ end
15
+
16
+ sphinxql = Riddle::Query::Insert.new(index.name, columns, values).replace!
17
+ connection.query sphinxql.to_sql
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def config
24
+ ThinkingSphinx::Configuration.instance
25
+ end
26
+
27
+ def connection
28
+ connection = config.connection
29
+ end
30
+
31
+ def indices
32
+ @indices ||= config.indices_for_references reference
33
+ end
34
+
35
+ def real_time_indices?
36
+ real_time_indices.any?
37
+ end
38
+
39
+ def real_time_indices
40
+ @real_time_indices ||= indices.select { |index|
41
+ index.is_a? ThinkingSphinx::RealTime::Index
42
+ }
43
+ end
44
+
45
+ def reference
46
+ instance.class.name.underscore.to_sym
47
+ end
48
+ end
@@ -0,0 +1,3 @@
1
+ class ThinkingSphinx::RealTime::Field < ThinkingSphinx::RealTime::Property
2
+ #
3
+ end
@@ -0,0 +1,47 @@
1
+ class ThinkingSphinx::RealTime::Index < Riddle::Configuration::RealtimeIndex
2
+ include ThinkingSphinx::Core::Index
3
+
4
+ attr_accessor :fields, :attributes
5
+
6
+ def initialize(reference, options = {})
7
+ @fields = []
8
+ @attributes = []
9
+
10
+ Template.new(self).apply
11
+
12
+ super reference, options
13
+ end
14
+
15
+ def unique_attribute_names
16
+ attributes.collect(&:name)
17
+ end
18
+
19
+ private
20
+
21
+ def interpreter
22
+ ThinkingSphinx::RealTime::Interpreter
23
+ end
24
+
25
+ def pre_render
26
+ super
27
+
28
+ @rt_field = fields.collect &:name
29
+
30
+ attributes.each do |attribute|
31
+ case attribute.type
32
+ when :integer, :boolean
33
+ @rt_attr_uint << attribute.name unless @rt_attr_uint.include?(attribute.name)
34
+ when :string
35
+ @rt_attr_string << attribute.name unless @rt_attr_string.include?(attribute.name)
36
+ when :timestamp
37
+ @rt_attr_timestamp << attribute.name unless @rt_attr_timestamp.include?(attribute.name)
38
+ when :float
39
+ @rt_attr_float << attribute.name unless @rt_attr_float.include?(attribute.name)
40
+ else
41
+ raise "Unknown attribute type '#{attribute.type(model)}'"
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ require 'thinking_sphinx/real_time/index/template'
@@ -0,0 +1,33 @@
1
+ class ThinkingSphinx::RealTime::Index::Template
2
+ attr_reader :index
3
+
4
+ def initialize(index)
5
+ @index = index
6
+ end
7
+
8
+ def apply
9
+ add_field class_column, :sphinx_internal_class
10
+
11
+ add_attribute :id, :sphinx_internal_id, :integer
12
+ add_attribute 0, :sphinx_deleted, :integer
13
+ end
14
+
15
+ private
16
+
17
+ def add_attribute(column, name, type)
18
+ index.attributes << ThinkingSphinx::RealTime::Attribute.new(
19
+ ThinkingSphinx::ActiveRecord::Column.new(*column),
20
+ :as => name, :type => type
21
+ )
22
+ end
23
+
24
+ def add_field(column, name)
25
+ index.fields << ThinkingSphinx::RealTime::Field.new(
26
+ ThinkingSphinx::ActiveRecord::Column.new(*column), :as => name
27
+ )
28
+ end
29
+
30
+ def class_column
31
+ [:class, :name]
32
+ end
33
+ end
@@ -0,0 +1,23 @@
1
+ class ThinkingSphinx::RealTime::Interpreter <
2
+ ::ThinkingSphinx::Core::Interpreter
3
+
4
+ def has(*columns)
5
+ options = columns.extract_options!
6
+ @index.attributes += columns.collect { |column|
7
+ ::ThinkingSphinx::RealTime::Attribute.new column, options
8
+ }
9
+ end
10
+
11
+ def indexes(*columns)
12
+ options = columns.extract_options!
13
+ @index.fields += columns.collect { |column|
14
+ ::ThinkingSphinx::RealTime::Field.new column, options
15
+ }
16
+ end
17
+
18
+ def set_property(properties)
19
+ properties.each do |key, value|
20
+ @index.send("#{key}=", value) if @index.class.settings.include?(key)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,16 @@
1
+ class ThinkingSphinx::RealTime::Property
2
+ def initialize(column, options = {})
3
+ @column, @options = column, options
4
+ end
5
+
6
+ def name
7
+ (@options[:as] || @column.__name).to_s
8
+ end
9
+
10
+ def translate(object)
11
+ return @column.__name unless @column.__name.is_a?(Symbol)
12
+
13
+ base = @column.__stack.inject(object) { |base, node| base.try(node) }
14
+ base.try(@column.__name)
15
+ end
16
+ end
@@ -0,0 +1,22 @@
1
+ module ThinkingSphinx::Scopes
2
+ extend ActiveSupport::Concern
3
+
4
+ module ClassMethods
5
+ def sphinx_scope(name, &block)
6
+ sphinx_scopes[name] = block
7
+ end
8
+
9
+ def sphinx_scopes
10
+ @sphinx_scopes ||= {}
11
+ end
12
+
13
+ private
14
+
15
+ def method_missing(method, *args, &block)
16
+ return super unless sphinx_scopes.keys.include?(method)
17
+
18
+ query, options = sphinx_scopes[method].call(*args)
19
+ search query, (options || {})
20
+ end
21
+ end
22
+ end
@@ -1,1054 +1,116 @@
1
- # encoding: UTF-8
2
- module ThinkingSphinx
3
- # Once you've got those indexes in and built, this is the stuff that
4
- # matters - how to search! This class provides a generic search
5
- # interface - which you can use to search all your indexed models at once.
6
- # Most times, you will just want a specific model's results - to search and
7
- # search_for_ids methods will do the job in exactly the same manner when
8
- # called from a model.
9
- #
10
- class Search < Array
11
- CoreMethods = %w( == class class_eval extend frozen? id instance_eval
12
- instance_of? instance_values instance_variable_defined?
13
- instance_variable_get instance_variable_set instance_variables is_a?
14
- kind_of? member? method methods nil? object_id respond_to?
15
- respond_to_missing? send should tap type )
16
- SafeMethods = %w( partition private_methods protected_methods
17
- public_methods send class )
18
-
19
- instance_methods.select { |method|
20
- method.to_s[/^__/].nil? && !CoreMethods.include?(method.to_s)
21
- }.each { |method|
22
- undef_method method
23
- }
24
-
25
- HashOptions = [:conditions, :with, :without, :with_all, :without_any]
26
- ArrayOptions = [:classes, :without_ids]
27
-
28
- attr_reader :args, :options
29
-
30
- # Deprecated. Use ThinkingSphinx.search
31
- def self.search(*args)
32
- warn 'ThinkingSphinx::Search.search is deprecated. Please use ThinkingSphinx.search instead.'
33
- ThinkingSphinx.search(*args)
34
- end
35
-
36
- # Deprecated. Use ThinkingSphinx.search_for_ids
37
- def self.search_for_ids(*args)
38
- warn 'ThinkingSphinx::Search.search_for_ids is deprecated. Please use ThinkingSphinx.search_for_ids instead.'
39
- ThinkingSphinx.search_for_ids(*args)
40
- end
41
-
42
- # Deprecated. Use ThinkingSphinx.search_for_ids
43
- def self.search_for_id(*args)
44
- warn 'ThinkingSphinx::Search.search_for_id is deprecated. Please use ThinkingSphinx.search_for_id instead.'
45
- ThinkingSphinx.search_for_id(*args)
46
- end
47
-
48
- # Deprecated. Use ThinkingSphinx.count
49
- def self.count(*args)
50
- warn 'ThinkingSphinx::Search.count is deprecated. Please use ThinkingSphinx.count instead.'
51
- ThinkingSphinx.count(*args)
52
- end
53
-
54
- # Deprecated. Use ThinkingSphinx.facets
55
- def self.facets(*args)
56
- warn 'ThinkingSphinx::Search.facets is deprecated. Please use ThinkingSphinx.facets instead.'
57
- ThinkingSphinx.facets(*args)
58
- end
59
-
60
- def self.warn(message)
61
- ::ActiveSupport::Deprecation.warn message
62
- end
63
-
64
- def self.bundle_searches(enum = nil)
65
- bundle = ThinkingSphinx::BundledSearch.new
66
-
67
- if enum.nil?
68
- yield bundle
69
- else
70
- enum.each { |item| yield bundle, item }
71
- end
72
-
73
- bundle.searches
74
- end
75
-
76
- def self.matching_fields(fields, bitmask)
77
- matches = []
78
- bitstring = bitmask.to_s(2).rjust(32, '0').reverse
79
-
80
- fields.each_with_index do |field, index|
81
- matches << field if bitstring[index, 1] == '1'
82
- end
83
- matches
84
- end
85
-
86
- def initialize(*args)
87
- ThinkingSphinx.context.define_indexes
88
-
89
- @array = []
90
- @options = args.extract_options!
91
- @args = args
92
-
93
- add_default_scope unless options[:ignore_default]
94
-
95
- populate if @options[:populate]
96
- end
97
-
98
- def ==(object)
99
- populate
100
- super
101
- end
102
-
103
- def to_a
104
- populate
105
- @array
106
- end
107
-
108
- # Populates the search result set
109
- def all
110
- populate
111
- self
112
- end
113
-
114
- def freeze
115
- populate
116
- @array.freeze
117
- self
118
- end
119
-
120
- def as_json(*args)
121
- populate
122
- @array.as_json(*args)
123
- end
124
-
125
- # Indication of whether the request has been made to Sphinx for the search
126
- # query.
127
- #
128
- # @return [Boolean] true if the results have been requested.
129
- #
130
- def populated?
131
- !!@populated
132
- end
133
-
134
- # Indication of whether the request resulted in an error from Sphinx.
135
- #
136
- # @return [Boolean] true if Sphinx reports query error
137
- #
138
- def error?
139
- !!error
140
- end
141
-
142
- # The Sphinx-reported error, if any.
143
- #
144
- # @return [String, nil]
145
- #
146
- def error
147
- populate
148
- @results[:error]
149
- end
150
-
151
- # Indication of whether the request resulted in a warning from Sphinx.
152
- #
153
- # @return [Boolean] true if Sphinx reports query warning
154
- #
155
- def warning?
156
- !!warning
157
- end
158
-
159
- # The Sphinx-reported warning, if any.
160
- #
161
- # @return [String, nil]
162
- #
163
- def warning
164
- populate
165
- @results[:warning]
166
- end
167
-
168
- # The query result hash from Riddle.
169
- #
170
- # @return [Hash] Raw Sphinx results
171
- #
172
- def results
173
- populate
174
- @results
175
- end
176
-
177
- def method_missing(method, *args, &block)
178
- if is_scope?(method)
179
- add_scope(method, *args, &block)
180
- return self
181
- elsif method == :search_count
182
- merge_search one_class.search(*args), self.args, options
183
- return scoped_count
184
- elsif method.to_s[/^each_with_.*/].nil? && !@array.respond_to?(method)
185
- super
186
- elsif !SafeMethods.include?(method.to_s)
187
- populate
188
- end
189
-
190
- if method.to_s[/^each_with_.*/] && !@array.respond_to?(method)
191
- each_with_attribute method.to_s.gsub(/^each_with_/, ''), &block
192
- else
193
- @array.send(method, *args, &block)
194
- end
195
- end
196
-
197
- # Returns true if the Search object or the underlying Array object respond
198
- # to the requested method.
199
- #
200
- # @param [Symbol] method The method name
201
- # @return [Boolean] true if either Search or Array responds to the method.
202
- #
203
- def respond_to?(method, include_private = false)
204
- super || @array.respond_to?(method, include_private)
205
- end
206
-
207
- # The current page number of the result set. Defaults to 1 if no page was
208
- # explicitly requested.
209
- #
210
- # @return [Integer]
211
- #
212
- def current_page
213
- @options[:page].blank? ? 1 : @options[:page].to_i
214
- end
215
-
216
- def first_page?
217
- current_page == 1
218
- end
219
-
220
- # Kaminari support
221
- def page(page_number)
222
- @options[:page] = page_number
223
- self
224
- end
225
-
226
- # The next page number of the result set. If there are no more pages
227
- # available, nil is returned.
228
- #
229
- # @return [Integer, nil]
230
- #
231
- def next_page
232
- current_page >= total_pages ? nil : current_page + 1
233
- end
234
-
235
- def next_page?
236
- !next_page.nil?
237
- end
238
-
239
- def last_page?
240
- next_page.nil?
241
- end
242
-
243
- # The previous page number of the result set. If this is the first page,
244
- # then nil is returned.
245
- #
246
- # @return [Integer, nil]
247
- #
248
- def previous_page
249
- current_page == 1 ? nil : current_page - 1
250
- end
251
-
252
- # The amount of records per set of paged results. Defaults to 20 unless a
253
- # specific page size is requested.
254
- #
255
- # @return [Integer]
256
- #
257
- def per_page
258
- @options[:limit] ||= @options[:per_page]
259
- @options[:limit] ||= 20
260
- @options[:limit].to_i
261
- end
262
- # Kaminari support
263
- alias_method :limit_value, :per_page
264
-
265
- # Kaminari support
266
- def per(limit)
267
- @options[:limit] = limit
268
- self
269
- end
270
-
271
- # The total number of pages available if the results are paginated.
272
- #
273
- # @return [Integer]
274
- #
275
- def total_pages
276
- populate
277
- return 0 if @results.nil? || @results[:total].nil?
278
-
279
- @total_pages ||= (@results[:total] / per_page.to_f).ceil
280
- end
281
- # Compatibility with kaminari and older versions of will_paginate
282
- alias_method :page_count, :total_pages
283
- alias_method :num_pages, :total_pages
284
-
285
- # Query time taken
286
- #
287
- # @return [Integer]
288
- #
289
- def query_time
290
- populate
291
- return 0 if @results[:time].nil?
292
-
293
- @query_time ||= @results[:time]
294
- end
295
-
296
- # The total number of search results available.
297
- #
298
- # @return [Integer]
299
- #
300
- def total_entries
301
- populate
302
- return 0 if @results.nil? || @results[:total_found].nil?
303
-
304
- @total_entries ||= @results[:total_found]
305
- end
306
-
307
- # Compatibility with kaminari
308
- alias_method :total_count, :total_entries
309
-
310
- # The current page's offset, based on the number of records per page.
311
- # Or explicit :offset if given.
312
- #
313
- # @return [Integer]
314
- #
315
- def offset
316
- @options[:offset] || ((current_page - 1) * per_page)
317
- end
318
-
319
- alias_method :offset_value, :offset
320
-
321
- def indexes
322
- return options[:index] if options[:index]
323
- return '*' if classes.empty?
324
-
325
- classes.collect { |klass|
326
- klass.sphinx_index_names
327
- }.flatten.uniq.join(',')
328
- end
329
-
330
- def each_with_groupby_and_count(&block)
331
- populate
332
- results[:matches].each_with_index do |match, index|
333
- yield self[index],
334
- match[:attributes]["@groupby"],
335
- match[:attributes]["@count"]
336
- end
337
- end
338
- alias_method :each_with_group_and_count, :each_with_groupby_and_count
339
-
340
- def each_with_weighting(&block)
341
- populate
342
- results[:matches].each_with_index do |match, index|
343
- yield self[index], match[:weight]
344
- end
345
- end
346
-
347
- def each_with_match(&block)
348
- populate
349
- results[:matches].each_with_index do |match, index|
350
- yield self[index], match
351
- end
352
- end
353
-
354
- def excerpt_for(string, model = nil)
355
- if model.nil? && one_class
356
- model ||= one_class
357
- end
358
-
359
- populate
360
-
361
- index = options[:index] || "#{model.core_index_names.first}"
362
- take_client do |client|
363
- client.excerpts(
364
- {
365
- :docs => [string.to_s],
366
- :words => query,
367
- :index => index.split(',').first.strip
368
- }.merge(options[:excerpt_options] || {})
369
- ).first
370
- end
371
- end
372
-
373
- def search(*args)
374
- args << args.extract_options!.merge(:ignore_default => true)
375
- merge_search ThinkingSphinx::Search.new(*args), self.args, options
376
- self
377
- end
378
-
379
- def search_for_ids(*args)
380
- args << args.extract_options!.merge(
381
- :ignore_default => true,
382
- :ids_only => true
383
- )
384
- merge_search ThinkingSphinx::Search.new(*args), self.args, options
385
- self
386
- end
387
-
388
- def facets(*args)
389
- options = args.extract_options!
390
- merge_search self, args, options
391
- args << options
392
-
393
- ThinkingSphinx::FacetSearch.new(*args)
394
- end
395
-
396
- def take_client
397
- if options[:client]
398
- prepare options[:client]
399
- yield options[:client]
400
- else
401
- ThinkingSphinx::Connection.take do |client|
402
- prepare client
403
- yield client
404
- end
405
- end
406
- end
407
-
408
- def append_to(client)
409
- prepare client
410
- client.append_query query, indexes, comment
411
- client.reset
412
- end
413
-
414
- def populate_from_queue(results)
415
- return if @populated
416
- @populated = true
417
- @results = results
418
-
419
- compose_results
420
- end
421
-
422
- private
1
+ class ThinkingSphinx::Search < Array
2
+ CORE_METHODS = %w( == class class_eval extend frozen? id instance_eval
3
+ instance_of? instance_values instance_variable_defined?
4
+ instance_variable_get instance_variable_set instance_variables is_a?
5
+ kind_of? member? method methods nil? object_id respond_to?
6
+ respond_to_missing? send should should_not type )
7
+ SAFE_METHODS = %w( partition private_methods protected_methods public_methods
8
+ send class )
9
+ DEFAULT_MASKS = [
10
+ ThinkingSphinx::Masks::PaginationMask,
11
+ ThinkingSphinx::Masks::ScopesMask
12
+ ]
13
+
14
+ instance_methods.select { |method|
15
+ method.to_s[/^__/].nil? && !CORE_METHODS.include?(method.to_s)
16
+ }.each { |method|
17
+ undef_method method
18
+ }
19
+
20
+ attr_reader :options, :masks
21
+ attr_accessor :query
22
+
23
+ def initialize(query = nil, options = {})
24
+ query, options = nil, query if query.is_a?(Hash)
25
+ @query, @options = query, options
26
+ @masks = @options.delete(:masks) || DEFAULT_MASKS
27
+ @middleware = @options.delete(:middleware)
28
+
29
+ populate if options[:populate]
30
+ end
423
31
 
424
- def config
32
+ def context
33
+ @context ||= ThinkingSphinx::Search::Context.new self,
425
34
  ThinkingSphinx::Configuration.instance
426
- end
427
-
428
- def populate
429
- return if @populated
430
- @populated = true
431
- retries = hard_retries
432
-
433
- begin
434
- retry_on_stale_index do
435
- begin
436
- @results = nil
437
- log query do
438
- take_client do |client|
439
- @results = client.query query, indexes, comment
440
- end
441
- end
442
- total = @results[:total_found].to_i
443
- log "Found #{total} result#{'s' unless total == 1}"
444
- log "Sphinx Daemon returned warning: #{warning}" if warning?
445
-
446
- if error?
447
- log "Sphinx Daemon returned error: #{error}"
448
- raise SphinxError.new(error, @results) unless options[:ignore_errors]
449
- end
450
- rescue Errno::ECONNREFUSED => err
451
- raise ThinkingSphinx::ConnectionError,
452
- 'Connection to Sphinx Daemon (searchd) failed.'
453
- end
454
-
455
- compose_results
456
- end
457
- rescue => e
458
- log 'Caught Sphinx exception: %s (%s %s left)' % [
459
- e.message, retries, (retries == 1 ? 'try' : 'tries')
460
- ]
461
- retries -= 1
462
- if retries >= 0
463
- retry
464
- else
465
- raise e
466
- end
467
- end
468
- end
469
-
470
- def compose_results
471
- if options[:ids_only]
472
- compose_ids_results
473
- elsif options[:attributes_only]
474
- compose_attributes_results
475
- elsif options[:only]
476
- compose_only_results
477
- else
478
- replace instances_from_matches
479
- add_excerpter
480
- add_sphinx_attributes
481
- add_matching_fields if options[:rank_mode] == :fieldmask
482
- end
483
- end
484
-
485
- def compose_ids_results
486
- replace @results[:matches].collect { |match|
487
- match[:attributes]['sphinx_internal_id']
488
- }
489
- end
490
-
491
- def compose_attributes_results
492
- replace @results[:matches].collect { |match|
493
- attributes = {}
494
- match[:attributes].each do |name, value|
495
- attributes[name.to_sym] = match[:attributes][name]
496
- end
497
- attributes
498
- }
499
- end
500
-
501
- def compose_only_results
502
- replace @results[:matches].collect { |match|
503
- case only = options[:only]
504
- when String, Symbol
505
- match[:attributes][only.to_s]
506
- when Array
507
- only.inject({}) do |hash, attribute|
508
- hash[attribute.to_sym] = match[:attributes][attribute.to_s]
509
- hash
510
- end
511
- else
512
- raise "Unexpected object for :only argument. String or Array is expected, #{only.class} was received."
513
- end
514
- }
515
- end
516
-
517
- def add_excerpter
518
- each do |object|
519
- next if object.nil?
520
-
521
- object.excerpts = ThinkingSphinx::Excerpter.new self, object
522
- end
523
- end
524
-
525
- def add_sphinx_attributes
526
- each do |object|
527
- next if object.nil?
528
-
529
- match = match_hash object
530
- next if match.nil?
531
-
532
- object.sphinx_attributes = match[:attributes]
533
- end
534
- end
535
-
536
- def add_matching_fields
537
- each do |object|
538
- next if object.nil?
539
-
540
- match = match_hash object
541
- next if match.nil?
542
- object.matching_fields = ThinkingSphinx::Search.matching_fields(
543
- @results[:fields], match[:weight]
544
- )
545
- end
546
- end
547
-
548
- def match_hash(object)
549
- @results[:matches].detect { |match|
550
- class_crc = object.class.name
551
- class_crc = object.class.to_crc32 if Riddle.loaded_version.to_i < 2
552
-
553
- match[:attributes]['sphinx_internal_id'] == object.
554
- primary_key_for_sphinx &&
555
- match[:attributes][crc_attribute] == class_crc
556
- }
557
- end
558
-
559
- def self.log(message, &block)
560
- if ThinkingSphinx::ActiveRecord::LogSubscriber.logger.nil?
561
- yield if block_given?
562
- return
563
- end
564
-
565
- if block_given?
566
- ::ActiveSupport::Notifications.
567
- instrument('query.thinking_sphinx', :query => message, &block)
568
- else
569
- ::ActiveSupport::Notifications.
570
- instrument('message.thinking_sphinx', :message => message)
571
- end
572
- end
573
-
574
- def log(query, &block)
575
- self.class.log(query, &block)
576
- end
577
-
578
- def prepare(client)
579
- index_options = {}
580
- if one_class && one_class.sphinx_indexes && one_class.sphinx_indexes.first
581
- index_options = one_class.sphinx_indexes.first.local_options
582
- end
583
-
584
- [
585
- :max_matches, :group_by, :group_function, :group_clause,
586
- :group_distinct, :id_range, :cut_off, :retry_count, :retry_delay,
587
- :rank_mode, :rank_expr, :max_query_time, :field_weights
588
- ].each do |key|
589
- value = options[key] || index_options[key]
590
- client.send("#{key}=", value) if value
591
- end
592
-
593
- # treated non-standard as :select is already used for AR queries
594
- client.select = options[:sphinx_select] || '*'
595
-
596
- client.limit = per_page
597
- client.offset = offset
598
- client.match_mode = match_mode
599
- client.filters = filters
600
- client.sort_mode = sort_mode
601
- client.sort_by = sort_by
602
- client.group_by = group_by if group_by
603
- client.group_function = group_function if group_function
604
- client.index_weights = index_weights
605
- client.anchor = anchor
606
-
607
- client
608
- end
609
-
610
- def retry_on_stale_index(&block)
611
- stale_ids = []
612
- retries = stale_retries
613
-
614
- begin
615
- options[:raise_on_stale] = retries > 0
616
- block.call
617
-
618
- # If ThinkingSphinx::Search#instances_from_matches found records in
619
- # Sphinx but not in the DB and the :raise_on_stale option is set, this
620
- # exception is raised. We retry a limited number of times, excluding the
621
- # stale ids from the search.
622
- rescue StaleIdsException => err
623
- retries -= 1
624
-
625
- # For logging
626
- stale_ids |= err.ids
627
- # ID exclusion
628
- options[:without_ids] = Array(options[:without_ids]) | err.ids
629
-
630
- log 'Stale Ids (%s %s left): %s' % [
631
- retries, (retries == 1 ? 'try' : 'tries'), stale_ids.join(', ')
632
- ]
633
- retry
634
- end
635
- end
636
-
637
- def classes
638
- @classes ||= options[:classes] || []
639
- end
640
-
641
- def one_class
642
- @one_class ||= classes.length != 1 ? nil : classes.first
643
- end
644
-
645
- def query
646
- @query ||= begin
647
- q = @args.join(' ') << conditions_as_query
648
- (options[:star] ? star_query(q) : q).strip
649
- end
650
- end
651
-
652
- def conditions_as_query
653
- return '' if @options[:conditions].blank?
654
-
655
- ' ' + @options[:conditions].keys.collect { |key|
656
- search_key = key.is_a?(::Array) ? "(#{key.join(',')})" : key
657
- "@#{search_key} #{options[:conditions][key]}"
658
- }.join(' ')
659
- end
660
-
661
- def star_query(query)
662
- token = options[:star].is_a?(Regexp) ? options[:star] : default_star_token
663
-
664
- query.gsub(/("#{token}(.*?#{token})?"|(?![!-])#{token})/u) do
665
- pre, proper, post = $`, $&, $'
666
- # E.g. "@foo", "/2", "~3", but not as part of a token
667
- is_operator = pre.match(%r{(\W|^)[@~/]\Z}) ||
668
- pre.match(%r{(\W|^)@\([^\)]*$})
669
- # E.g. "foo bar", with quotes
670
- is_quote = proper.starts_with?('"') && proper.ends_with?('"')
671
- has_star = pre.ends_with?("*") || post.starts_with?("*")
672
- if is_operator || is_quote || has_star
673
- proper
674
- else
675
- "*#{proper}*"
676
- end
677
- end
678
- end
679
-
680
- if Regexp.instance_methods.include?(:encoding)
681
- DefaultStarToken = Regexp.new('\p{Word}+')
682
- else
683
- DefaultStarToken = Regexp.new('\w+', nil, 'u')
684
- end
685
-
686
- def default_star_token
687
- DefaultStarToken
688
- end
689
-
690
- def comment
691
- options[:comment] || ''
692
- end
693
-
694
- def match_mode
695
- options[:match_mode] || (options[:conditions].blank? ? :all : :extended)
696
- end
697
-
698
- def sort_mode
699
- @sort_mode ||= case options[:sort_mode]
700
- when :asc
701
- :attr_asc
702
- when :desc
703
- :attr_desc
704
- when nil
705
- case options[:order]
706
- when String
707
- :extended
708
- when Symbol
709
- :attr_asc
710
- else
711
- :relevance
712
- end
713
- else
714
- options[:sort_mode]
715
- end
716
- end
717
-
718
- def sort_by
719
- case @sort_by = (options[:sort_by] || options[:order])
720
- when String
721
- sorted_fields_to_attributes(@sort_by.clone)
722
- when Symbol
723
- field_names.include?(@sort_by) ?
724
- @sort_by.to_s.concat('_sort') : @sort_by.to_s
725
- else
726
- ''
727
- end
728
- end
729
-
730
- def field_names
731
- return [] unless one_class
732
-
733
- one_class.sphinx_indexes.collect { |index|
734
- index.fields.collect { |field| field.unique_name }
735
- }.flatten
736
- end
737
-
738
- def sorted_fields_to_attributes(order_string)
739
- field_names.each { |field|
740
- order_string.gsub!(/(^|\s)#{field}(,?\s|$)/) { |match|
741
- match.gsub field.to_s, field.to_s.concat("_sort")
742
- }
743
- }
744
-
745
- order_string
746
- end
747
-
748
- # Turn :index_weights => { "foo" => 2, User => 1 } into :index_weights =>
749
- # { "foo" => 2, "user_core" => 1, "user_delta" => 1 }
750
- #
751
- def index_weights
752
- weights = options[:index_weights] || {}
753
- weights.keys.inject({}) do |hash, key|
754
- if key.is_a?(Class)
755
- name = ThinkingSphinx::Index.name_for(key)
756
- hash["#{name}_core"] = weights[key]
757
- hash["#{name}_delta"] = weights[key]
758
- else
759
- hash[key] = weights[key]
760
- end
761
-
762
- hash
763
- end
764
- end
765
-
766
- def group_by
767
- options[:group] ? options[:group].to_s : nil
768
- end
769
-
770
- def group_function
771
- options[:group] ? :attr : nil
772
- end
773
-
774
- def internal_filters
775
- filters = [Riddle::Client::Filter.new('sphinx_deleted', [0])]
776
-
777
- class_crcs = classes.collect { |klass|
778
- klass.to_crc32s
779
- }.flatten
780
-
781
- unless class_crcs.empty?
782
- filters << Riddle::Client::Filter.new('class_crc', class_crcs)
783
- end
784
-
785
- filters << Riddle::Client::Filter.new(
786
- 'sphinx_internal_id', filter_value(options[:without_ids]), true
787
- ) unless options[:without_ids].nil? || options[:without_ids].empty?
788
-
789
- filters
790
- end
791
-
792
- def filters
793
- internal_filters +
794
- (options[:with] || {}).collect { |attrib, value|
795
- Riddle::Client::Filter.new attrib.to_s, filter_value(value)
796
- } +
797
- (options[:without] || {}).collect { |attrib, value|
798
- Riddle::Client::Filter.new attrib.to_s, filter_value(value), true
799
- } +
800
- (options[:with_all] || {}).collect { |attrib, values|
801
- Array(values).collect { |value|
802
- Riddle::Client::Filter.new attrib.to_s, filter_value(value)
803
- }
804
- }.flatten +
805
- (options[:without_any] || {}).collect { |attrib, values|
806
- Array(values).collect { |value|
807
- Riddle::Client::Filter.new attrib.to_s, filter_value(value), true
808
- }
809
- }.flatten
810
- end
811
-
812
- # When passed a Time instance, returns the integer timestamp.
813
- def filter_value(value)
814
- case value
815
- when Range
816
- filter_value(value.first).first..filter_value(value.last).first
817
- when Array
818
- value.collect { |v| filter_value(v) }.flatten
819
- when Time
820
- [value.to_i]
821
- when Date
822
- [Time.utc(value.year, value.month, value.day).to_i]
823
- when NilClass
824
- 0
825
- else
826
- Array(value)
827
- end
828
- end
829
-
830
- def anchor
831
- return {} unless options[:geo] || (options[:lat] && options[:lng])
832
-
833
- {
834
- :latitude => options[:geo] ? options[:geo].first : options[:lat],
835
- :longitude => options[:geo] ? options[:geo].last : options[:lng],
836
- :latitude_attribute => latitude_attr.to_s,
837
- :longitude_attribute => longitude_attr.to_s
838
- }
839
- end
840
-
841
- def latitude_attr
842
- options[:latitude_attr] ||
843
- index_option(:latitude_attr) ||
844
- attribute(:lat, :latitude)
845
- end
846
-
847
- def longitude_attr
848
- options[:longitude_attr] ||
849
- index_option(:longitude_attr) ||
850
- attribute(:lon, :lng, :longitude)
851
- end
35
+ end
852
36
 
853
- def index_option(key)
854
- return nil unless one_class
37
+ def meta
38
+ populate
39
+ context[:meta]
40
+ end
855
41
 
856
- one_class.sphinx_indexes.collect { |index|
857
- index.local_options[key]
858
- }.compact.first
859
- end
42
+ def offset
43
+ @options[:offset] || ((current_page - 1) * per_page)
44
+ end
860
45
 
861
- def attribute(*keys)
862
- return nil unless one_class
46
+ alias_method :offset_value, :offset
863
47
 
864
- keys.detect { |key|
865
- attributes.include?(key)
866
- }
867
- end
48
+ def per_page
49
+ @options[:limit] ||= (@options[:per_page] || 20)
50
+ @options[:limit].to_i
51
+ end
868
52
 
869
- def attributes
870
- return [] unless one_class
53
+ alias_method :limit_value, :per_page
871
54
 
872
- attributes = one_class.sphinx_indexes.collect { |index|
873
- index.attributes.collect { |attrib| attrib.unique_name }
874
- }.flatten
875
- end
55
+ def populate
56
+ return self if @populated
876
57
 
877
- def stale_retries
878
- case options[:retry_stale]
879
- when TrueClass
880
- 3
881
- when nil, FalseClass
882
- 0
883
- else
884
- options[:retry_stale].to_i
885
- end
886
- end
887
-
888
- def hard_retries
889
- options[:hard_retry_count] || config.hard_retry_count
890
- end
58
+ middleware.call [context]
59
+ @populated = true
891
60
 
892
- def include_for_class(klass)
893
- includes = options[:include] || klass.sphinx_index_options[:include]
894
-
895
- case includes
896
- when NilClass
897
- nil
898
- when Array
899
- include_from_array includes, klass
900
- when Symbol
901
- klass.reflections[includes].nil? ? nil : includes
902
- when Hash
903
- include_from_hash includes, klass
904
- else
905
- includes
906
- end
907
- end
61
+ self
62
+ end
908
63
 
909
- def include_from_array(array, klass)
910
- scoped_array = []
911
- array.each do |value|
912
- case value
913
- when Hash
914
- scoped_hash = include_from_hash(value, klass)
915
- scoped_array << scoped_hash unless scoped_hash.nil?
916
- else
917
- scoped_array << value unless klass.reflections[value].nil?
918
- end
919
- end
920
- scoped_array.empty? ? nil : scoped_array
921
- end
64
+ def populated!
65
+ @populated = true
66
+ end
922
67
 
923
- def include_from_hash(hash, klass)
924
- scoped_hash = {}
925
- hash.keys.each do |key|
926
- scoped_hash[key] = hash[key] unless klass.reflections[key].nil?
927
- end
928
- scoped_hash.empty? ? nil : scoped_hash
929
- end
68
+ def raw
69
+ populate
70
+ context[:raw]
71
+ end
930
72
 
931
- def instances_from_class(klass, matches)
932
- index_options = klass.sphinx_index_options
73
+ def respond_to?(method, include_private = false)
74
+ super || context[:results].respond_to?(method, include_private)
75
+ end
933
76
 
934
- ids = matches.collect { |match| match[:attributes]["sphinx_internal_id"] }
935
- instances = ids.length > 0 ? klass.unscoped.find(
936
- :all,
937
- :joins => options[:joins],
938
- :conditions => {klass.primary_key_for_sphinx.to_sym => ids},
939
- :include => include_for_class(klass),
940
- :select => (options[:select] || index_options[:select]),
941
- :order => (options[:sql_order] || index_options[:sql_order])
942
- ) : []
77
+ def to_a
78
+ populate
79
+ context[:results].collect { |result|
80
+ result.respond_to?(:unglazed) ? result.unglazed : result
81
+ }
82
+ end
943
83
 
944
- # Raise an exception if we find records in Sphinx but not in the DB, so
945
- # the search method can retry without them. See
946
- # ThinkingSphinx::Search.retry_search_on_stale_index.
947
- if options[:raise_on_stale] && instances.length < ids.length
948
- stale_ids = ids - instances.map { |i| i.id }
949
- raise StaleIdsException, stale_ids
950
- end
84
+ private
951
85
 
952
- # if the user has specified an SQL order, return the collection
953
- # without rearranging it into the Sphinx order
954
- return instances if (options[:sql_order] || index_options[:sql_order])
86
+ def mask_stack
87
+ @mask_stack ||= masks.collect { |klass| klass.new self }
88
+ end
955
89
 
956
- ids.collect { |obj_id|
957
- instances.detect do |obj|
958
- obj.primary_key_for_sphinx == obj_id
959
- end
960
- }
90
+ def method_missing(method, *args, &block)
91
+ mask_stack.each do |mask|
92
+ return mask.send(method, *args, &block) if mask.respond_to?(method)
961
93
  end
962
94
 
963
- # Group results by class and call #find(:all) once for each group to reduce
964
- # the number of #find's in multi-model searches.
965
- #
966
- def instances_from_matches
967
- return single_class_results if one_class
95
+ populate if !SAFE_METHODS.include?(method.to_s)
968
96
 
969
- groups = results[:matches].group_by { |match|
970
- match[:attributes][crc_attribute]
971
- }
972
- groups.each do |crc, group|
973
- group.replace(
974
- instances_from_class(class_from_crc(crc), group)
975
- )
976
- end
977
-
978
- results[:matches].collect do |match|
979
- groups.detect { |crc, group|
980
- crc == match[:attributes][crc_attribute]
981
- }[1].compact.detect { |obj|
982
- obj.primary_key_for_sphinx == match[:attributes]["sphinx_internal_id"]
983
- }
984
- end
985
- end
986
-
987
- def single_class_results
988
- instances_from_class one_class, results[:matches]
989
- end
97
+ context[:results].send(method, *args, &block)
98
+ end
990
99
 
991
- def class_from_crc(crc)
992
- if Riddle.loaded_version.to_i < 2
993
- config.models_by_crc[crc].constantize
100
+ def middleware
101
+ @middleware ||= begin
102
+ if options[:ids_only]
103
+ ThinkingSphinx::Middlewares::IDS_ONLY
994
104
  else
995
- crc.constantize
996
- end
997
- end
998
-
999
- def each_with_attribute(attribute, &block)
1000
- populate
1001
- results[:matches].each_with_index do |match, index|
1002
- yield self[index],
1003
- (match[:attributes][attribute] || match[:attributes]["@#{attribute}"])
105
+ ThinkingSphinx::Middlewares::DEFAULT
1004
106
  end
1005
107
  end
1006
-
1007
- def is_scope?(method)
1008
- one_class && one_class.sphinx_scopes.include?(method)
1009
- end
1010
-
1011
- # Adds the default_sphinx_scope if set.
1012
- def add_default_scope
1013
- return unless one_class && one_class.has_default_sphinx_scope?
1014
- add_scope(one_class.get_default_sphinx_scope.to_sym)
1015
- end
1016
-
1017
- def add_scope(method, *args, &block)
1018
- method = "#{method}_without_default".to_sym
1019
- merge_search one_class.send(method, *args, &block), self.args, options
1020
- end
1021
-
1022
- def merge_search(search, args, options)
1023
- search.args.each { |arg| args << arg }
1024
-
1025
- search.options.keys.each do |key|
1026
- if HashOptions.include?(key)
1027
- options[key] ||= {}
1028
- options[key].merge! search.options[key]
1029
- elsif ArrayOptions.include?(key)
1030
- options[key] ||= []
1031
- options[key] += search.options[key]
1032
- options[key].uniq!
1033
- else
1034
- options[key] = search.options[key]
1035
- end
1036
- end
1037
- end
1038
-
1039
- def scoped_count
1040
- return self.total_entries if(@options[:ids_only] || @options[:only])
1041
-
1042
- @options[:ids_only] = true
1043
- results_count = self.total_entries
1044
- @options[:ids_only] = false
1045
- @populated = false
1046
-
1047
- results_count
1048
- end
1049
-
1050
- def crc_attribute
1051
- Riddle.loaded_version.to_i < 2 ? 'class_crc' : 'sphinx_internal_class'
1052
- end
1053
108
  end
1054
109
  end
110
+
111
+ require 'thinking_sphinx/search/batch_inquirer'
112
+ require 'thinking_sphinx/search/context'
113
+ require 'thinking_sphinx/search/glaze'
114
+ require 'thinking_sphinx/search/merger'
115
+ require 'thinking_sphinx/search/query'
116
+ require 'thinking_sphinx/search/stale_ids_exception'