jmongo 1.1.2 → 1.1.3

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -5,4 +5,7 @@ gem 'rake'
5
5
  gem 'shoulda'
6
6
  gem 'mocha'
7
7
 
8
-
8
+ if RUBY_PLATFORM !~ /java/
9
+ gem 'bson_ext'
10
+ gem 'mongo'
11
+ end
@@ -32,6 +32,7 @@ GEM
32
32
 
33
33
  PLATFORMS
34
34
  java
35
+ ruby
35
36
 
36
37
  DEPENDENCIES
37
38
  awesome_print (~> 0.4)
data/README.txt CHANGED
@@ -26,7 +26,7 @@ the collection and cursor test suites pass. NOTE: a few (2/3) tests have been sk
26
26
  them to see if they affect you.
27
27
 
28
28
  The Mongoid rspec functional suite runs 2607 examples with 28 failures when using JMongo
29
- My Mongoid repo was forked after this commit (so newer funtionality/spec will be missing)
29
+ My Mongoid repo was forked after this commit (so newer funtionality/specs will be missing)
30
30
  commit 6cc97092bc10535b8b65647a3d14b10ca1b94c8c
31
31
  Author: Bernerd Schaefer <bj.schaefer@gmail.com>
32
32
  Date: Tue Jun 28 12:59:34 2011 +0200
@@ -36,7 +36,7 @@ The failures are classed in this way:
36
36
  * Managing Replica Sets directly
37
37
  * Managing Connection Pools directly
38
38
  * XML serialization
39
- * Ruby RegExp to BSON encode and decode
39
+ * Ruby RegExp to BSON encode and decode (FIXED)
40
40
 
41
41
  I will fix these problems in due course
42
42
 
@@ -46,3 +46,12 @@ JMongo lets the Java driver handle reading from slaves and writing to master, al
46
46
  tested JMongo with Replica Sets yet.
47
47
  If you intend to use the fsync=true uri option to imply safe=true on your queries, at the moment you will also
48
48
  need to specify the w option in the uri. e.g. mongodb://0.0.0.0:27017/?fsync=true;w=1;
49
+
50
+ 2011-10-18
51
+ I have added non-blocking support to the Cursor if it is tailable and bound to a Capped Collection.
52
+ This is to have similar behaviour to the ruby driver as far as test expectations.
53
+ The unblocking behaviour is achieved by inserting a "poison doc" into the capped collection in a timeout thread.
54
+ In the Cursor.new options hash if you set await_data to hash, float or true it will not block on cursor.next.
55
+ a value of true will use the defaults, a value of float will set the timeout whaile a hash will allow you to
56
+ control the timeout as well as your own poison doc and the poison doc equality (lambda/proc) mechanism.
57
+ The default timeout is 0.125 seconds.
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'jmongo'
3
- s.version = '1.1.2'
4
- s.date = '2011-10-10'
3
+ s.version = '1.1.3'
4
+ s.date = '2011-10-18'
5
5
  s.platform = Gem::Platform::RUBY
6
6
  s.authors = ["Chuck Remes","Guy Boertje", "Lee Henson"]
7
7
  s.email = ["cremes@mac.com", "guyboertje@gmail.com", "lee.m.henson@gmail.com"]
@@ -17,6 +17,8 @@ module Mongo
17
17
  class Cursor
18
18
  include Mongo::JavaImpl::Utils
19
19
 
20
+ NEXT_DOCUMENT_TIMEOUT = 0.125
21
+
20
22
  attr_reader :j_cursor, :collection, :selector, :fields,
21
23
  :order, :hint, :snapshot, :timeout,
22
24
  :full_collection_name, :transformer,
@@ -25,7 +27,6 @@ module Mongo
25
27
  def initialize(collection, options={})
26
28
  @collection = collection
27
29
  @j_collection = collection.j_collection
28
- @query_run = false
29
30
  @selector = convert_selector_for_query(options[:selector])
30
31
  @fields = convert_fields_for_query(options[:fields])
31
32
  @admin = options.fetch(:admin, false)
@@ -42,8 +43,25 @@ module Mongo
42
43
  @explain = options[:explain]
43
44
  @socket = options[:socket]
44
45
  @timeout = options.fetch(:timeout, true)
45
- @tailable = options.fetch(:tailable, false)
46
46
  @transformer = options[:transformer]
47
+ @tailable = options.fetch(:tailable, false)
48
+ @await_data = @tailable ? options[:await_data] : nil
49
+ @next_timeout = NEXT_DOCUMENT_TIMEOUT
50
+ @is_poison_function = nil
51
+ case @await_data
52
+ when Hash
53
+ @poison_doc = @await_data.fetch(:poison_doc, default_poison_doc)
54
+ @is_poison_function = @await_data[:is_poison_function]
55
+ @next_timeout = @await_data.fetch(:next_timeout, NEXT_DOCUMENT_TIMEOUT).to_f
56
+ when Numeric
57
+ @poison_doc = default_poison_doc
58
+ @next_timeout = @await_data.to_f
59
+ when TrueClass
60
+ @poison_doc = default_poison_doc
61
+ else
62
+ @poison_doc = nil
63
+ end
64
+ @timeout_thread = TimeoutThread.new(@collection, @poison_doc, @next_timeout) if @tailable && @poison_doc
47
65
 
48
66
  @full_collection_name = "#{@collection.db.name}.#{@collection.name}"
49
67
 
@@ -52,12 +70,13 @@ module Mongo
52
70
 
53
71
  def rewind!
54
72
  close
55
- @query_run = false
56
73
  spawn_cursor
57
74
  end
58
75
 
59
76
  def close
60
- @query_run = true
77
+ if @j_cursor.num_seen == 0 && !@tailable
78
+ @j_cursor.next rescue nil
79
+ end
61
80
  @j_cursor.close
62
81
  end
63
82
 
@@ -74,13 +93,13 @@ module Mongo
74
93
  end
75
94
 
76
95
  def add_option(opt)
77
- check_modifiable
96
+ raise_invalid_op if @j_cursor.num_seen != 0
78
97
  @j_cursor.addOption(opt)
79
98
  options
80
99
  end
81
100
 
82
101
  def options
83
- @j_cursor.getOptions
102
+ @j_cursor.options
84
103
  end
85
104
 
86
105
  def query_opts
@@ -90,7 +109,7 @@ module Mongo
90
109
  end
91
110
 
92
111
  def remove_option(opt)
93
- check_modifiable
112
+ raise_invalid_op if @j_cursor.num_seen != 0
94
113
  @j_cursor.setOptions(options & ~opt)
95
114
  options
96
115
  end
@@ -100,7 +119,15 @@ module Mongo
100
119
  end
101
120
 
102
121
  def next_document
103
- _xform(has_next? ? __next : nil)
122
+ doc = nil
123
+ trap_raise(Mongo::OperationFailure) do
124
+ if @tailable
125
+ doc = __next
126
+ elsif has_next?
127
+ doc = __next
128
+ end
129
+ end
130
+ _xform(doc)
104
131
  end
105
132
  alias :next :next_document
106
133
 
@@ -114,12 +141,15 @@ module Mongo
114
141
  private :_xform
115
142
 
116
143
  def has_next?
117
- @j_cursor.has_next?
144
+ if @tailable
145
+ true
146
+ else
147
+ @j_cursor.has_next?
148
+ end
118
149
  end
119
150
 
120
151
  # iterate directly from the mongo db
121
152
  def each
122
- check_modifiable
123
153
  while has_next?
124
154
  yield next_document
125
155
  end
@@ -127,7 +157,6 @@ module Mongo
127
157
 
128
158
  def _batch_size(size=nil)
129
159
  return if size.nil?
130
- check_modifiable
131
160
  raise ArgumentError, "batch_size requires an integer" unless size.is_a? Integer
132
161
  @batch_size = size
133
162
  end
@@ -140,8 +169,7 @@ module Mongo
140
169
  end
141
170
 
142
171
  def _limit(number_to_return=nil)
143
- return if number_to_return.nil?
144
- check_modifiable
172
+ return if number_to_return.nil? && @limit
145
173
  raise ArgumentError, "limit requires an integer" unless number_to_return.is_a? Integer
146
174
  @limit = number_to_return
147
175
  end
@@ -150,14 +178,13 @@ module Mongo
150
178
  def limit(number_to_return=nil)
151
179
  _limit(number_to_return)
152
180
  wrap_invalid_op do
153
- @j_cursor = @j_cursor.limit(@limit) if @limit
181
+ @j_cursor = @j_cursor.limit(@limit)
154
182
  end
155
183
  self
156
184
  end
157
185
 
158
186
  def _skip(number_to_skip=nil)
159
- return if number_to_skip.nil?
160
- check_modifiable
187
+ return if number_to_skip.nil? && @skip
161
188
  raise ArgumentError, "skip requires an integer" unless number_to_skip.is_a? Integer
162
189
  @skip = number_to_skip
163
190
  end
@@ -166,14 +193,13 @@ module Mongo
166
193
  def skip(number_to_skip=nil)
167
194
  _skip(number_to_skip)
168
195
  wrap_invalid_op do
169
- @j_cursor = @j_cursor.skip(@skip) if @skip
196
+ @j_cursor = @j_cursor.skip(@skip)
170
197
  end
171
198
  self
172
199
  end
173
200
 
174
201
  def _sort(key_or_list=nil, direction=nil)
175
- return if key_or_list.nil?
176
- check_modifiable
202
+ return if key_or_list.nil? && @order
177
203
  @order = prep_sort(key_or_list, direction)
178
204
  end
179
205
  private :_sort
@@ -181,7 +207,7 @@ module Mongo
181
207
  def sort(key_or_list, direction=nil)
182
208
  _sort(key_or_list, direction)
183
209
  wrap_invalid_op do
184
- @j_cursor = @j_cursor.sort(@order) if @order
210
+ @j_cursor = @j_cursor.sort(@order)
185
211
  end
186
212
  self
187
213
  end
@@ -191,11 +217,12 @@ module Mongo
191
217
  end
192
218
 
193
219
  def count(skip_and_limit = false)
194
- if skip_and_limit && @skip && @limit
195
- check_modifiable
196
- @j_cursor.size
197
- else
198
- @j_cursor.count
220
+ wrap_invalid_op do
221
+ if skip_and_limit && @skip && @limit
222
+ @j_cursor.size
223
+ else
224
+ @j_cursor.count
225
+ end
199
226
  end
200
227
  end
201
228
 
@@ -225,11 +252,45 @@ module Mongo
225
252
  Set.new self.to_a
226
253
  end
227
254
 
255
+ def done_size
256
+ @j_cursor.num_seen
257
+ end
258
+
259
+ def to_do_size
260
+ @j_cursor.size - @j_cursor.num_seen
261
+ end
262
+
228
263
  private
229
264
 
265
+ def default_poison_doc
266
+ { 'jmongo_poison_document' => true }
267
+ end
268
+
269
+ def default_is_poison?(doc)
270
+ !!doc['jmongo_poison_document']
271
+ end
272
+
230
273
  def __next
231
- @query_run = true
232
- from_dbobject(@j_cursor.next)
274
+ @timeout_thread.trigger if @tailable
275
+ doc = from_dbobject(@j_cursor.next)
276
+ if @tailable
277
+ if poisoned?(doc)
278
+ nil
279
+ else
280
+ @timeout_thread.cancel
281
+ doc
282
+ end
283
+ else
284
+ doc
285
+ end
286
+ end
287
+
288
+ def poisoned?(doc)
289
+ if @is_poison_function
290
+ @is_poison_function.call(doc)
291
+ else
292
+ default_is_poison?(doc)
293
+ end
233
294
  end
234
295
 
235
296
  # Convert the +:fields+ parameter from a single field name or an array
@@ -260,19 +321,26 @@ module Mongo
260
321
  @j_cursor = @j_cursor.sort(@order) if @order
261
322
  @j_cursor = @j_cursor.skip(@skip) if @skip && @skip > 0
262
323
  @j_cursor = @j_cursor.limit(@limit) if @limit && @limit > 0
324
+ @j_cursor = @j_cursor.hint(@hint) if @hint
325
+ @j_cursor = @j_cursor.snapshot if @snapshot
263
326
  @j_cursor = @j_cursor.batchSize(@batch_size) if @batch_size && @batch_size > 0
264
-
265
- @j_cursor = @j_cursor.addOption JMongo::Bytes::QUERYOPTION_NOTIMEOUT unless @timeout
266
- @j_cursor = @j_cursor.addOption JMongo::Bytes::QUERYOPTION_TAILABLE if @tailable
327
+ opts_bf = @j_cursor.options
328
+ opts_bf = mask_option(opts_bf, JMongo::Bytes::QUERYOPTION_NOTIMEOUT, !@timeout)
329
+ opts_bf = mask_option(opts_bf, JMongo::Bytes::QUERYOPTION_TAILABLE, !!@tailable)
330
+ opts_bf = mask_option(opts_bf, JMongo::Bytes::QUERYOPTION_AWAITDATA, !!@await_data)
331
+ @j_cursor.options = opts_bf
267
332
  end
268
333
 
269
334
  self
270
335
  end
271
336
 
272
- def check_modifiable
273
- if @query_run
274
- raise_invalid_op
337
+ def mask_option(bitfield, bit, set = true)
338
+ if set
339
+ bitfield |= bit
340
+ else
341
+ bitfield &= ~bit
275
342
  end
343
+ bitfield
276
344
  end
277
345
 
278
346
  def wrap_invalid_op
@@ -1,6 +1,48 @@
1
1
  # Copyright (C) 2010 Guy Boertje
2
2
 
3
3
  module Mongo
4
+
5
+ class TimeoutThread
6
+ attr_reader :thread, :timeout
7
+ def initialize(collection, doc, timeout)
8
+ @collection = collection
9
+ @doc = doc
10
+ @timeout = timeout
11
+ @queue = SizedQueue.new(1)
12
+ spawn_thread
13
+ end
14
+
15
+ def trigger
16
+ @queue.push(true) if @queue.length == 0
17
+ end
18
+
19
+ def cancel
20
+ return if @thread[:cancel]
21
+ @thread[:cancel] = true
22
+ end
23
+
24
+ def stop
25
+ @queue.push false
26
+ end
27
+
28
+ private
29
+
30
+ def spawn_thread
31
+ @thread = Thread.new do
32
+ while true
33
+ going = @queue.pop
34
+ break if !going
35
+ sleep @timeout
36
+ unless Thread.current[:cancel]
37
+ @collection.insert(@doc)
38
+ end
39
+ Thread.current[:cancel] = false
40
+ end
41
+ end
42
+ @thread[:cancel] = false
43
+ end
44
+ end
45
+
4
46
  module JavaImpl
5
47
 
6
48
  module NoImplYetClass
@@ -10,6 +52,7 @@ module Mongo
10
52
  end
11
53
 
12
54
  module Utils
55
+
13
56
  def raise_not_implemented
14
57
  raise NoMethodError, "This method hasn't been implemented yet."
15
58
  end
@@ -1,3 +1,3 @@
1
1
  module Mongo
2
- VERSION = '1.1.2'
2
+ VERSION = '1.1.3'
3
3
  end
@@ -878,41 +878,61 @@ describe "Collection" do
878
878
  end
879
879
  end
880
880
 
881
- # describe "Capped collections" do
882
- # before do
883
- # Cfg.db.drop_collection('log')
884
- # @capped = Cfg.db.create_collection('log', :capped => true, :size => 1024)
885
-
886
- # 10.times { |n| @capped.insert({:n => n}) }
887
- # end
888
-
889
- # it "should find using a standard cursor" do
890
- # cursor = @capped.find
891
- # 10.times do
892
- # assert cursor.next_document
893
- # end
894
- # assert_nil cursor.next_document
895
- # @capped.insert({:n => 100})
896
- # assert_nil cursor.next_document
897
- # end
898
-
899
- # it "should fail tailable cursor on a non-capped collection" do
900
- # col = Cfg.db['regular-collection']
901
- # col.insert({:a => 1000})
902
- # tail = Cursor.new(col, :tailable => true, :order => [['$natural', 1]])
903
- # assert_raises OperationFailure do
904
- # tail.next_document
905
- # end
906
- # end
907
-
908
- # it "should find using a tailable cursor" do
909
- # tail = Cursor.new(@capped, :tailable => true, :order => [['$natural', 1]])
910
- # 10.times do
911
- # assert tail.next_document
912
- # end
913
- # assert_nil tail.next_document
914
- # @capped.insert({:n => 100})
915
- # assert tail.next_document
916
- # end
917
- # end
881
+ describe "Capped collections" do
882
+ before do
883
+ Cfg.db.drop_collection('log')
884
+ @capped = Cfg.db.create_collection('log', :capped => true, :max => 1000, :size => 400000)
885
+
886
+ 1000.times { |n| @capped.insert({:n => n}) }
887
+ end
888
+
889
+ it "should find using a standard cursor" do
890
+ cursor = @capped.find
891
+ 1000.times do
892
+ assert cursor.next_document
893
+ end
894
+ assert_nil cursor.next_document
895
+ @capped.insert({:n => 100})
896
+ assert_nil cursor.next_document
897
+ end
898
+
899
+ it "should fail tailable cursor on a non-capped collection" do
900
+ col = Cfg.db['regular-collection']
901
+ col.insert({:a => 1000})
902
+ tail = Mongo::Cursor.new(col, :tailable => true, :order => [['$natural', 1]])
903
+ assert_raises Mongo::OperationFailure do
904
+ tail.next_document
905
+ end
906
+ end
907
+
908
+ it "should find using a tailable cursor" do
909
+ tail = Mongo::Cursor.new(@capped, :timeout => false, :tailable => true, :await_data => true, :order => [['$natural', 1]])
910
+ 1000.times do
911
+ assert tail.next_document
912
+ end
913
+ assert true, tail.has_next?
914
+ assert_nil tail.next_document
915
+ end
916
+
917
+ it "should find using a tailable cursor with await_data set to a float" do
918
+ tail = Mongo::Cursor.new(@capped, :timeout => false, :tailable => true, :await_data => 3.0, :order => [['$natural', 1]])
919
+ 1000.times do
920
+ assert tail.next_document
921
+ end
922
+ assert true, tail.has_next?
923
+ assert_nil tail.next_document
924
+ end
925
+
926
+ it "should find using a tailable cursor with await_data set to hash" do
927
+ await = {:poison_doc => {'pppppoisoned' => 42},
928
+ :is_poison_function => lambda { |doc| doc['pppppoisoned'] == 42 },
929
+ :next_timeout => 2.0}
930
+ tail = Mongo::Cursor.new(@capped, :timeout => false, :tailable => true, :await_data => await, :order => [['$natural', 1]])
931
+ 1000.times do
932
+ assert tail.next_document
933
+ end
934
+ assert true, tail.has_next?
935
+ assert_nil tail.next_document
936
+ end
937
+ end
918
938
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jmongo
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.2
4
+ version: 1.1.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -11,11 +11,11 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2011-10-10 00:00:00.000000000 Z
14
+ date: 2011-10-18 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: require_all
18
- requirement: &10546520 !ruby/object:Gem::Requirement
18
+ requirement: &10865120 !ruby/object:Gem::Requirement
19
19
  none: false
20
20
  requirements:
21
21
  - - ~>
@@ -23,10 +23,10 @@ dependencies:
23
23
  version: '1.2'
24
24
  type: :runtime
25
25
  prerelease: false
26
- version_requirements: *10546520
26
+ version_requirements: *10865120
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: awesome_print
29
- requirement: &10546040 !ruby/object:Gem::Requirement
29
+ requirement: &10864640 !ruby/object:Gem::Requirement
30
30
  none: false
31
31
  requirements:
32
32
  - - ~>
@@ -34,10 +34,10 @@ dependencies:
34
34
  version: '0.4'
35
35
  type: :development
36
36
  prerelease: false
37
- version_requirements: *10546040
37
+ version_requirements: *10864640
38
38
  - !ruby/object:Gem::Dependency
39
39
  name: fuubar
40
- requirement: &10545480 !ruby/object:Gem::Requirement
40
+ requirement: &10864180 !ruby/object:Gem::Requirement
41
41
  none: false
42
42
  requirements:
43
43
  - - ~>
@@ -45,10 +45,10 @@ dependencies:
45
45
  version: '0.0'
46
46
  type: :development
47
47
  prerelease: false
48
- version_requirements: *10545480
48
+ version_requirements: *10864180
49
49
  - !ruby/object:Gem::Dependency
50
50
  name: rspec
51
- requirement: &10544920 !ruby/object:Gem::Requirement
51
+ requirement: &10863300 !ruby/object:Gem::Requirement
52
52
  none: false
53
53
  requirements:
54
54
  - - ~>
@@ -56,7 +56,7 @@ dependencies:
56
56
  version: '2.6'
57
57
  type: :development
58
58
  prerelease: false
59
- version_requirements: *10544920
59
+ version_requirements: *10863300
60
60
  description: Thin jruby wrapper around Mongo Java Driver
61
61
  email:
62
62
  - cremes@mac.com
@@ -183,7 +183,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
183
183
  version: '0'
184
184
  requirements: []
185
185
  rubyforge_project:
186
- rubygems_version: 1.8.6
186
+ rubygems_version: 1.8.10
187
187
  signing_key:
188
188
  specification_version: 3
189
189
  summary: Thin ruby wrapper around Mongo Java Driver; for JRuby only