rhoconnect 3.3.6 → 3.4.2

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 (152) hide show
  1. data/CHANGELOG.md +40 -4
  2. data/Gemfile +2 -2
  3. data/Gemfile.lock +27 -25
  4. data/bench/benchapp/Gemfile +7 -27
  5. data/bench/benchapp/config.ru +9 -31
  6. data/bench/blobapp/Gemfile +7 -27
  7. data/bench/blobapp/config.ru +9 -29
  8. data/bench/lib/bench.rb +8 -1
  9. data/bench/lib/bench/test_data.rb +4 -1
  10. data/bench/scripts/blob_cud_script.rb +4 -0
  11. data/bench/scripts/cud_script.rb +7 -1
  12. data/bench/scripts/helpers.rb +1 -1
  13. data/bench/scripts/test_query_script.rb +20 -7
  14. data/bench/spec/bench_spec_helper.rb +3 -1
  15. data/bin/rhoconnect +22 -12
  16. data/commands/{commands/dtach_commands → dtach}/dtach_about.rb +0 -0
  17. data/commands/{commands/dtach_commands → dtach}/dtach_install.rb +0 -0
  18. data/commands/{commands/redis_commands → dtach}/redis_attach.rb +0 -0
  19. data/commands/execute.rb +14 -15
  20. data/commands/generators/update.rb +26 -0
  21. data/commands/{commands/redis_commands → redis}/redis_about.rb +0 -0
  22. data/commands/redis/redis_download.rb +13 -0
  23. data/commands/redis/redis_install.rb +26 -0
  24. data/commands/{commands/redis_commands → redis}/redis_make.rb +0 -0
  25. data/commands/{commands/redis_commands → redis}/redis_restart.rb +3 -2
  26. data/commands/{commands/redis_commands → redis}/redis_start.rb +0 -0
  27. data/commands/{commands/redis_commands → redis}/redis_startbg.rb +0 -0
  28. data/commands/{commands/redis_commands → redis}/redis_stop.rb +3 -2
  29. data/commands/{commands/rhoconnect → rhoconnect}/clean_start.rb +0 -0
  30. data/commands/{commands/rhoconnect → rhoconnect}/config.rb +7 -2
  31. data/commands/{commands/rhoconnect → rhoconnect}/create_user.rb +0 -0
  32. data/commands/{commands/rhoconnect → rhoconnect}/delete_device.rb +0 -0
  33. data/commands/{commands/rhoconnect → rhoconnect}/delete_user.rb +0 -0
  34. data/commands/{commands/rhoconnect → rhoconnect}/flushdb.rb +4 -4
  35. data/commands/{commands/rhoconnect → rhoconnect}/get_token.rb +0 -0
  36. data/commands/{commands/rhoconnect → rhoconnect}/reset.rb +0 -0
  37. data/commands/{commands/rhoconnect → rhoconnect}/reset_refresh.rb +0 -0
  38. data/commands/{commands/rhoconnect → rhoconnect}/restart.rb +0 -0
  39. data/commands/{commands/rhoconnect → rhoconnect}/secret.rb +0 -0
  40. data/commands/{commands/rhoconnect → rhoconnect}/set_admin_password.rb +0 -0
  41. data/commands/{commands/rhoconnect → rhoconnect}/spec.rb +0 -0
  42. data/commands/rhoconnect/start.rb +27 -0
  43. data/commands/{commands/rhoconnect → rhoconnect}/startbg.rb +0 -0
  44. data/commands/{commands/rhoconnect → rhoconnect}/startdebug.rb +2 -2
  45. data/commands/{commands/rhoconnect → rhoconnect}/stop.rb +3 -4
  46. data/commands/{commands/rhoconnect → rhoconnect}/version.rb +0 -0
  47. data/commands/{commands/rhoconnect → rhoconnect}/web.rb +0 -0
  48. data/commands/rhoconnect_attach/attach.rb +10 -0
  49. data/commands/rhoconnect_console/console.rb +16 -0
  50. data/commands/{commands/rhoconnect → rhoconnect_console}/console_helper.rb +0 -0
  51. data/commands/{commands/rhoconnect → rhoconnect_war}/war.rb +0 -0
  52. data/commands/{commands/redis_commands → utilities}/redis_runner.rb +22 -19
  53. data/commands/utilities/utilities.rb +6 -0
  54. data/doc/benchmarks.txt +2 -2
  55. data/doc/bulk-sync.txt +12 -1
  56. data/doc/client-java.txt +3 -3
  57. data/doc/client-objc.txt +1 -1
  58. data/doc/client.txt +5 -5
  59. data/doc/command-line.txt +80 -135
  60. data/doc/deploying.txt +119 -12
  61. data/doc/extending-rhoconnect-server.txt +1 -1
  62. data/doc/heroku-addon.txt +119 -23
  63. data/doc/install.txt +101 -39
  64. data/doc/java-plugin.txt +2 -2
  65. data/doc/licensing.txt +1 -1
  66. data/doc/plugin-intro.txt +3 -1
  67. data/doc/preparing-production.txt +4 -4
  68. data/doc/public/cli.txt +2 -2
  69. data/doc/push-backend-setup.txt +11 -1
  70. data/doc/push-client-setup.txt +72 -2
  71. data/doc/push-server-setup.txt +129 -8
  72. data/doc/rails-plugin.txt +245 -40
  73. data/doc/rest-api.txt +10 -6
  74. data/doc/rhoconnect-calculator.txt +237 -0
  75. data/doc/rhoconnect-redis-stack.txt +35 -0
  76. data/doc/session-and-configuration.txt +24 -0
  77. data/doc/settings.txt +51 -41
  78. data/doc/source-adapters.txt +45 -45
  79. data/doc/stats-middleware.txt +2 -2
  80. data/doc/supported-platforms.txt +6 -6
  81. data/doc/testing.txt +2 -2
  82. data/doc/tutorial.txt +63 -63
  83. data/examples/simple/Gemfile +7 -35
  84. data/examples/simple/config.ru +8 -26
  85. data/examples/simple/sources/product.rb +6 -6
  86. data/generators/rhoconnect.rb +5 -0
  87. data/generators/templates/application/Gemfile +7 -37
  88. data/generators/templates/application/Rakefile +8 -0
  89. data/generators/templates/application/config.ru +12 -31
  90. data/generators/templates/application/rcgemfile +44 -0
  91. data/generators/templates/application/settings/settings.yml +7 -5
  92. data/install.sh +4 -4
  93. data/installer/unix-like/create_texts.rb +7 -2
  94. data/installer/unix-like/rho_connect_install_constants.rb +2 -2
  95. data/installer/unix-like/rho_connect_install_installers.rb +1 -16
  96. data/lib/rhoconnect.rb +51 -38
  97. data/lib/rhoconnect/api/app/query.rb +4 -1
  98. data/lib/rhoconnect/api/app/search.rb +4 -1
  99. data/lib/rhoconnect/api/client/list_client_docs.rb +3 -1
  100. data/lib/rhoconnect/api/user/ping.rb +1 -5
  101. data/lib/rhoconnect/application/init.rb +43 -0
  102. data/lib/rhoconnect/async.rb +11 -6
  103. data/lib/rhoconnect/client_sync.rb +30 -37
  104. data/lib/rhoconnect/document.rb +4 -0
  105. data/lib/rhoconnect/graph_helper.rb +74 -56
  106. data/lib/rhoconnect/middleware/helpers.rb +4 -0
  107. data/lib/rhoconnect/ping.rb +1 -0
  108. data/lib/rhoconnect/ping/gcm.rb +58 -0
  109. data/lib/rhoconnect/predefined_adapters/bench_adapter.rb +7 -1
  110. data/lib/rhoconnect/source.rb +70 -56
  111. data/lib/rhoconnect/source_sync.rb +33 -5
  112. data/lib/rhoconnect/store.rb +358 -110
  113. data/lib/rhoconnect/user.rb +8 -0
  114. data/lib/rhoconnect/utilities.rb +16 -14
  115. data/lib/rhoconnect/version.rb +1 -1
  116. data/lib/rhoconnect/web-console/models/client.js +1 -1
  117. data/lib/rhoconnect/web-console/public/UNVR67bold.ttf +0 -0
  118. data/lib/rhoconnect/web-console/public/bootstrap.css +6 -0
  119. data/lib/rhoconnect/web-console/public/logo.png +0 -0
  120. data/lib/rhoconnect/web-console/server.rb +13 -11
  121. data/lib/rhoconnect/web-console/templates/index.erb +5 -5
  122. data/lib/rhoconnect/web-console/templates/jqplot.erb +1 -0
  123. data/lib/rhoconnect/web-console/views/doc.js +0 -4
  124. data/lib/rhoconnect/web-console/views/home.js +2 -1
  125. data/lib/rhoconnect/web-console/views/new_ping.js +11 -6
  126. data/lib/rhoconnect/web-console/views/stats.js +9 -5
  127. data/rhoconnect.gemspec +6 -4
  128. data/spec/api/app/fast_update_spec.rb +2 -2
  129. data/spec/api/source/get_source_params_spec.rb +1 -0
  130. data/spec/apps/rhotestapp/settings/settings.yml +5 -5
  131. data/spec/client_sync_spec.rb +3 -14
  132. data/spec/perf/perf_spec_helper.rb +11 -7
  133. data/spec/perf/store_perf_spec.rb +88 -11
  134. data/spec/ping/gcm_spec.rb +99 -0
  135. data/spec/server/server_spec.rb +7 -0
  136. data/spec/server/stats_spec.rb +9 -2
  137. data/spec/source_sync_spec.rb +29 -0
  138. data/spec/spec_helper.rb +40 -38
  139. data/spec/stats/record_spec.rb +18 -9
  140. data/spec/store_spec.rb +128 -19
  141. data/spec/testdata/10000-data.txt +0 -0
  142. data/spec/testdata/5-data.txt +0 -0
  143. data/spec/testdata/5000-data.txt +0 -0
  144. data/tasks/jasmine.rake +1 -0
  145. data/tasks/redis.rake +16 -13
  146. metadata +71 -39
  147. data/commands/commands/redis_commands/redis_download.rb +0 -33
  148. data/commands/commands/redis_commands/redis_install.rb +0 -26
  149. data/commands/commands/rhoconnect/attach.rb +0 -8
  150. data/commands/commands/rhoconnect/console.rb +0 -15
  151. data/commands/commands/rhoconnect/start.rb +0 -18
  152. data/commands/utilities/dtach_installed.rb +0 -10
@@ -1,4 +1,6 @@
1
1
  require 'digest/sha1'
2
+ require 'set'
3
+ require 'connection_pool'
2
4
 
3
5
  module Rhoconnect
4
6
 
@@ -18,7 +20,7 @@ module Rhoconnect
18
20
  def create(server=nil)
19
21
  @@db ||= _get_redis(server)
20
22
  raise "Error connecting to Redis store." unless @@db and
21
- (@@db.is_a?(Redis) or @@db.is_a?(Redis::Client))
23
+ (@@db.is_a?(Redis) or @@db.is_a?(Redis::Client) or @@db.is_a?(ConnectionPool::Wrapper))
22
24
  end
23
25
 
24
26
  def start_transaction
@@ -53,7 +55,7 @@ module Rhoconnect
53
55
  end
54
56
 
55
57
  def put_object(dockey, key, data={})
56
- _put_object(dockey, key, data)
58
+ _put_objects(dockey, {key => data})
57
59
  end
58
60
 
59
61
  # Adds set with given data, replaces existing set
@@ -64,12 +66,7 @@ module Rhoconnect
64
66
  flash_data(dockey) unless append
65
67
  # Inserts a hash or array
66
68
  if data.is_a?Hash
67
- @@db.pipelined do
68
- data.each do |key,value|
69
- raise ArgumentError, "Invalid value object: #{value.inspect}. Hash is expected." unless value.is_a?(Hash)
70
- _put_object(dockey, key, value)
71
- end
72
- end
69
+ _put_objects(dockey, data)
73
70
  else
74
71
  put_list(dockey,data,append)
75
72
  end
@@ -81,8 +78,8 @@ module Rhoconnect
81
78
  if dockey and data
82
79
  flash_data(dockey) unless append
83
80
  @@db.pipelined do
84
- data.each do |value|
85
- @@db.rpush(dockey, value)
81
+ data.each do |element|
82
+ @@db.rpush(dockey, element)
86
83
  end
87
84
  end
88
85
  end
@@ -95,29 +92,45 @@ module Rhoconnect
95
92
  return 0 unless dockey and data
96
93
 
97
94
  new_object_count = 0
98
- objs = get_objects(dockey, data.keys)
95
+ objs = get_objects(dockey, data.keys) || {}
96
+
97
+ collected_adds = {}
98
+ collected_rems = {}
99
+ my_bucket = nil
99
100
  @@db.pipelined do
100
- data.each do |key,value|
101
+ data.each do |key,obj|
101
102
  is_create = objs[key].nil?
102
103
  new_object_count += 1 if is_create
103
104
  obj_bucket = _add_bucket_index(dockey, "#{_create_obj_index(key)}")
104
- value.each do |attrib,value|
105
- next if _is_reserved?(attrib, value)
106
-
107
- new_element = setelement(key,attrib,value)
108
- element_exists = is_create ? false : objs[key].has_key?(attrib)
109
- if element_exists
110
- existing_element = setelement(key,attrib,objs[key][attrib])
111
- if existing_element != new_element
112
- @@db.srem(obj_bucket, existing_element)
113
- @@db.sadd(obj_bucket, new_element)
114
- end
115
- else
116
- @@db.sadd(obj_bucket, new_element)
117
- end
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
118
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)
119
127
  end
120
128
  end
129
+
130
+
131
+ #data1 = @@db.smembers(my_bucket)
132
+ #puts "data1 is #{data1.inspect}"
133
+
121
134
  new_object_count
122
135
  end
123
136
 
@@ -125,28 +138,14 @@ module Rhoconnect
125
138
  def delete_objects(dockey,data=[])
126
139
  return 0 unless dockey and data
127
140
 
128
- deleted_object_count = 0
129
141
  objs = get_objects(dockey, data)
130
- @@db.pipelined do
131
- data.each do |id|
132
- if _delete_object(dockey, id, objs[id])
133
- deleted_object_count += 1
134
- end
135
- end
136
- end
137
- _cleanup_buckets(dockey, objs.keys)
138
- deleted_object_count
142
+ _delete_objects(dockey, objs)
139
143
  end
140
144
 
141
145
  # Deletes data from a given doctype,source,user
142
146
  def delete_data(dockey,data={})
143
147
  if dockey and data
144
- @@db.pipelined do
145
- data.each do |key,value|
146
- _delete_object(dockey, key, value)
147
- end
148
- end
149
- _cleanup_buckets(dockey, data.keys)
148
+ _delete_objects(dockey, data)
150
149
  end
151
150
  true
152
151
  end
@@ -180,15 +179,12 @@ module Rhoconnect
180
179
  end
181
180
 
182
181
  def get_object(dockey, key)
183
- _get_object(dockey, key)
182
+ res = _get_objects(dockey, [key])
183
+ (res and res.size > 0) ? res.values[0] : nil
184
184
  end
185
185
 
186
186
  def get_objects(dockey, keys)
187
- res = {}
188
- keys.each do |key|
189
- res[key] = _get_object(dockey, key)
190
- end if keys
191
- res
187
+ _get_objects(dockey, keys)
192
188
  end
193
189
 
194
190
  # Retrieves set for given dockey,source,user
@@ -197,14 +193,18 @@ module Rhoconnect
197
193
  if dockey
198
194
  if type == Hash
199
195
  buckets = _get_buckets(dockey)
200
- buckets.each do |bucket|
201
- members = @@db.smembers(bucket)
202
- members.each do |element|
203
- key,attrib,value = getelement(element)
204
- res[key] = {} unless res[key]
205
- res[key].merge!({attrib => value})
206
- end if members
207
- end if buckets
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
208
  else
209
209
  res = get_list(dockey)
210
210
  end
@@ -221,35 +221,116 @@ module Rhoconnect
221
221
  end
222
222
 
223
223
  # Retrieves diff data hash between two sets
224
+ # each entry is in the form of DIFF_OBJ_ELEMENT => [OBJ_KEY, OBJ_DATA_PAIRS]
224
225
  def get_diff_data(src_dockey,dst_dockey,p_size=nil)
225
226
  res = {}
226
227
  if src_dockey and dst_dockey
227
228
  # obtain combined indices
228
229
  indices = @@db.hgetall("#{dst_dockey}:indices")
229
- indices.merge!(@@db.hgetall("#{dst_dockey}:indices"))
230
230
  indices.keys.each do |index|
231
231
  dst_bucket_name = "#{dst_dockey}:#{index}"
232
232
  src_bucket_name = "#{src_dockey}:#{index}"
233
- @@db.sdiff(dst_bucket_name,src_bucket_name).each do |element|
234
- key,attrib,value = getelement(element)
235
- res[key] = {} unless res[key]
236
- res[key].merge!({attrib => value})
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)
237
239
  end
238
- break if p_size and (res.size >= p_size)
239
240
  end
240
241
  end
241
- if p_size
242
- diff = {}
243
- page_size = p_size
244
- res.each do |key,item|
245
- diff[key] = item
246
- page_size -= 1
247
- break if page_size <= 0
242
+ res
243
+ end
244
+
245
+ 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)
248
281
  end
249
- diff
250
- else
251
- res
252
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]
292
+ end
293
+
294
+ 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)
253
334
  end
254
335
 
255
336
  # Deletes all keys matching a given mask
@@ -367,7 +448,7 @@ module Rhoconnect
367
448
  data.each do |key,hash_value|
368
449
  unique_record_key = setelement(current_score,assoc_key, key)
369
450
  Store.db.zadd(dockey, current_score, unique_record_key)
370
- Store.put_data("#{dockey}:#{unique_record_key}",{key => hash_value})
451
+ Store.put_data("#{dockey}:#{unique_record_key}",{key => hash_value})
371
452
  end
372
453
  true
373
454
  end
@@ -403,6 +484,62 @@ module Rhoconnect
403
484
  Store.db.zremrangebyrank(dockey, 0, -1)
404
485
  end
405
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)
541
+ end
542
+
406
543
  def exists?(key)
407
544
  @@db.exists(key) || @@db.exists("#{key}:indices")
408
545
  end
@@ -411,20 +548,46 @@ module Rhoconnect
411
548
  alias_method :set_data, :put_data
412
549
 
413
550
  private
551
+ if RUBY_VERSION =~ /1.9/
414
552
  def _get_redis(server=nil)
415
553
  url = ENV[REDIS_URL] || ENV[REDISTOGO_URL] || nil
416
554
  if url
417
- Redis.connect(:url => 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
418
559
  elsif server and server.is_a?(String)
419
560
  host,port,db,password = server.split(':')
420
- Redis.new(:thread_safe => true, :host => host,
421
- :port => port, :db => db, :password => password)
422
- elsif server and server.is_a?(Redis)
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))
423
567
  server
424
568
  else
425
- Redis.new(:thread_safe => true)
569
+ ConnectionPool::Wrapper.new(:size => 5, :timeout => 30) do
570
+ Redis.connect(:timeout => 30, :thread_safe => true)
571
+ end
426
572
  end
427
573
  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)
587
+ end
588
+ end
589
+ end # end of if RUBY_VERSION
590
+
428
591
 
429
592
  def _lock_key(dockey)
430
593
  "lock:#{dockey}"
@@ -476,57 +639,142 @@ module Rhoconnect
476
639
  @@db.hvals("#{dockey}:indices")
477
640
  end
478
641
 
479
- def _cleanup_buckets(dockey, keys)
480
- keys.each do |key|
481
- obj_index = _create_obj_index(key)
482
- bucket_name = "#{dockey}:#{obj_index}"
483
- _remove_bucket_index(dockey, obj_index) unless @@db.exists(bucket_name)
484
- end if keys
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
485
647
  end
486
648
 
487
- def _put_object(dockey, key, obj={})
488
- return if obj.empty? or not dockey or not key
649
+ def _put_objects(dockey, data={})
650
+ return if data.empty? or not dockey
489
651
 
490
- obj_bucket_index = _create_obj_index(key)
491
- bucket_name = "#{dockey}:#{obj_bucket_index}"
492
- _add_bucket_index(dockey, obj_bucket_index)
652
+ collected_adds = {}
493
653
  @@db.pipelined do
494
- obj.each do |attrib, value|
495
- unless _is_reserved?(attrib,value)
496
- @@db.sadd(bucket_name,setelement(key,attrib,value))
497
- end
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
+
664
+ 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)
498
669
  end
499
670
  end
500
671
  end
501
672
 
502
- def _delete_object(dockey, key, obj)
503
- return false if not key or not dockey or not obj
673
+ def _delete_objects(dockey, data={})
674
+ return 0 if data.empty? or not dockey
504
675
 
505
- obj_bucket_index = _create_obj_index(key)
506
- bucket_name = "#{dockey}:#{obj_bucket_index}"
676
+ deleted_object_count = 0
677
+ indices_to_cleanup = Set.new
678
+ collected_rems = {}
507
679
  @@db.pipelined do
508
- obj.each do |attrib,value|
509
- @@db.srem(bucket_name, setelement(key,attrib,value))
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
689
+ 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)
510
695
  end
511
696
  end
512
- true
697
+ _cleanup_buckets(dockey, indices_to_cleanup.to_a)
698
+ deleted_object_count
513
699
  end
514
700
 
515
- def _get_object(dockey, key)
701
+ def _get_objects(dockey, keys)
516
702
  return nil unless dockey
517
703
  res = nil
518
- obj_bucket_index = _create_obj_index(key)
519
- bucket_name = "#{dockey}:#{obj_bucket_index}"
520
- key_str_size = "#{key}".size
521
- members = @@db.smembers(bucket_name)
522
- members.each do |element|
523
- next unless element[0..key_str_size - 1] == key
524
- obj_id,attrib,value = getelement(element)
525
- res ||= {}
526
- res[attrib] = value
704
+ keys_map = Set.new
705
+ buckets = Set.new
706
+ keys.each do |key|
707
+ obj_bucket_index = _create_obj_index(key)
708
+ bucket_name = "#{dockey}:#{obj_bucket_index}"
709
+ keys_map << key
710
+ buckets << bucket_name
711
+ end
712
+ members = @@db.pipelined do
713
+ buckets.to_a.each do |bucket_name|
714
+ @@db.smembers(bucket_name)
715
+ end
716
+ 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
527
726
  end if members
528
727
  res
529
728
  end
729
+
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)]
735
+ end
736
+ def get_obj_key_and_pairs(elem)
737
+ pairs = elem.split(":^rho&:")
738
+ return unless pairs
739
+ [pairs[0], pairs[1..-1]]
740
+ end
741
+ def split_obj_pairs(pairs)
742
+ obj = {}
743
+ pairs.each do |pair|
744
+ attrib,value = pair.split(':',2)
745
+ obj[attrib] = value
746
+ end
747
+ obj
748
+ end
749
+
750
+ # Set Obj Element MUST ensure the order of attribs
751
+ # In 1.8.7 Hash keys are not-sorted - therefore
752
+ # to ensure same order - we sort them before storing
753
+ 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
761
+ end
762
+ elem
763
+ end
764
+ # in Ruby 1.9.x Hash keys are sorted (always in the same order), so
765
+ # we do not need to do redundant sorting
766
+ else
767
+ def set_obj_element(key, obj)
768
+ return unless (key and key.size > 0 and obj and obj.size > 0)
769
+ elem = "#{key}"
770
+ obj.each do |attrib, value|
771
+ unless _is_reserved?(attrib,value)
772
+ elem += ":^rho&:#{attrib}:#{value}"
773
+ end
774
+ end
775
+ elem
776
+ end
777
+ end # if Ruby 1.8
530
778
  end
531
779
  end
532
780
  end