rhoconnect 3.4.5 → 4.0.0.beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (337) hide show
  1. data/CHANGELOG.md +57 -3
  2. data/Gemfile +9 -7
  3. data/Gemfile.lock +37 -37
  4. data/Rakefile +18 -7
  5. data/bench/benchapp/Gemfile +1 -1
  6. data/bench/benchapp/config.ru +0 -3
  7. data/bench/benchapp/controllers/ruby/application.rb +17 -0
  8. data/bench/benchapp/controllers/ruby/application_controller.rb +17 -0
  9. data/bench/benchapp/controllers/ruby/mock_adapter_controller.rb +8 -0
  10. data/bench/benchapp/controllers/ruby/queue_mock_adapter_controller.rb +8 -0
  11. data/bench/benchapp/{sources → models/ruby}/mock_adapter.rb +1 -1
  12. data/bench/benchapp/{sources → models/ruby}/queue_mock_adapter.rb +0 -0
  13. data/bench/benchapp/spec/{sources → models/ruby}/mock_adapter_spec.rb +1 -1
  14. data/bench/benchapp/spec/{sources → models/ruby}/queue_mock_adapter_spec.rb +1 -1
  15. data/bench/benchapp/spec/spec_helper.rb +2 -2
  16. data/bench/blobapp/Gemfile +1 -1
  17. data/bench/blobapp/config.ru +0 -3
  18. data/bench/blobapp/controllers/ruby/application_controller.rb +17 -0
  19. data/bench/blobapp/controllers/ruby/blob_adapter_controller.rb +8 -0
  20. data/bench/blobapp/{sources → models/ruby}/blob_adapter.rb +9 -2
  21. data/bench/blobapp/spec/{sources → models/ruby}/blob_adapter_spec.rb +1 -1
  22. data/bench/blobapp/spec/spec_helper.rb +1 -1
  23. data/bench/lib/bench/cli.rb +1 -1
  24. data/bench/scripts/blob_cud_script.rb +1 -1
  25. data/bench/scripts/query_md_script.rb +1 -1
  26. data/bench/scripts/query_only_script.rb +1 -1
  27. data/bench/scripts/query_script.rb +1 -1
  28. data/bench/scripts/test_query_script.rb +7 -1
  29. data/bench/spec/mock_adapter_spec.rb +1 -1
  30. data/bench/spec/result_spec.rb +3 -3
  31. data/bin/rhoconnect +5 -3
  32. data/commands/dtach/dtach_install.rb +2 -2
  33. data/commands/execute.rb +8 -3
  34. data/commands/generators/app.rb +3 -3
  35. data/commands/generators/controller.rb +6 -0
  36. data/commands/generators/model.rb +6 -0
  37. data/commands/generators/source.rb +3 -3
  38. data/commands/generators/update.rb +1 -1
  39. data/commands/redis/redis_about.rb +2 -2
  40. data/commands/redis/redis_download.rb +1 -1
  41. data/commands/redis/redis_install.rb +4 -3
  42. data/commands/redis/redis_restart.rb +4 -4
  43. data/commands/redis/redis_start.rb +5 -4
  44. data/commands/redis/redis_startbg.rb +5 -4
  45. data/commands/redis/redis_status.rb +13 -0
  46. data/commands/redis/redis_stop.rb +3 -3
  47. data/commands/rhoconnect/config.rb +28 -16
  48. data/commands/rhoconnect/flushdb.rb +1 -2
  49. data/commands/rhoconnect/get_token.rb +15 -11
  50. data/commands/rhoconnect/restart.rb +13 -5
  51. data/commands/rhoconnect/set_admin_password.rb +8 -8
  52. data/commands/rhoconnect/start.rb +74 -16
  53. data/commands/rhoconnect/startbg.rb +1 -1
  54. data/commands/rhoconnect/startdebug.rb +1 -1
  55. data/commands/rhoconnect/stop.rb +13 -1
  56. data/commands/rhoconnect/web.rb +5 -5
  57. data/commands/rhoconnect_console/console.rb +7 -5
  58. data/commands/{rhoconnect → rhoconnect_spec}/spec.rb +0 -0
  59. data/commands/rhoconnect_war/war.rb +9 -9
  60. data/commands/utilities/blank_app.ru +56 -0
  61. data/commands/utilities/redis_runner.rb +54 -19
  62. data/doc/authentication.txt +80 -6
  63. data/doc/blob-sync.txt +104 -97
  64. data/doc/bulk-sync.txt +1 -1
  65. data/doc/client-java.txt +3 -3
  66. data/doc/client-objc.txt +2 -2
  67. data/doc/client.txt +4 -4
  68. data/doc/command-line.txt +105 -200
  69. data/doc/data-partitioning.txt +40 -0
  70. data/doc/deploying.txt +249 -77
  71. data/doc/extending-rhoconnect-server.txt +40 -57
  72. data/doc/heroku-addon.txt +2 -0
  73. data/doc/install.txt +45 -95
  74. data/doc/introduction.txt +1 -1
  75. data/doc/java-plugin.txt +365 -190
  76. data/doc/metadata.txt +1 -1
  77. data/doc/migration.txt +108 -142
  78. data/doc/preparing-production.txt +1 -1
  79. data/doc/push-backend-setup.txt +2 -0
  80. data/doc/push-client-setup-android.txt +78 -0
  81. data/doc/push-client-setup-bb.txt +81 -0
  82. data/doc/push-client-setup-ios.txt +70 -0
  83. data/doc/push-client-setup-rps.txt +200 -0
  84. data/doc/push-client-setup.txt +63 -66
  85. data/doc/push-server-setup.txt +67 -40
  86. data/doc/push-testing.txt +29 -0
  87. data/doc/push.txt +21 -6
  88. data/doc/rest-api.txt +128 -55
  89. data/doc/rhoconnect-redis-stack.txt +120 -0
  90. data/doc/settings.txt +4 -12
  91. data/doc/source-adapters-intro.txt +28 -0
  92. data/doc/source-adapters.txt +235 -272
  93. data/doc/stats-middleware.txt +9 -29
  94. data/doc/supported-platforms.txt +21 -30
  95. data/doc/testing.txt +40 -42
  96. data/doc/tutorial.txt +72 -57
  97. data/examples/simple/Gemfile +1 -1
  98. data/examples/simple/application.rb +4 -5
  99. data/examples/simple/my_server.rb +2 -2
  100. data/examples/simple/settings/settings.yml +1 -1
  101. data/generators/rhoconnect.rb +151 -50
  102. data/generators/templates/application/Gemfile +1 -1
  103. data/generators/templates/application/Rakefile +3 -3
  104. data/generators/templates/application/config.ru +1 -4
  105. data/generators/templates/application/controllers/application_controller.rb +17 -0
  106. data/generators/templates/application/controllers/js/application_controller.js +14 -0
  107. data/generators/templates/application/controllers/ruby/application_controller.rb +17 -0
  108. data/generators/templates/application/package.json +8 -0
  109. data/generators/templates/application/rcgemfile +2 -5
  110. data/generators/templates/application/settings/settings.yml +3 -3
  111. data/generators/templates/application/spec/application_controller_spec.rb +23 -0
  112. data/generators/templates/application/spec/js_spec.rb +25 -0
  113. data/generators/templates/application/spec/spec_helper.rb +21 -7
  114. data/generators/templates/source/controllers/js/controller.js +7 -0
  115. data/generators/templates/source/controllers/ruby/controller.rb +8 -0
  116. data/generators/templates/source/controllers/ruby/controller_spec.rb +27 -0
  117. data/generators/templates/source/models/js/model.js +46 -0
  118. data/generators/templates/source/{source_adapter.rb → models/ruby/model.rb} +15 -10
  119. data/generators/templates/source/{source_spec.rb → models/ruby/model_spec.rb} +1 -1
  120. data/install.sh +5 -5
  121. data/installer/unix-like/create_texts.rb +2 -2
  122. data/installer/unix-like/rho_connect_install_constants.rb +2 -2
  123. data/installer/unix-like/rho_connect_install_utilities.rb +1 -1
  124. data/installer/utils/constants.rb +4 -4
  125. data/js-adapters/ballroom.js +216 -0
  126. data/js-adapters/node.rb +52 -0
  127. data/js-adapters/node_channel.rb +181 -0
  128. data/js-adapters/request.js +27 -0
  129. data/js-adapters/response.js +57 -0
  130. data/js-adapters/rhoconnect_helpers.js +60 -0
  131. data/js-adapters/router.js +60 -0
  132. data/js-adapters/server.js +5 -0
  133. data/lib/rhoconnect/api/app/ans_login.rb +3 -3
  134. data/lib/rhoconnect/api/app/bulk_data.rb +10 -10
  135. data/lib/rhoconnect/api/app/fast_delete.rb +11 -10
  136. data/lib/rhoconnect/api/app/fast_insert.rb +11 -10
  137. data/lib/rhoconnect/api/app/fast_update.rb +11 -10
  138. data/lib/rhoconnect/api/app/login.rb +5 -5
  139. data/lib/rhoconnect/api/app/push_deletes.rb +12 -11
  140. data/lib/rhoconnect/api/app/push_objects.rb +12 -11
  141. data/lib/rhoconnect/api/app/query.rb +8 -7
  142. data/lib/rhoconnect/api/app/queue_updates.rb +98 -94
  143. data/lib/rhoconnect/api/app/search.rb +8 -7
  144. data/lib/rhoconnect/api/client/client_get_db_doc.rb +5 -5
  145. data/lib/rhoconnect/api/client/client_set_db_doc.rb +8 -8
  146. data/lib/rhoconnect/api/client/create.rb +7 -7
  147. data/lib/rhoconnect/api/client/get_client_params.rb +4 -4
  148. data/lib/rhoconnect/api/client/list_client_docs.rb +17 -17
  149. data/lib/rhoconnect/api/client/register.rb +12 -12
  150. data/lib/rhoconnect/api/client/reset.rb +5 -5
  151. data/lib/rhoconnect/api/readstate/set_refresh_time.rb +9 -9
  152. data/lib/rhoconnect/api/source/get_source_params.rb +4 -4
  153. data/lib/rhoconnect/api/source/list_sources.rb +16 -16
  154. data/lib/rhoconnect/api/source/update_source_params.rb +6 -6
  155. data/lib/rhoconnect/api/store/get_db_doc.rb +4 -4
  156. data/lib/rhoconnect/api/store/set_db_doc.rb +7 -7
  157. data/lib/rhoconnect/api/system/get_adapter.rb +4 -4
  158. data/lib/rhoconnect/api/system/get_license_info.rb +8 -8
  159. data/lib/rhoconnect/api/system/login.rb +15 -15
  160. data/lib/rhoconnect/api/system/reset.rb +11 -11
  161. data/lib/rhoconnect/api/system/save_adapter.rb +4 -4
  162. data/lib/rhoconnect/api/system/stats.rb +22 -22
  163. data/lib/rhoconnect/api/user/create_user.rb +7 -7
  164. data/lib/rhoconnect/api/user/delete_client.rb +6 -6
  165. data/lib/rhoconnect/api/user/delete_user.rb +11 -10
  166. data/lib/rhoconnect/api/user/list_clients.rb +4 -4
  167. data/lib/rhoconnect/api/user/list_source_docs.rb +10 -10
  168. data/lib/rhoconnect/api/user/list_users.rb +3 -3
  169. data/lib/rhoconnect/api/user/ping.rb +3 -3
  170. data/lib/rhoconnect/api/user/show_user.rb +3 -3
  171. data/lib/rhoconnect/api/user/update_user.rb +5 -5
  172. data/lib/rhoconnect/api/user/user_get_db_doc.rb +5 -5
  173. data/lib/rhoconnect/api/user/user_set_db_doc.rb +10 -10
  174. data/lib/rhoconnect/api_token.rb +5 -6
  175. data/lib/rhoconnect/app.rb +6 -46
  176. data/lib/rhoconnect/application/init.rb +5 -2
  177. data/lib/rhoconnect/async.rb +76 -39
  178. data/lib/rhoconnect/bulk_data/bulk_data.rb +6 -4
  179. data/lib/rhoconnect/client.rb +59 -9
  180. data/lib/rhoconnect/condition/admin_required.rb +27 -0
  181. data/lib/rhoconnect/condition/client_required.rb +50 -0
  182. data/lib/rhoconnect/condition/login_required.rb +22 -0
  183. data/lib/rhoconnect/condition/source_required.rb +49 -0
  184. data/lib/rhoconnect/condition/verbs.rb +17 -0
  185. data/lib/rhoconnect/condition/verify_success.rb +19 -0
  186. data/lib/rhoconnect/controller/app_base.rb +74 -0
  187. data/lib/rhoconnect/controller/base.rb +68 -0
  188. data/lib/rhoconnect/controller/clients_controller.rb +79 -0
  189. data/lib/rhoconnect/controller/dynamic_adapter_controller.rb +93 -0
  190. data/lib/rhoconnect/controller/js_base.rb +124 -0
  191. data/lib/rhoconnect/controller/read_state_controller.rb +22 -0
  192. data/lib/rhoconnect/controller/source_adapter_base.rb +14 -0
  193. data/lib/rhoconnect/controller/sources_controller.rb +44 -0
  194. data/lib/rhoconnect/controller/store_controller.rb +25 -0
  195. data/lib/rhoconnect/controller/system_controller.rb +67 -0
  196. data/lib/rhoconnect/controller/users_controller.rb +99 -0
  197. data/lib/rhoconnect/db_adapter.rb +1 -3
  198. data/lib/rhoconnect/document.rb +159 -50
  199. data/lib/rhoconnect/handler/authenticate/execute_methods.rb +77 -0
  200. data/lib/rhoconnect/handler/authenticate/runner.rb +49 -0
  201. data/lib/rhoconnect/handler/authenticate.rb +3 -0
  202. data/lib/rhoconnect/handler/bulk_data.rb +28 -0
  203. data/lib/rhoconnect/handler/changes/engine.rb +271 -0
  204. data/lib/rhoconnect/handler/changes/execute_methods.rb +88 -0
  205. data/lib/rhoconnect/handler/changes/pass_through_runner.rb +11 -0
  206. data/lib/rhoconnect/handler/changes/runner.rb +53 -0
  207. data/lib/rhoconnect/handler/changes.rb +31 -0
  208. data/lib/rhoconnect/handler/helpers/auth_method.rb +29 -0
  209. data/lib/rhoconnect/handler/helpers/binding.rb +18 -0
  210. data/lib/rhoconnect/handler/helpers/bulk_data.rb +53 -0
  211. data/lib/rhoconnect/handler/helpers/source_job.rb +14 -0
  212. data/lib/rhoconnect/handler/helpers.rb +4 -0
  213. data/lib/rhoconnect/handler/plugin_callbacks/execute_methods.rb +99 -0
  214. data/lib/rhoconnect/handler/plugin_callbacks/runner.rb +28 -0
  215. data/lib/rhoconnect/handler/plugin_callbacks.rb +67 -0
  216. data/lib/rhoconnect/handler/query/engine.rb +93 -0
  217. data/lib/rhoconnect/handler/query/execute_methods.rb +21 -0
  218. data/lib/rhoconnect/handler/query/pass_through_runner.rb +35 -0
  219. data/lib/rhoconnect/handler/query/runner.rb +270 -0
  220. data/lib/rhoconnect/handler/query.rb +19 -0
  221. data/lib/rhoconnect/handler/search/engine.rb +60 -0
  222. data/lib/rhoconnect/handler/search/execute_methods.rb +32 -0
  223. data/lib/rhoconnect/handler/search/pass_through_runner.rb +18 -0
  224. data/lib/rhoconnect/handler/search/runner.rb +104 -0
  225. data/lib/rhoconnect/handler/search.rb +26 -0
  226. data/lib/rhoconnect/handler/sync.rb +29 -0
  227. data/lib/rhoconnect/jobs/source_job.rb +13 -4
  228. data/lib/rhoconnect/js_adapter.rb +79 -0
  229. data/lib/rhoconnect/license.rb +10 -2
  230. data/lib/rhoconnect/middleware/current_user.rb +14 -1
  231. data/lib/rhoconnect/middleware/helpers.rb +10 -93
  232. data/lib/rhoconnect/middleware/x_domain_session_wrapper.rb +1 -1
  233. data/lib/rhoconnect/model/base.rb +229 -0
  234. data/lib/rhoconnect/model/dynamic_adapter_model.rb +90 -0
  235. data/lib/rhoconnect/model/js_base.rb +121 -0
  236. data/lib/rhoconnect/ping/android.rb +1 -1
  237. data/lib/rhoconnect/predefined_adapters/bench_adapter.rb +7 -4
  238. data/lib/rhoconnect/read_state.rb +3 -3
  239. data/lib/rhoconnect/server.rb +159 -190
  240. data/lib/rhoconnect/source.rb +100 -11
  241. data/lib/rhoconnect/stats/record.rb +10 -10
  242. data/lib/rhoconnect/store.rb +905 -591
  243. data/lib/rhoconnect/{model.rb → store_orm.rb} +53 -115
  244. data/lib/rhoconnect/tasks.rb +18 -4
  245. data/lib/rhoconnect/test_methods.rb +30 -17
  246. data/lib/rhoconnect/user.rb +35 -17
  247. data/lib/rhoconnect/utilities.rb +1 -1
  248. data/lib/rhoconnect/version.rb +2 -2
  249. data/lib/rhoconnect/web-console/server.rb +29 -14
  250. data/lib/rhoconnect/web-console/views/home.js +10 -10
  251. data/lib/rhoconnect/web-console/views/new_ping.js +1 -1
  252. data/lib/rhoconnect.rb +120 -51
  253. data/rhoconnect.gemspec +4 -3
  254. data/spec/api/api_helper.rb +1 -6
  255. data/spec/api/app/fast_delete_spec.rb +4 -4
  256. data/spec/api/app/fast_insert_spec.rb +4 -4
  257. data/spec/api/app/fast_update_spec.rb +8 -8
  258. data/spec/api/app/push_deletes_spec.rb +2 -2
  259. data/spec/api/app/push_objects_spec.rb +5 -5
  260. data/spec/api/client/client_get_db_doc_spec.rb +6 -4
  261. data/spec/api/client/client_set_db_doc_spec.rb +3 -2
  262. data/spec/api/client/get_client_params_spec.rb +14 -0
  263. data/spec/api/client/list_client_docs_spec.rb +30 -20
  264. data/spec/api/client/reset_spec.rb +36 -0
  265. data/spec/api/source/get_source_params_spec.rb +23 -17
  266. data/spec/api/system/get_license_info_spec.rb +0 -20
  267. data/spec/api/system/login_spec.rb +8 -0
  268. data/spec/api/system/reset_spec.rb +0 -1
  269. data/spec/api/system/stats_spec.rb +5 -5
  270. data/spec/api/user/create_user_spec.rb +14 -6
  271. data/spec/api/user/delete_user_spec.rb +20 -18
  272. data/spec/api/user/list_users_spec.rb +5 -6
  273. data/spec/api/user/update_user_spec.rb +5 -4
  274. data/spec/apps/rhotestapp/config.ru +16 -1
  275. data/spec/apps/rhotestapp/controllers/js/js_sample_controller.js +23 -0
  276. data/spec/apps/rhotestapp/controllers/js/sample2_controller.js +32 -0
  277. data/spec/apps/rhotestapp/controllers/ruby/application_controller.rb +21 -0
  278. data/spec/apps/rhotestapp/controllers/ruby/sample_adapter_controller.rb +8 -0
  279. data/spec/apps/rhotestapp/models/js/js_sample.js +55 -0
  280. data/spec/apps/rhotestapp/models/js/sample2.js +25 -0
  281. data/spec/apps/rhotestapp/{sources → models/ruby}/base_adapter.rb +0 -0
  282. data/spec/apps/rhotestapp/{sources → models/ruby}/fixed_schema_adapter.rb +0 -0
  283. data/spec/apps/rhotestapp/{sources → models/ruby}/other_adapter.rb +0 -0
  284. data/spec/apps/rhotestapp/{sources → models/ruby}/sample_adapter.rb +0 -0
  285. data/spec/apps/rhotestapp/{sources → models/ruby}/simple_adapter.rb +2 -2
  286. data/spec/apps/rhotestapp/{sources → models/ruby}/sub_adapter.rb +0 -0
  287. data/spec/apps/rhotestapp/settings/settings.yml +0 -1
  288. data/spec/bulk_data/bulk_data_spec.rb +20 -5
  289. data/spec/cli/cli_spec.rb +83 -0
  290. data/spec/client_spec.rb +20 -17
  291. data/spec/client_sync_spec.rb +244 -406
  292. data/spec/controllers/js_base_spec.rb +89 -0
  293. data/spec/doc/doc_spec.rb +18 -18
  294. data/spec/document_spec.rb +29 -13
  295. data/spec/dynamic_adapter_spec.rb +6 -6
  296. data/spec/generator/generator_spec.rb +7 -4
  297. data/spec/jobs/bulk_data_job_spec.rb +14 -10
  298. data/spec/jobs/source_job_spec.rb +8 -8
  299. data/spec/license_spec.rb +5 -2
  300. data/spec/models/js_model_spec.rb +39 -0
  301. data/spec/node_spec.rb +42 -0
  302. data/spec/perf/store_perf_spec.rb +67 -12
  303. data/spec/ping/android_spec.rb +1 -1
  304. data/spec/read_state_spec.rb +1 -1
  305. data/spec/rhoconnect_spec.rb +1 -1
  306. data/spec/server/cors_spec.rb +14 -18
  307. data/spec/server/server_spec.rb +265 -88
  308. data/spec/server/stats_spec.rb +1 -1
  309. data/spec/source_adapter_spec.rb +54 -27
  310. data/spec/source_spec.rb +8 -3
  311. data/spec/source_sync_spec.rb +538 -468
  312. data/spec/spec_helper.rb +35 -4
  313. data/spec/stats/record_spec.rb +10 -10
  314. data/spec/{model_spec.rb → store_orm_spec.rb} +56 -54
  315. data/spec/store_spec.rb +159 -179
  316. data/spec/support/shared_examples.rb +36 -27
  317. data/spec/sync_states_spec.rb +40 -33
  318. data/spec/test_methods_spec.rb +18 -14
  319. data/spec/user_spec.rb +17 -30
  320. metadata +156 -52
  321. data/bench/benchapp/application.rb +0 -39
  322. data/bench/blobapp/application.rb +0 -44
  323. data/commands/rhoconnect/clean_start.rb +0 -9
  324. data/commands/rhoconnect/create_user.rb +0 -18
  325. data/commands/rhoconnect/delete_device.rb +0 -9
  326. data/commands/rhoconnect/delete_user.rb +0 -8
  327. data/commands/rhoconnect/reset.rb +0 -16
  328. data/commands/rhoconnect/reset_refresh.rb +0 -11
  329. data/generators/templates/application/application.rb +0 -43
  330. data/lib/rhoconnect/client_sync.rb +0 -434
  331. data/lib/rhoconnect/dynamic_adapter.rb +0 -91
  332. data/lib/rhoconnect/middleware/admin_user.rb +0 -23
  333. data/lib/rhoconnect/middleware/current_request.rb +0 -16
  334. data/lib/rhoconnect/middleware/login_required.rb +0 -22
  335. data/lib/rhoconnect/source_adapter.rb +0 -132
  336. data/lib/rhoconnect/source_sync.rb +0 -464
  337. data/spec/apps/rhotestapp/application.rb +0 -23
@@ -5,762 +5,1077 @@ require 'connection_pool'
5
5
  module Rhoconnect
6
6
 
7
7
  class StoreLockException < RuntimeError; end
8
-
8
+
9
9
  class Store
10
- RESERVED_ATTRIB_NAMES = ["attrib_type", "id"] unless defined? RESERVED_ATTRIB_NAMES
11
- @@db = nil
10
+ @@dbs = nil
12
11
 
13
12
  class << self
14
- def db; @@db || @@db = _get_redis end
15
-
16
- def db=(server=nil)
17
- @@db = _get_redis(server)
13
+ def nullify
14
+ @@dbs = nil
18
15
  end
19
-
16
+
20
17
  def create(server=nil)
21
- @@db ||= _get_redis(server)
22
- raise "Error connecting to Redis store." unless @@db and
23
- (@@db.is_a?(Redis) or @@db.is_a?(Redis::Client) or @@db.is_a?(ConnectionPool::Wrapper))
18
+ return @@dbs if @@dbs
19
+
20
+ if server.is_a?Array
21
+ server.each do |server_string|
22
+ db_inst = RedisImpl.new
23
+ db_inst.create(server_string)
24
+ @@dbs ||= []
25
+ @@dbs << db_inst
26
+ end
27
+ else
28
+ db_inst = RedisImpl.new
29
+ db_inst.create(server)
30
+ @@dbs ||= []
31
+ @@dbs << db_inst
32
+ end
33
+ @@dbs
24
34
  end
25
-
26
- def start_transaction
27
- @@db.multi
35
+
36
+ def reconnect
37
+ @@dbs.each do |db_inst|
38
+ db_inst.reconnect
39
+ end
28
40
  end
29
-
30
- def execute_transaction
31
- @@db.exec
41
+
42
+ def num_stores
43
+ @@dbs.nil? ? 0 : @@dbs.size
44
+ end
45
+
46
+ def get_store(index = 0)
47
+ @@dbs[index]
48
+ end
49
+
50
+ def flush_all
51
+ @@dbs.each do |store_instance|
52
+ store_instance.flush_all
53
+ end
32
54
  end
55
+
56
+ # Deletes all keys matching a given mask
57
+ def flush_data(keymask)
58
+ @@dbs.each do |store|
59
+ store.flush_data(keymask)
60
+ end
61
+ end
62
+ alias_method :flash_data, :flush_data
33
63
 
34
64
  def doc_type(dockey)
35
- @@db.type(dockey) if dockey
65
+ get_store(0).db.type(dockey) if dockey
36
66
  end
37
67
 
38
68
  def set_db_doc(dockey, data, append=false)
39
- if data.is_a?(String)
40
- put_value(dockey, data)
41
- else
42
- put_data(dockey, data, append)
43
- end
69
+ get_store(0).set_db_doc(dockey, data, append)
44
70
  end
45
71
 
46
72
  def get_db_doc(dockey)
47
- doctype = Store.doc_type(dockey)
48
- if doctype == 'string'
49
- Store.get_value(dockey)
50
- elsif doctype == 'list'
51
- Store.get_data(dockey, Array).to_json
52
- else
53
- Store.get_data(dockey).to_json
73
+ doc = ""
74
+ @@dbs.each do |store|
75
+ if store.exists?(dockey)
76
+ doc = store.get_db_doc(dockey)
77
+ break
78
+ end
54
79
  end
80
+ doc
81
+ end
82
+
83
+ def keys(pattern)
84
+ get_store(0).keys(pattern)
55
85
  end
56
86
 
57
87
  def put_object(dockey, key, data={})
58
- _put_objects(dockey, {key => data})
88
+ get_store(0).put_object(dockey, key, data)
59
89
  end
60
90
 
61
91
  # Adds set with given data, replaces existing set
62
92
  # if it exists or appends data to the existing set
63
93
  # if append flag set to true
64
94
  def put_data(dockey,data={},append=false)
65
- if dockey and data
66
- flash_data(dockey) unless append
67
- # Inserts a hash or array
68
- if data.is_a?Hash
69
- _put_objects(dockey, data)
70
- else
71
- put_list(dockey,data,append)
72
- end
73
- end
74
- true
95
+ get_store(0).put_data(dockey, data, append)
96
+ end
97
+
98
+ # Same as above, but sets TTL on every key
99
+ def put_tmp_data(dockey,data={},append=false)
100
+ get_store(0).put_tmp_data(dockey, data, append)
75
101
  end
76
102
 
77
103
  def put_list(dockey, data=[], append=false)
78
- if dockey and data
79
- flash_data(dockey) unless append
80
- @@db.pipelined do
81
- data.each do |element|
82
- @@db.rpush(dockey, element)
83
- end
84
- end
85
- end
86
- true
104
+ get_store(0).put_list(dockey, data, append)
87
105
  end
88
106
 
89
107
  # updates objects for a given doctype, source, user
90
108
  # create new objects if necessary
91
109
  def update_objects(dockey, data={})
92
- return 0 unless dockey and data
93
-
94
- new_object_count = 0
95
- objs = get_objects(dockey, data.keys) || {}
96
-
97
- collected_adds = {}
98
- collected_rems = {}
99
- my_bucket = nil
100
- @@db.pipelined do
101
- data.each do |key,obj|
102
- is_create = objs[key].nil?
103
- new_object_count += 1 if is_create
104
- obj_bucket = _add_bucket_index(dockey, "#{_create_obj_index(key)}")
105
-
106
- # collect SREM (if object exists in DB)
107
- unless is_create
108
- old_element = set_obj_element(key,objs[key])
109
- collected_rems[obj_bucket] ||= []
110
- collected_rems[obj_bucket] << old_element
111
- end
112
- # update the object and collect SADD
113
- objs[key] ||= {}
114
- objs[key].merge!(obj)
115
-
116
- new_element = set_obj_element(key,objs[key])
117
- collected_adds[obj_bucket] ||= []
118
- collected_adds[obj_bucket] << new_element
119
- end
120
- # process all SADD and SREM commands as one
121
- # SREM must go first
122
- collected_rems.each do |bucket, bucket_data|
123
- @@db.srem(bucket, bucket_data)
124
- end
125
- collected_adds.each do |bucket, bucket_data|
126
- @@db.sadd(bucket, bucket_data)
127
- end
128
- end
129
-
130
-
131
- #data1 = @@db.smembers(my_bucket)
132
- #puts "data1 is #{data1.inspect}"
133
-
134
- new_object_count
110
+ get_store(0).update_objects(dockey, data)
135
111
  end
136
112
 
137
113
  # Removes objects from a given doctype,source,user
138
114
  def delete_objects(dockey,data=[])
139
- return 0 unless dockey and data
140
-
141
- objs = get_objects(dockey, data)
142
- _delete_objects(dockey, objs)
115
+ get_store(0).delete_objects(dockey, data)
143
116
  end
144
117
 
145
118
  # Deletes data from a given doctype,source,user
146
119
  def delete_data(dockey,data={})
147
- if dockey and data
148
- _delete_objects(dockey, data)
149
- end
150
- true
120
+ get_store(0).delete_data(dockey, data)
151
121
  end
152
122
 
153
123
  # Adds a simple key/value pair
154
124
  def put_value(dockey,value)
155
- if dockey
156
- if value
157
- @@db.set(dockey,value.to_s)
158
- else
159
- @@db.del(dockey)
160
- end
161
- end
125
+ get_store(0).put_value(dockey, value)
162
126
  end
163
127
 
164
128
  # Retrieves value for a given key
165
129
  def get_value(dockey)
166
- @@db.get(dockey) if dockey
130
+ get_store(0).get_value(dockey)
131
+ end
132
+
133
+ def delete_value(dockey)
134
+ get_store(0).delete_value(dockey)
167
135
  end
168
136
 
169
137
  def incr(dockey)
170
- @@db.incr(dockey)
138
+ get_store(0).incr(dockey)
171
139
  end
172
140
 
173
141
  def decr(dockey)
174
- @@db.decr(dockey)
142
+ get_store(0).decr(dockey)
175
143
  end
176
144
 
177
145
  def update_count(dockey, count)
178
- Store.db.incrby(dockey, count)
146
+ get_store(0).update_count(dockey, count)
179
147
  end
180
148
 
181
149
  def get_object(dockey, key)
182
- res = _get_objects(dockey, [key])
183
- (res and res.size > 0) ? res.values[0] : nil
150
+ get_store(0).get_object(dockey, key)
184
151
  end
185
152
 
186
153
  def get_objects(dockey, keys)
187
- _get_objects(dockey, keys)
154
+ get_store(0).get_objects(dockey, keys)
188
155
  end
189
156
 
190
157
  # Retrieves set for given dockey,source,user
191
158
  def get_data(dockey,type=Hash)
192
- res = type == Hash ? {} : []
193
- if dockey
194
- if type == Hash
195
- buckets = _get_buckets(dockey)
196
- members = @@db.pipelined do
197
- buckets.each do |bucket|
198
- @@db.smembers(bucket)
199
- end if buckets
200
- end
201
- members.each do |elements|
202
- elements.each do |element|
203
- key,obj = get_obj_element(element)
204
- res[key] = obj
205
- #res[key].merge!({attrib => value})
206
- end if elements
207
- end if members
208
- else
209
- res = get_list(dockey)
210
- end
211
- end
212
- res
159
+ get_store(0).get_data(dockey, type)
213
160
  end
214
161
 
215
162
  def get_list(dockey)
216
- res = []
217
- if dockey
218
- res = @@db.lrange(dockey, 0, -1)
219
- end
220
- res
163
+ get_store(0).get_list(dockey)
164
+ end
165
+
166
+ # low-level operations with sorted sets
167
+ def zadd(dockey, score, value)
168
+ get_store(0).zadd(dockey, score, value)
169
+ end
170
+
171
+ def zrem(dockey, value)
172
+ get_store(0).zrem(dockey, value)
173
+ end
174
+
175
+ def zremrangebyscore(dockey, min_elem, max_elem)
176
+ get_store(0).zremrangebyscore(dockey, min_elem, max_elem)
177
+ end
178
+
179
+ def zscore(dockey, value)
180
+ get_store(0).zscore(dockey, value)
181
+ end
182
+
183
+ def zrevrange(dockey, start, stop)
184
+ get_store(0).zrevrange(dockey, start, stop)
185
+ end
186
+
187
+ def zrange(dockey, start, stop)
188
+ get_store(0).zrange(dockey, start, stop)
221
189
  end
222
190
 
223
191
  # Retrieves diff data hash between two sets
224
192
  # each entry is in the form of DIFF_OBJ_ELEMENT => [OBJ_KEY, OBJ_DATA_PAIRS]
225
193
  def get_diff_data(src_dockey,dst_dockey,p_size=nil)
226
- res = {}
227
- if src_dockey and dst_dockey
228
- # obtain combined indices
229
- indices = @@db.hgetall("#{dst_dockey}:indices")
230
- indices.keys.each do |index|
231
- dst_bucket_name = "#{dst_dockey}:#{index}"
232
- src_bucket_name = "#{src_dockey}:#{index}"
233
- diff_elements = @@db.sdiff(dst_bucket_name,src_bucket_name)
234
- diff_elements.each do |element|
235
- keypairs = get_obj_key_and_pairs(element)
236
- next unless keypairs
237
- res[element] = keypairs
238
- return res if p_size and (res.size >= p_size)
239
- end
240
- end
241
- end
242
- res
194
+ get_store(0).get_diff_data(src_dockey, dst_dockey, p_size)
195
+ end
196
+
197
+ # Retrieves diff data hash between two sets by using BruteForce approach
198
+ # => download both sets from Redis and compute diffs inside of Ruby
199
+ # worst-cast scenario - it is much slower than doing Redis sdiff
200
+ # but : it allows Redis clustering
201
+ # each entry is in the form of DIFF_OBJ_ELEMENT => [OBJ_KEY, OBJ_DATA_PAIRS]
202
+ def get_diff_data_bruteforce(src_dockey,dst_dockey,p_size=nil)
203
+ get_store(0).get_diff_data_bruteforce(src_dockey, dst_dockey, p_size)
243
204
  end
244
205
 
245
206
  def get_inserts_deletes(inserts_elements_map, deletes_elements_map)
246
- inserts_obj_hash = {}
247
- inserts_elements_map.each do |element,keypairs|
248
- key,obj_pairs = keypairs[0],keypairs[1]
249
- next unless (key and obj_pairs)
250
- inserts_obj_hash[key] = Set.new(obj_pairs)
251
- end
252
-
253
- deletes_obj_hash = {}
254
- deletes_elements_map.each do |element,keypairs|
255
- key,obj_pairs = keypairs[0],keypairs[1]
256
- next unless (key and obj_pairs)
257
- deletes_obj_hash[key] = Set.new(obj_pairs)
258
- end
259
- # modified attributes
260
- inserts = {}
261
- deletes = {}
262
-
263
- inserts_obj_hash.each do |key, obj_set|
264
- deletes_pairs = nil
265
- inserts_pairs = nil
266
- if deletes_obj_hash.has_key?(key)
267
- deletes_pairs = deletes_obj_hash[key].dup.subtract(obj_set).to_a
268
- inserts_pairs = obj_set.dup.subtract(deletes_obj_hash[key]).to_a
269
- # remove the key from the deletes set - we already processed it
270
- deletes_obj_hash.delete(key)
271
- else
272
- # if object is not in the deletes set - then, it's all inserts
273
- inserts_pairs = obj_set.to_a
274
- end
275
- # split resulting pairs
276
- if inserts_pairs and inserts_pairs.size > 0
277
- inserts[key] = split_obj_pairs(inserts_pairs)
278
- end
279
- if deletes_pairs and deletes_pairs.size > 0
280
- deletes[key] = split_obj_pairs(deletes_pairs)
281
- end
282
- end
283
- # after we analyzed the inserts__obj_hash
284
- # => deletes_obj_hash should contain only the unmatched deletes
285
- deletes_obj_hash.each do |key, obj_set|
286
- if obj_set.size > 0
287
- deletes[key] = split_obj_pairs(obj_set.to_a)
288
- end
289
- end
290
-
291
- [inserts, deletes]
207
+ get_store(0).get_inserts_deletes(inserts_elements_map, deletes_elements_map)
292
208
  end
293
209
 
294
210
  def update_elements(dockey, inserts_elements_map, deletes_elements_map)
295
- indices_to_cleanup = Set.new
296
- @@db.pipelined do
297
- collected_adds = {}
298
- collected_rems = {}
299
-
300
- inserts_elements_map.each do |element,keypairs|
301
- key = keypairs[0]
302
- next if not key or not element or element.size == 0
303
-
304
- obj_bucket_index = _create_obj_index(key)
305
- bucket_name = "#{dockey}:#{obj_bucket_index}"
306
- _add_bucket_index(dockey, obj_bucket_index)
307
-
308
- collected_adds[bucket_name] ||= []
309
- collected_adds[bucket_name] << element
310
- end
311
-
312
- deletes_elements_map.each do |element,keypairs|
313
- key = keypairs[0]
314
- next if not key or not element or element.size == 0
315
-
316
- obj_bucket_index = _create_obj_index(key)
317
- bucket_name = "#{dockey}:#{obj_bucket_index}"
318
- indices_to_cleanup << bucket_name
319
-
320
- collected_rems[bucket_name] ||= []
321
- collected_rems[bucket_name] << element
322
- end
323
-
324
- # now, perform SREM first, then SADD
325
- collected_rems.each do |bucket, bucket_data|
326
- @@db.srem(bucket, bucket_data)
327
- end
328
- collected_adds.each do |bucket,bucket_data|
329
- @@db.sadd(bucket, bucket_data)
330
- end
331
- end
332
- # now, cleanup buckets if necessary
333
- _cleanup_buckets(dockey, indices_to_cleanup.to_a)
334
- end
335
-
336
- # Deletes all keys matching a given mask
337
- def flash_data(keymask)
338
- if keymask.to_s[/[*\[\]?]/]
339
- # If the keymask contains any pattern matching characters
340
- # Use keys command to find all keys matching pattern (this is extremely expensive)
341
- # Then delete matches
342
- @@db.keys(keymask).each do |key|
343
- _delete_doc(key)
344
- end
345
- else
346
- # The keymask doesn't contain pattern matching characters
347
- # A delete call is all that is needed
348
- _delete_doc(keymask)
349
- end
211
+ get_store(0).update_elements(dockey, inserts_elements_map, deletes_elements_map)
350
212
  end
351
213
 
352
214
  # Lock a given key and release when provided block is finished
353
- def lock(dockey,timeout=0,raise_on_expire=false)
354
- m_lock = get_lock(dockey,timeout,raise_on_expire)
355
- res = yield
356
- release_lock(dockey,m_lock)
357
- res
215
+ def lock(dockey,timeout=0,raise_on_expire=false, &block)
216
+ get_store(0).lock(dockey, timeout, raise_on_expire, &block)
358
217
  end
359
218
 
360
219
  def get_lock(dockey,timeout=0,raise_on_expire=false)
361
- lock_key = _lock_key(dockey)
362
- current_time = Time.now.to_i
363
- ts = current_time+(Rhoconnect.lock_duration || timeout)+1
364
- loop do
365
- if not @@db.setnx(lock_key,ts)
366
- current_lock = @@db.get(lock_key)
367
- # ensure lock wasn't released between the setnx and get calls
368
- if current_lock
369
- current_lock_timeout = current_lock.to_i
370
- if raise_on_expire or Rhoconnect.raise_on_expired_lock
371
- if current_lock_timeout <= current_time
372
- # lock expired before operation which set it up completed
373
- # this process cannot continue without corrupting locked data
374
- raise StoreLockException, "Lock \"#{lock_key}\" expired before it was released"
375
- end
376
- else
377
- if current_lock_timeout <= current_time and
378
- @@db.getset(lock_key,ts).to_i <= current_time
379
- # previous lock expired and we replaced it with our own
380
- break
381
- end
382
- end
383
- # lock was released between setnx and get - try to acquire it again
384
- elsif @@db.setnx(lock_key,ts)
385
- break
386
- end
387
- sleep(1)
388
- current_time = Time.now.to_i
389
- else
390
- break #no lock was set, so we set ours and leaving
391
- end
392
- end
393
- return ts
220
+ get_store(0).get_lock(dockey, timeout, raise_on_expire)
394
221
  end
395
222
 
396
- # Due to redis bug #140, setnx always returns true so this doesn't work
397
- # def get_lock(dockey,timeout=0)
398
- # lock_key = _lock_key(dockey)
399
- # until @@db.setnx(lock_key,1) do
400
- # sleep(1)
401
- # end
402
- # @@db.expire(lock_key,timeout+1)
403
- # Time.now.to_i+timeout+1
404
- # end
405
-
406
223
  def release_lock(dockey,lock,raise_on_expire=false)
407
- @@db.del(_lock_key(dockey)) if raise_on_expire or Rhoconnect.raise_on_expired_lock or (lock >= Time.now.to_i)
224
+ get_store(0).release_lock(dockey, lock, raise_on_expire)
408
225
  end
409
226
 
410
227
  # Create a copy of srckey in dstkey
411
228
  def clone(srckey,dstkey)
412
- buckets = _get_bucket_indices(srckey)
413
- if buckets.size
414
- @@db.pipelined do
415
- buckets.each do |bucket_index|
416
- _add_bucket_index(dstkey, bucket_index)
417
- @@db.sdiffstore("#{dstkey}:#{bucket_index}", "#{srckey}:#{bucket_index}", '')
418
- end
419
- end
420
- else
421
- @@db.sdiffstore(dstkey,srckey,'')
422
- end
229
+ get_store(0).clone(srckey, dstkey)
423
230
  end
424
231
 
425
232
  # Rename srckey to dstkey
426
233
  def rename(srckey,dstkey)
427
- buckets = _get_bucket_indices(srckey)
428
- if buckets.size
429
- @@db.pipelined do
430
- @@db.del("#{srckey}:indices")
431
- buckets.each do |bucket_index|
432
- _add_bucket_index(dstkey, bucket_index)
433
- @@db.rename("#{srckey}:#{bucket_index}", "#{dstkey}:#{bucket_index}")
434
- end
435
- end
436
- else
437
- @@db.rename(srckey,dstkey) if @@db.exists(srckey)
438
- end
234
+ get_store(0).rename(srckey, dstkey)
235
+ end
236
+
237
+ # Rename srckey to dstkey
238
+ def rename_tmp_data(srckey,dstkey)
239
+ get_store(0).rename_tmp_data(srckey, dstkey)
439
240
  end
440
241
 
242
+
441
243
  def put_zdata(dockey,assoc_key,data={},append=false)
442
- return true unless (dockey and assoc_key and data)
443
- flush_zdata(dockey) unless append
444
- current_score = 0
445
- current_score_data = Store.db.zrevrange(dockey,0,0,:with_scores => true)
446
- current_score = current_score_data[-1][1].to_i if current_score_data and current_score_data[-1]
447
- current_score += 1
448
- data.each do |key,hash_value|
449
- unique_record_key = setelement(current_score,assoc_key, key)
450
- Store.db.zadd(dockey, current_score, unique_record_key)
451
- Store.put_data("#{dockey}:#{unique_record_key}",{key => hash_value})
452
- end
453
- true
244
+ get_store(0).put_zdata(dockey, assoc_key, data, append)
454
245
  end
455
246
 
456
247
  # Retrieves set for given dockey,associated key (client_id), obj_hashes
457
248
  def get_zdata(dockey)
458
- data = Store.db.zrange(dockey, 0, -1)
459
- ret = []
460
- keys = []
461
- unless data.nil?
462
- scores = []
463
- data.each do |zsetkey|
464
- obj_hash = Store.get_data "#{dockey}:#{zsetkey}"
465
- score,key,objkey = getelement(zsetkey)
466
- if scores[-1] != score
467
- ret << obj_hash
468
- keys << key
469
- scores << score
470
- else
471
- ret[-1].merge!(obj_hash)
472
- end
473
- end
474
- end
475
- [ret, keys]
249
+ get_store(0).get_zdata(dockey)
476
250
  end
477
251
 
478
252
  # Deletes all keys and their hashes from the Redis DB
479
253
  def flush_zdata(dockey)
480
- data = Store.db.zrange(dockey, 0, -1)
481
- data.each do |hash_key|
482
- _delete_doc("#{dockey}:#{hash_key}")
483
- end
484
- Store.db.zremrangebyrank(dockey, 0, -1)
485
- end
486
-
487
- # these methods should be used for transactional, multi-source CUD queue requests
488
- def put_multi_zdata(dockey,assoc_key,sources=[],data={},append=false)
489
- return true unless (dockey and assoc_key and sources and data)
490
- flush_multi_zdata(dockey) unless append
491
- current_score = 0
492
- current_score_data = Store.db.zrevrange(dockey,0,0,:with_scores => true)
493
- current_score = current_score_data[-1][1].to_i if current_score_data and current_score_data[-1]
494
- current_score += 1
495
- Store.put_data("#{dockey}:#{current_score.to_i}:#{assoc_key}:sources", sources)
496
- data.each do |source_key,source_hashes|
497
- source_hashes.each do |operation,obj_hashes|
498
- unique_zrecord_key = setelement("#{current_score.to_i},#{assoc_key}",source_key,operation)
499
- Store.db.zadd(dockey, current_score.to_i, unique_zrecord_key)
500
- Store.put_data("#{dockey}:#{unique_zrecord_key}", obj_hashes)
501
- end
502
- end
503
- true
504
- end
505
-
506
- def get_multi_zdata(dockey)
507
- data = Store.db.zrange(dockey, 0, -1)
508
- ret = []
509
- sources = []
510
- keys = []
511
- unless data.nil?
512
- scores = []
513
- data.each do |zsetkey|
514
- scored_assoc_key,source_key,operation = getelement(zsetkey)
515
- score,assoc_key = scored_assoc_key.split(',')
516
- obj_hash = Store.get_data "#{dockey}:#{zsetkey}"
517
- if scores[-1] != score.to_i
518
- sources << Store.get_data("#{dockey}:#{score}:#{assoc_key}:sources", Array)
519
- ret << {source_key => {operation => obj_hash}}
520
- scores << score.to_i
521
- keys << assoc_key
522
- else
523
- ret[-1][source_key] ||= {}
524
- ret[-1][source_key].merge!({operation => obj_hash})
525
- end
526
- end
527
- end
528
- [ret, sources, keys]
529
- end
530
-
531
- # Deletes all keys and their hashes from the Redis DB
532
- def flush_multi_zdata(dockey)
533
- data = Store.db.zrange(dockey, 0, -1)
534
- data.each do |zsetkey|
535
- scored_assoc_key,source_key,operation = getelement(zsetkey)
536
- score,assoc_key = scored_assoc_key.split(',')
537
- _delete_doc("#{dockey}:#{zsetkey}")
538
- _delete_doc("#{dockey}:#{score}:#{assoc_key}:sources")
539
- end
540
- Store.db.zremrangebyrank(dockey, 0, -1)
254
+ get_store(0).flush_zdata(dockey)
541
255
  end
542
256
 
543
257
  def exists?(key)
544
- @@db.exists(key) || @@db.exists("#{key}:indices")
258
+ get_store(0).exists?(key)
545
259
  end
546
260
 
547
261
  alias_method :set_value, :put_value
548
262
  alias_method :set_data, :put_data
549
-
550
- private
551
- if RUBY_VERSION =~ /1.9/
552
- def _get_redis(server=nil)
553
- url = ENV[REDIS_URL] || ENV[REDISTOGO_URL] || nil
554
- if url
555
- ConnectionPool::Wrapper.new(:size => Rhoconnect.connection_pool_size,
556
- :timeout => Rhoconnect.connection_pool_timeout) do
557
- Redis.connect(:url => url, :timeout => Rhoconnect.redis_timeout, :thread_safe => true)
558
- end
559
- elsif server and server.is_a?(String)
560
- host,port,db,password = server.split(':')
561
- ConnectionPool::Wrapper.new(:size => Rhoconnect.connection_pool_size,
562
- :timeout => Rhoconnect.connection_pool_timeout) do
563
- Redis.connect(:thread_safe => true, :host => host,
564
- :port => port, :db => db, :password => password, :timeout => Rhoconnect.redis_timeout)
565
- end
566
- elsif server and (server.is_a?(Redis) or server.is_a?(ConnectionPool::Wrapper))
567
- server
263
+ end
264
+ end
265
+
266
+ class RedisImpl
267
+ RESERVED_ATTRIB_NAMES = ["attrib_type", "id"] unless defined? RESERVED_ATTRIB_NAMES
268
+ @db = nil
269
+
270
+ def create(server=nil)
271
+ @db ||= _get_redis(server)
272
+ raise "Error connecting to Redis store." unless @db and
273
+ (@db.is_a?(Redis) or @db.is_a?(Redis::Client) or @db.is_a?(ConnectionPool::Wrapper))
274
+ end
275
+
276
+ def reconnect
277
+ @db.client.reconnect
278
+ end
279
+
280
+ def flush_all
281
+ @db.flushdb
282
+ end
283
+
284
+ def start_transaction
285
+ @db.multi
286
+ end
287
+
288
+ def execute_transaction
289
+ @db.exec
290
+ end
291
+
292
+ def doc_type(dockey)
293
+ @db.type(dockey) if dockey
294
+ end
295
+
296
+ def set_db_doc(dockey, data, append=false)
297
+ if data.is_a?(String)
298
+ put_value(dockey, data)
299
+ else
300
+ put_data(dockey, data, append)
301
+ end
302
+ end
303
+
304
+ def get_db_doc(dockey)
305
+ doctype = doc_type(dockey)
306
+ if doctype == 'string'
307
+ get_value(dockey)
308
+ elsif doctype == 'list'
309
+ get_data(dockey, Array).to_json
310
+ else
311
+ get_data(dockey).to_json
312
+ end
313
+ end
314
+
315
+ def put_object(dockey, key, data={})
316
+ _put_objects(dockey, {key => data})
317
+ end
318
+
319
+ # Same as above, but sets TTL on every key
320
+ def put_tmp_data(dockey,data={},append=false)
321
+ put_data(dockey, data, append, Rhoconnect.store_key_ttl)
322
+ end
323
+
324
+ # Adds set with given data, replaces existing set
325
+ # if it exists or appends data to the existing set
326
+ # if append flag set to true
327
+ # if ttl > 0 - sets expriration time on the keys
328
+ def put_data(dockey,data={},append=false, ttl=0)
329
+ if dockey and data
330
+ flush_data(dockey) unless append
331
+ # Inserts a hash or array
332
+ if data.is_a?Hash
333
+ _put_objects(dockey, data, ttl)
568
334
  else
569
- ConnectionPool::Wrapper.new(:size => 5, :timeout => 30) do
570
- Redis.connect(:timeout => 30, :thread_safe => true)
571
- end
335
+ put_list(dockey,data,append, ttl)
572
336
  end
573
337
  end
574
- else # Ruby 1.8 does not support Connnection Pools
575
- def _get_redis(server=nil)
576
- url = ENV[REDIS_URL] || ENV[REDISTOGO_URL] || nil
577
- if url
578
- Redis.connect(:url => url, :timeout => Rhoconnect.redis_timeout, :thread_safe => true)
579
- elsif server and server.is_a?(String)
580
- host,port,db,password = server.split(':')
581
- Redis.connect(:thread_safe => true, :host => host,
582
- :port => port, :db => db, :password => password, :timeout => Rhoconnect.redis_timeout)
583
- elsif server and (server.is_a?(Redis) or server.is_a?(ConnectionPool::Wrapper))
584
- server
585
- else
586
- Redis.connect(:timeout => 30, :thread_safe => true)
338
+ true
339
+ end
340
+
341
+ def put_list(dockey, data=[], append=false, ttl=0)
342
+ if dockey and data
343
+ flush_data(dockey) unless append
344
+ @db.pipelined do
345
+ data.each do |element|
346
+ @db.rpush(dockey, element)
347
+ end
348
+ @db.expire(dockey, ttl) if ttl > 0
587
349
  end
588
350
  end
589
- end # end of if RUBY_VERSION
590
-
351
+ true
352
+ end
353
+
354
+ # updates objects for a given doctype, source, user
355
+ # create new objects if necessary
356
+ def update_objects(dockey, data={})
357
+ return 0 unless dockey and data
591
358
 
592
- def _lock_key(dockey)
593
- "lock:#{dockey}"
594
- end
359
+ new_object_count = 0
360
+ objs = get_objects(dockey, data.keys) || {}
361
+
362
+ collected_adds = {}
363
+ collected_rems = {}
364
+ my_bucket = nil
365
+ @db.pipelined do
366
+ data.each do |key,obj|
367
+ is_create = objs[key].nil?
368
+ new_object_count += 1 if is_create
369
+ obj_bucket = _add_bucket_index(dockey, "#{_create_obj_index(key)}")
595
370
 
596
- def _is_reserved?(attrib,value) #:nodoc:
597
- RESERVED_ATTRIB_NAMES.include? attrib
598
- end
599
-
600
- # operations with docs that are split into buckets
601
- def _delete_doc(dockey)
602
- # check if this doc has buckets
603
- if(@@db.exists("#{dockey}:indices"))
604
- buckets_list = _get_buckets(dockey)
605
- # delete all buckets
606
- @@db.pipelined do
607
- @@db.del("#{dockey}:indices")
608
- buckets_list.each do |bucket|
609
- @@db.del(bucket)
610
- end
371
+ # collect SREM (if object exists in DB)
372
+ unless is_create
373
+ old_element = set_obj_element(key,objs[key])
374
+ collected_rems[obj_bucket] ||= []
375
+ collected_rems[obj_bucket] << old_element
611
376
  end
377
+ # update the object and collect SADD
378
+ objs[key] ||= {}
379
+ objs[key].merge!(obj)
380
+
381
+ new_element = set_obj_element(key,objs[key])
382
+ collected_adds[obj_bucket] ||= []
383
+ collected_adds[obj_bucket] << new_element
384
+ end
385
+ # process all SADD and SREM commands as one
386
+ # SREM must go first
387
+ collected_rems.each do |bucket, bucket_data|
388
+ @db.srem(bucket, bucket_data)
389
+ end
390
+ collected_adds.each do |bucket, bucket_data|
391
+ @db.sadd(bucket, bucket_data)
612
392
  end
613
-
614
- # delete main doc
615
- @@db.del(dockey)
616
393
  end
617
394
 
618
- # create object's bucket index
619
- # using SHA1 hashing
620
- def _create_obj_index(key)
621
- Digest::SHA1.hexdigest(key)[0..1]
622
- end
623
395
 
624
- def _add_bucket_index(dockey, bucket_index)
625
- bucket_name = "#{dockey}:#{bucket_index}"
626
- @@db.hsetnx("#{dockey}:indices", bucket_index, bucket_name)
627
- bucket_name
628
- end
396
+ #data1 = @db.smembers(my_bucket)
397
+ #puts "data1 is #{data1.inspect}"
629
398
 
630
- def _remove_bucket_index(dockey, bucket_index)
631
- @@db.hdel("#{dockey}:indices", bucket_index)
632
- end
399
+ new_object_count
400
+ end
401
+
402
+ # Removes objects from a given doctype,source,user
403
+ def delete_objects(dockey,data=[])
404
+ return 0 unless dockey and data
633
405
 
634
- def _get_bucket_indices(dockey)
635
- @@db.hkeys("#{dockey}:indices")
406
+ objs = get_objects(dockey, data)
407
+ _delete_objects(dockey, objs)
408
+ end
409
+
410
+ # Deletes data from a given doctype,source,user
411
+ def delete_data(dockey,data={})
412
+ if dockey and data
413
+ _delete_objects(dockey, data)
414
+ end
415
+ true
416
+ end
417
+
418
+ # Adds a simple key/value pair
419
+ def put_value(dockey,value)
420
+ if dockey
421
+ if value
422
+ @db.set(dockey,value.to_s)
423
+ else
424
+ @db.del(dockey)
425
+ end
426
+ end
427
+ end
428
+
429
+ # Retrieves value for a given key
430
+ def get_value(dockey)
431
+ @db.get(dockey) if dockey
432
+ end
433
+
434
+ def delete_value(dockey)
435
+ @db.del(dockey)
436
+ end
437
+
438
+ def incr(dockey)
439
+ @db.incr(dockey)
440
+ end
441
+
442
+ def decr(dockey)
443
+ @db.decr(dockey)
444
+ end
445
+
446
+ def update_count(dockey, count)
447
+ @db.incrby(dockey, count)
448
+ end
449
+
450
+ def get_object(dockey, key)
451
+ res = _get_objects(dockey, [key])
452
+ (res and res.size > 0) ? res.values[0] : nil
453
+ end
454
+
455
+ def get_objects(dockey, keys)
456
+ _get_objects(dockey, keys)
457
+ end
458
+
459
+ # Retrieves set for given dockey,source,user
460
+ def get_data(dockey,type=Hash)
461
+ res = type == Hash ? {} : []
462
+ if dockey
463
+ if type == Hash
464
+ buckets = _get_buckets(dockey)
465
+ members = @db.pipelined do
466
+ buckets.each do |bucket|
467
+ @db.smembers(bucket)
468
+ end if buckets
469
+ end
470
+ members.each do |elements|
471
+ elements.each do |element|
472
+ key,obj = get_obj_element(element)
473
+ res[key] = obj
474
+ #res[key].merge!({attrib => value})
475
+ end if elements
476
+ end if members
477
+ else
478
+ res = get_list(dockey)
479
+ end
480
+ end
481
+ res
482
+ end
483
+
484
+ def get_list(dockey)
485
+ res = []
486
+ if dockey
487
+ res = @db.lrange(dockey, 0, -1)
488
+ end
489
+ res
490
+ end
491
+
492
+ # Retrieves diff data hash between two sets
493
+ # each entry is in the form of DIFF_OBJ_ELEMENT => [OBJ_KEY, OBJ_DATA_PAIRS]
494
+ def get_diff_data(src_dockey,dst_dockey,p_size=nil)
495
+ res = {}
496
+ return res if p_size == 0
497
+ # return immediately if p_size == 0
498
+ # NOTE: 0 and nil are different, nil means - return all diffs
499
+ if src_dockey and dst_dockey
500
+ # obtain combined indices
501
+ indices = @db.hgetall("#{dst_dockey}:indices")
502
+ indices.keys.each do |index|
503
+ dst_bucket_name = "#{dst_dockey}:#{index}"
504
+ src_bucket_name = "#{src_dockey}:#{index}"
505
+ diff_elements = @db.sdiff(dst_bucket_name,src_bucket_name)
506
+ diff_elements.each do |element|
507
+ keypairs = get_obj_key_and_pairs(element)
508
+ next unless keypairs
509
+ res[element] = keypairs
510
+ return res if p_size and (res.size >= p_size)
511
+ end
512
+ end
513
+ end
514
+ res
515
+ end
516
+
517
+ # Retrieves diff data hash between two sets by using BruteForce approach
518
+ # => download both sets from Redis and compute diffs inside of Ruby
519
+ # worst-cast scenario - it is much slower than doing Redis sdiff
520
+ # but : it allows Redis clustering
521
+ # each entry is in the form of DIFF_OBJ_ELEMENT => [OBJ_KEY, OBJ_DATA_PAIRS]
522
+ def get_diff_data_bruteforce(src_dockey,dst_dockey,p_size=nil)
523
+ inserts = {}
524
+ deletes = {}
525
+ # return immediately if p_size == 0
526
+ # NOTE: 0 and nil are different, nil means - return all diffs
527
+ return res if p_size == 0
528
+ if src_dockey and dst_dockey
529
+ # obtain combined indices
530
+ indices = @db.hgetall("#{dst_dockey}:indices")
531
+ indices.merge!(@db.hgetall("#{src_dockey}:indices"))
532
+ indices.keys.each do |index|
533
+ dst_bucket_name = "#{dst_dockey}:#{index}"
534
+ src_bucket_name = "#{src_dockey}:#{index}"
535
+ src_elements = Set.new(@db.smembers(src_bucket_name))
536
+ dst_elements = Set.new(@db.smembers(dst_bucket_name))
537
+
538
+ insert_diff_elements = dst_elements.dup.subtract(src_elements) unless p_size and (inserts.size >= p_size)
539
+ delete_diff_elements = src_elements.dup.subtract(dst_elements) unless p_size and (deletes.size >= p_size)
540
+
541
+ insert_diff_elements.each do |element|
542
+ keypairs = get_obj_key_and_pairs(element)
543
+ next unless keypairs
544
+ inserts[element] = keypairs
545
+ break if p_size and (inserts.size >= p_size)
546
+ end if insert_diff_elements
547
+
548
+ delete_diff_elements.each do |element|
549
+ keypairs = get_obj_key_and_pairs(element)
550
+ next unless keypairs
551
+ deletes[element] = keypairs
552
+ break if p_size and (deletes.size >= p_size)
553
+ end if delete_diff_elements
554
+
555
+ break if p_size and (inserts.size >= p_size) and (deletes.size >= p_size)
556
+ end
557
+ end
558
+ [inserts, deletes]
559
+ end
560
+
561
+ def get_inserts_deletes(inserts_elements_map, deletes_elements_map)
562
+ inserts_obj_hash = {}
563
+ inserts_elements_map.each do |element,keypairs|
564
+ key,obj_pairs = keypairs[0],keypairs[1]
565
+ next unless (key and obj_pairs)
566
+ inserts_obj_hash[key] = Set.new(obj_pairs)
636
567
  end
637
568
 
638
- def _get_buckets(dockey)
639
- @@db.hvals("#{dockey}:indices")
569
+ deletes_obj_hash = {}
570
+ deletes_elements_map.each do |element,keypairs|
571
+ key,obj_pairs = keypairs[0],keypairs[1]
572
+ next unless (key and obj_pairs)
573
+ deletes_obj_hash[key] = Set.new(obj_pairs)
640
574
  end
575
+ # modified attributes
576
+ inserts = {}
577
+ deletes = {}
641
578
 
642
- def _cleanup_buckets(dockey, indices_to_cleanup)
643
- indices_to_cleanup.each do |index|
644
- bucket_name = "#{dockey}:#{index}"
645
- _remove_bucket_index(dockey, index) unless @@db.exists(bucket_name)
646
- end if indices_to_cleanup
579
+ inserts_obj_hash.each do |key, obj_set|
580
+ deletes_pairs = nil
581
+ inserts_pairs = nil
582
+ if deletes_obj_hash.has_key?(key)
583
+ deletes_pairs = deletes_obj_hash[key].dup.subtract(obj_set).to_a
584
+ inserts_pairs = obj_set.dup.subtract(deletes_obj_hash[key]).to_a
585
+ # remove the key from the deletes set - we already processed it
586
+ deletes_obj_hash.delete(key)
587
+ else
588
+ # if object is not in the deletes set - then, it's all inserts
589
+ inserts_pairs = obj_set.to_a
590
+ end
591
+ # split resulting pairs
592
+ if inserts_pairs and inserts_pairs.size > 0
593
+ inserts[key] = split_obj_pairs(inserts_pairs)
594
+ end
595
+ if deletes_pairs and deletes_pairs.size > 0
596
+ deletes[key] = split_obj_pairs(deletes_pairs)
597
+ end
598
+ end
599
+ # after we analyzed the inserts__obj_hash
600
+ # => deletes_obj_hash should contain only the unmatched deletes
601
+ deletes_obj_hash.each do |key, obj_set|
602
+ if obj_set.size > 0
603
+ deletes[key] = split_obj_pairs(obj_set.to_a)
604
+ end
647
605
  end
648
606
 
649
- def _put_objects(dockey, data={})
650
- return if data.empty? or not dockey
651
-
607
+ [inserts, deletes]
608
+ end
609
+
610
+ def update_elements(dockey, inserts_elements_map, deletes_elements_map)
611
+ indices_to_cleanup = Set.new
612
+ @db.pipelined do
652
613
  collected_adds = {}
653
- @@db.pipelined do
654
- data.each do |key,obj|
655
- raise ArgumentError, "Invalid value object: #{obj.inspect}. Hash is expected." unless obj.is_a?(Hash)
656
- next if obj.empty? or not key
657
-
658
- obj_bucket_index = _create_obj_index(key)
659
- bucket_name = "#{dockey}:#{obj_bucket_index}"
660
- _add_bucket_index(dockey, obj_bucket_index)
661
- collected_adds[bucket_name] ||= []
662
- collected_adds[bucket_name] << set_obj_element(key, obj)
663
-
614
+ collected_rems = {}
615
+
616
+ inserts_elements_map.each do |element,keypairs|
617
+ key = keypairs[0]
618
+ next if not key or not element or element.size == 0
619
+
620
+ obj_bucket_index = _create_obj_index(key)
621
+ bucket_name = "#{dockey}:#{obj_bucket_index}"
622
+ _add_bucket_index(dockey, obj_bucket_index)
623
+
624
+ collected_adds[bucket_name] ||= []
625
+ collected_adds[bucket_name] << element
626
+ end
627
+
628
+ deletes_elements_map.each do |element,keypairs|
629
+ key = keypairs[0]
630
+ next if not key or not element or element.size == 0
631
+
632
+ obj_bucket_index = _create_obj_index(key)
633
+ bucket_name = "#{dockey}:#{obj_bucket_index}"
634
+ indices_to_cleanup << bucket_name
635
+
636
+ collected_rems[bucket_name] ||= []
637
+ collected_rems[bucket_name] << element
638
+ end
639
+
640
+ # now, perform SREM first, then SADD
641
+ collected_rems.each do |bucket, bucket_data|
642
+ @db.srem(bucket, bucket_data)
643
+ end
644
+ collected_adds.each do |bucket,bucket_data|
645
+ @db.sadd(bucket, bucket_data)
646
+ end
647
+ end
648
+ # now, cleanup buckets if necessary
649
+ _cleanup_buckets(dockey, indices_to_cleanup.to_a)
650
+ end
651
+
652
+ def keys(pattern)
653
+ @db.keys(pattern)
654
+ end
655
+
656
+ # Deletes all keys matching a given mask
657
+ def flush_data(keymask)
658
+ if keymask.to_s[/[*\[\]?]/]
659
+ # If the keymask contains any pattern matching characters
660
+ # Use keys command to find all keys matching pattern (this is extremely expensive)
661
+ # Then delete matches
662
+ keys(keymask).each do |key|
663
+ _delete_doc(key)
664
+ end
665
+ else
666
+ # The keymask doesn't contain pattern matching characters
667
+ # A delete call is all that is needed
668
+ _delete_doc(keymask)
669
+ end
670
+ end
671
+ alias_method :flash_data, :flush_data
672
+
673
+ # Lock a given key and release when provided block is finished
674
+ def lock(dockey,timeout=0,raise_on_expire=false, &block)
675
+ m_lock = get_lock(dockey,timeout,raise_on_expire)
676
+ res = yield
677
+ release_lock(dockey,m_lock)
678
+ res
679
+ end
680
+
681
+ def get_lock(dockey,timeout=0,raise_on_expire=false)
682
+ lock_key = _lock_key(dockey)
683
+ current_time = Time.now.to_i
684
+ ts = current_time+(Rhoconnect.lock_duration || timeout)+1
685
+ loop do
686
+ if not @db.setnx(lock_key,ts)
687
+ current_lock = @db.get(lock_key)
688
+ # ensure lock wasn't released between the setnx and get calls
689
+ if current_lock
690
+ current_lock_timeout = current_lock.to_i
691
+ if raise_on_expire or Rhoconnect.raise_on_expired_lock
692
+ if current_lock_timeout <= current_time
693
+ # lock expired before operation which set it up completed
694
+ # this process cannot continue without corrupting locked data
695
+ raise StoreLockException, "Lock \"#{lock_key}\" expired before it was released"
696
+ end
697
+ else
698
+ if current_lock_timeout <= current_time and
699
+ @db.getset(lock_key,ts).to_i <= current_time
700
+ # previous lock expired and we replaced it with our own
701
+ break
702
+ end
703
+ end
704
+ # lock was released between setnx and get - try to acquire it again
705
+ elsif @db.setnx(lock_key,ts)
706
+ break
707
+ end
708
+ sleep(1)
709
+ current_time = Time.now.to_i
710
+ else
711
+ break #no lock was set, so we set ours and leaving
712
+ end
713
+ end
714
+ return ts
715
+ end
716
+
717
+ # Due to redis bug #140, setnx always returns true so this doesn't work
718
+ # def get_lock(dockey,timeout=0)
719
+ # lock_key = _lock_key(dockey)
720
+ # until @db.setnx(lock_key,1) do
721
+ # sleep(1)
722
+ # end
723
+ # @db.expire(lock_key,timeout+1)
724
+ # Time.now.to_i+timeout+1
725
+ # end
726
+
727
+ def release_lock(dockey,lock,raise_on_expire=false)
728
+ @db.del(_lock_key(dockey)) if raise_on_expire or Rhoconnect.raise_on_expired_lock or (lock >= Time.now.to_i)
729
+ end
730
+
731
+ # Create a copy of srckey in dstkey
732
+ def clone(srckey,dstkey)
733
+ buckets = _get_bucket_indices(srckey)
734
+ if buckets.size
735
+ @db.pipelined do
736
+ buckets.each do |bucket_index|
737
+ _add_bucket_index(dstkey, bucket_index)
738
+ @db.sdiffstore("#{dstkey}:#{bucket_index}", "#{srckey}:#{bucket_index}", '')
664
739
  end
665
- # all SADD operations on a bucket key
666
- # are combined into one - it proves to perform faster
667
- collected_adds.each do |bucket,bucket_data|
668
- @@db.sadd(bucket, bucket_data)
740
+ end
741
+ else
742
+ @db.sdiffstore(dstkey,srckey,'')
743
+ end
744
+ end
745
+
746
+ # Rename temp doc srckey to persist dstkey
747
+ def rename_tmp_data(srckey,dstkey)
748
+ rename(srckey,dstkey,true)
749
+ end
750
+
751
+ # Rename srckey to dstkey
752
+ # also, removes TTL if ordered (normally - it is not necessary)
753
+ def rename(srckey,dstkey,make_persist=false)
754
+ buckets = _get_bucket_indices(srckey)
755
+ if buckets.size
756
+ @db.pipelined do
757
+ @db.del("#{srckey}:indices")
758
+ buckets.each do |bucket_index|
759
+ _add_bucket_index(dstkey, bucket_index)
760
+ @db.rename("#{srckey}:#{bucket_index}", "#{dstkey}:#{bucket_index}")
669
761
  end
762
+ if make_persist
763
+ @db.persist("#{dstkey}:indices")
764
+ buckets.each do |bucket_index|
765
+ @db.persist("#{dstkey}:#{bucket_index}")
766
+ end
767
+ end
768
+ end
769
+ else
770
+ if @db.exists(srckey)
771
+ @db.rename(srckey,dstkey)
772
+ @db.persist(dstkey) if make_persist
670
773
  end
671
774
  end
672
-
673
- def _delete_objects(dockey, data={})
674
- return 0 if data.empty? or not dockey
675
-
676
- deleted_object_count = 0
677
- indices_to_cleanup = Set.new
678
- collected_rems = {}
679
- @@db.pipelined do
680
- data.each do |key, obj|
681
- next if obj.empty? or not key
682
- obj_bucket_index = _create_obj_index(key)
683
- bucket_name = "#{dockey}:#{obj_bucket_index}"
684
- indices_to_cleanup << obj_bucket_index
685
-
686
- collected_rems[bucket_name] ||= []
687
- collected_rems[bucket_name] << set_obj_element(key, obj)
688
- deleted_object_count += 1
775
+ end
776
+
777
+ def put_zdata(dockey,assoc_key,data={},append=false)
778
+ return true unless (dockey and assoc_key and data)
779
+ flush_zdata(dockey) unless append
780
+ current_score = 0
781
+ current_score_data = @db.zrevrange(dockey,0,0,:with_scores => true)
782
+ current_score = current_score_data[-1][1].to_i if current_score_data and current_score_data[-1]
783
+ current_score += 1
784
+ data.each do |key,hash_value|
785
+ unique_record_key = setelement(current_score,assoc_key, key)
786
+ @db.zadd(dockey, current_score, unique_record_key)
787
+ put_data("#{dockey}:#{unique_record_key}",{key => hash_value})
788
+ end
789
+ true
790
+ end
791
+
792
+ # Retrieves set for given dockey,associated key (client_id), obj_hashes
793
+ def get_zdata(dockey)
794
+ data = @db.zrange(dockey, 0, -1)
795
+ ret = []
796
+ keys = []
797
+ unless data.nil?
798
+ scores = []
799
+ data.each do |zsetkey|
800
+ obj_hash = get_data "#{dockey}:#{zsetkey}"
801
+ score,key,objkey = getelement(zsetkey)
802
+ if scores[-1] != score
803
+ ret << obj_hash
804
+ keys << key
805
+ scores << score
806
+ else
807
+ ret[-1].merge!(obj_hash)
689
808
  end
690
-
691
- # all SREM operations on a bucket
692
- # are combined into one
693
- collected_rems.each do |bucket,bucket_data|
694
- @@db.srem(bucket, bucket_data)
809
+ end
810
+ end
811
+ [ret, keys]
812
+ end
813
+
814
+ # Deletes all keys and their hashes from the Redis DB
815
+ def flush_zdata(dockey)
816
+ data = @db.zrange(dockey, 0, -1)
817
+ data.each do |hash_key|
818
+ _delete_doc("#{dockey}:#{hash_key}")
819
+ end
820
+ @db.zremrangebyrank(dockey, 0, -1)
821
+ end
822
+
823
+ def exists?(key)
824
+ @db.exists(key) || @db.exists("#{key}:indices")
825
+ end
826
+
827
+ # low-level operations with sorted sets
828
+ def zadd(dockey, score, value)
829
+ @db.zadd(dockey, score, value)
830
+ end
831
+
832
+ def zrem(dockey, value)
833
+ @db.zrem(dockey, value)
834
+ end
835
+
836
+ def zremrangebyscore(dockey, min_elem, max_elem)
837
+ @db.zremrangebyscore(dockey, min_elem, max_elem)
838
+ end
839
+
840
+ def zscore(dockey, value)
841
+ @db.zscore(dockey, value)
842
+ end
843
+
844
+ def zrevrange(dockey, start, stop)
845
+ @db.zrevrange(dockey, start, stop)
846
+ end
847
+
848
+ def zrange(dockey, start, stop)
849
+ @db.zrange(dockey, start, stop)
850
+ end
851
+
852
+ alias_method :set_value, :put_value
853
+ alias_method :set_data, :put_data
854
+
855
+ # This method should never be accessed by anything except specs
856
+ def db
857
+ return @db
858
+ end
859
+
860
+ private
861
+
862
+ if RUBY_VERSION =~ /1.9/
863
+ def _get_redis(server=nil)
864
+ url = ENV[REDIS_URL] || ENV[REDISTOGO_URL] || nil
865
+ if url
866
+ ConnectionPool::Wrapper.new(:size => Rhoconnect.connection_pool_size,
867
+ :timeout => Rhoconnect.connection_pool_timeout) do
868
+ Redis.connect(:url => url, :timeout => Rhoconnect.redis_timeout, :thread_safe => true)
869
+ end
870
+ elsif server and server.is_a?(String)
871
+ host,port,db,password = server.split(':')
872
+ ConnectionPool::Wrapper.new(:size => Rhoconnect.connection_pool_size,
873
+ :timeout => Rhoconnect.connection_pool_timeout) do
874
+ Redis.connect(:thread_safe => true, :host => host,
875
+ :port => port, :db => db, :password => password, :timeout => Rhoconnect.redis_timeout)
876
+ end
877
+ elsif server and (server.is_a?(Redis) or server.is_a?(ConnectionPool::Wrapper))
878
+ server
879
+ else
880
+ ConnectionPool::Wrapper.new(:size => 5, :timeout => 30) do
881
+ Redis.connect(:timeout => 30, :thread_safe => true)
882
+ end
883
+ end
884
+ end
885
+ else # Ruby 1.8 does not support Connnection Pools
886
+ def _get_redis(server=nil)
887
+ url = ENV[REDIS_URL] || ENV[REDISTOGO_URL] || nil
888
+ if url
889
+ Redis.connect(:url => url, :timeout => Rhoconnect.redis_timeout, :thread_safe => true)
890
+ elsif server and server.is_a?(String)
891
+ host,port,db,password = server.split(':')
892
+ Redis.connect(:thread_safe => true, :host => host,
893
+ :port => port, :db => db, :password => password, :timeout => Rhoconnect.redis_timeout)
894
+ elsif server and (server.is_a?(Redis) or server.is_a?(ConnectionPool::Wrapper))
895
+ server
896
+ else
897
+ Redis.connect(:timeout => 30, :thread_safe => true)
898
+ end
899
+ end
900
+ end # end of if RUBY_VERSION
901
+
902
+
903
+ def _lock_key(dockey)
904
+ "lock:#{dockey}"
905
+ end
906
+
907
+ def _is_reserved?(attrib,value) #:nodoc:
908
+ RESERVED_ATTRIB_NAMES.include? attrib
909
+ end
910
+
911
+ # operations with docs that are split into buckets
912
+ def _delete_doc(dockey)
913
+ # check if this doc has buckets
914
+ if(@db.exists("#{dockey}:indices"))
915
+ buckets_list = _get_buckets(dockey)
916
+ # delete all buckets
917
+ @db.pipelined do
918
+ @db.del("#{dockey}:indices")
919
+ buckets_list.each do |bucket|
920
+ @db.del(bucket)
695
921
  end
696
922
  end
697
- _cleanup_buckets(dockey, indices_to_cleanup.to_a)
698
- deleted_object_count
699
923
  end
700
924
 
701
- def _get_objects(dockey, keys)
702
- return nil unless dockey
703
- res = nil
704
- keys_map = Set.new
705
- buckets = Set.new
706
- keys.each do |key|
925
+ # delete main doc
926
+ @db.del(dockey)
927
+ end
928
+
929
+ # create object's bucket index
930
+ # using SHA1 hashing
931
+ def _create_obj_index(key)
932
+ Digest::SHA1.hexdigest(key)[0..1]
933
+ end
934
+
935
+ def _add_bucket_index(dockey, bucket_index)
936
+ bucket_name = "#{dockey}:#{bucket_index}"
937
+ @db.hsetnx("#{dockey}:indices", bucket_index, bucket_name)
938
+ bucket_name
939
+ end
940
+
941
+ def _remove_bucket_index(dockey, bucket_index)
942
+ @db.hdel("#{dockey}:indices", bucket_index)
943
+ end
944
+
945
+ def _get_bucket_indices(dockey)
946
+ @db.hkeys("#{dockey}:indices")
947
+ end
948
+
949
+ def _get_buckets(dockey)
950
+ @db.hvals("#{dockey}:indices")
951
+ end
952
+
953
+ def _cleanup_buckets(dockey, indices_to_cleanup)
954
+ indices_to_cleanup.each do |index|
955
+ bucket_name = "#{dockey}:#{index}"
956
+ _remove_bucket_index(dockey, index) unless @db.exists(bucket_name)
957
+ end if indices_to_cleanup
958
+ end
959
+
960
+ def _put_objects(dockey, data={}, ttl=0)
961
+ return if data.empty? or not dockey
962
+
963
+ collected_adds = {}
964
+ @db.pipelined do
965
+ data.each do |key,obj|
966
+ raise ArgumentError, "Invalid value object: #{obj.inspect}. Hash is expected." unless obj.is_a?(Hash)
967
+ next if obj.empty? or not key
968
+
707
969
  obj_bucket_index = _create_obj_index(key)
708
970
  bucket_name = "#{dockey}:#{obj_bucket_index}"
709
- keys_map << key
710
- buckets << bucket_name
971
+ _add_bucket_index(dockey, obj_bucket_index)
972
+ collected_adds[bucket_name] ||= []
973
+ collected_adds[bucket_name] << set_obj_element(key, obj)
974
+
711
975
  end
712
- members = @@db.pipelined do
713
- buckets.to_a.each do |bucket_name|
714
- @@db.smembers(bucket_name)
976
+ # all SADD operations on a bucket key
977
+ # are combined into one - it proves to perform faster
978
+ collected_adds.each do |bucket,bucket_data|
979
+ @db.sadd(bucket, bucket_data)
980
+ if ttl > 0
981
+ @db.expire(bucket, ttl)
715
982
  end
716
983
  end
717
- members.each do |bucket_data|
718
- bucket_data.each do |element|
719
- key,pairs = get_obj_key_and_pairs(element)
720
- next unless keys_map.include?(key)
721
- obj = split_obj_pairs(pairs)
722
- next if obj.empty?
723
- res ||= {}
724
- res[key] = obj
725
- end if bucket_data
726
- end if members
727
- res
984
+ @db.expire("#{dockey}:indices", ttl) if ttl > 0
728
985
  end
986
+ end
987
+
988
+ def _delete_objects(dockey, data={})
989
+ return 0 if data.empty? or not dockey
729
990
 
730
- # operations on object elements
731
- def get_obj_element(elem)
732
- key,pairs = get_obj_key_and_pairs(elem)
733
- return unless (key and pairs)
734
- [key,split_obj_pairs(pairs)]
991
+ deleted_object_count = 0
992
+ indices_to_cleanup = Set.new
993
+ collected_rems = {}
994
+ @db.pipelined do
995
+ data.each do |key, obj|
996
+ next if obj.empty? or not key
997
+ obj_bucket_index = _create_obj_index(key)
998
+ bucket_name = "#{dockey}:#{obj_bucket_index}"
999
+ indices_to_cleanup << obj_bucket_index
1000
+
1001
+ collected_rems[bucket_name] ||= []
1002
+ collected_rems[bucket_name] << set_obj_element(key, obj)
1003
+ deleted_object_count += 1
1004
+ end
1005
+
1006
+ # all SREM operations on a bucket
1007
+ # are combined into one
1008
+ collected_rems.each do |bucket,bucket_data|
1009
+ @db.srem(bucket, bucket_data)
1010
+ end
735
1011
  end
736
- def get_obj_key_and_pairs(elem)
737
- pairs = elem.split(":^rho&:")
738
- return unless pairs
739
- [pairs[0], pairs[1..-1]]
1012
+ _cleanup_buckets(dockey, indices_to_cleanup.to_a)
1013
+ deleted_object_count
1014
+ end
1015
+
1016
+ def _get_objects(dockey, keys)
1017
+ return nil unless dockey
1018
+ res = nil
1019
+ keys_map = Set.new
1020
+ buckets = Set.new
1021
+ keys.each do |key|
1022
+ obj_bucket_index = _create_obj_index(key)
1023
+ bucket_name = "#{dockey}:#{obj_bucket_index}"
1024
+ keys_map << key
1025
+ buckets << bucket_name
740
1026
  end
741
- def split_obj_pairs(pairs)
742
- obj = {}
743
- pairs.each do |pair|
744
- attrib,value = pair.split(':',2)
745
- obj[attrib] = value
1027
+ members = @db.pipelined do
1028
+ buckets.to_a.each do |bucket_name|
1029
+ @db.smembers(bucket_name)
746
1030
  end
747
- obj
748
1031
  end
1032
+ members.each do |bucket_data|
1033
+ bucket_data.each do |element|
1034
+ key,pairs = get_obj_key_and_pairs(element)
1035
+ next unless keys_map.include?(key)
1036
+ obj = split_obj_pairs(pairs)
1037
+ next if obj.empty?
1038
+ res ||= {}
1039
+ res[key] = obj
1040
+ end if bucket_data
1041
+ end if members
1042
+ res
1043
+ end
1044
+
1045
+ # operations on object elements
1046
+ def get_obj_element(elem)
1047
+ key,pairs = get_obj_key_and_pairs(elem)
1048
+ return unless (key and pairs)
1049
+ [key,split_obj_pairs(pairs)]
1050
+ end
1051
+ def get_obj_key_and_pairs(elem)
1052
+ pairs = elem.split(":^rho&:")
1053
+ return unless pairs
1054
+ [pairs[0], pairs[1..-1]]
1055
+ end
1056
+ def split_obj_pairs(pairs)
1057
+ obj = {}
1058
+ pairs.each do |pair|
1059
+ attrib,value = pair.split(':',2)
1060
+ obj[attrib] = value
1061
+ end
1062
+ obj
1063
+ end
749
1064
 
750
1065
  # Set Obj Element MUST ensure the order of attribs
751
1066
  # In 1.8.7 Hash keys are not-sorted - therefore
752
1067
  # to ensure same order - we sort them before storing
753
1068
  if RUBY_VERSION =~ /1.8/
754
- def set_obj_element(key, obj)
755
- return unless (key and key.size > 0 and obj and obj.size > 0)
756
- elem = "#{key}"
757
- obj.sort.each do |attrib, value|
758
- unless _is_reserved?(attrib,value)
759
- elem += ":^rho&:#{attrib}:#{value}"
760
- end
1069
+ def set_obj_element(key, obj)
1070
+ return unless (key and key.size > 0 and obj and obj.size > 0)
1071
+ elem = "#{key}"
1072
+ obj.sort.each do |attrib, value|
1073
+ unless _is_reserved?(attrib,value)
1074
+ elem += ":^rho&:#{attrib}:#{value}"
761
1075
  end
762
- elem
763
1076
  end
1077
+ elem
1078
+ end
764
1079
  # in Ruby 1.9.x Hash keys are sorted (always in the same order), so
765
1080
  # we do not need to do redundant sorting
766
1081
  else
@@ -775,6 +1090,5 @@ else
775
1090
  elem
776
1091
  end
777
1092
  end # if Ruby 1.8
778
- end
779
1093
  end
780
1094
  end