diff --git a/Rakefile b/Rakefile index 1b68779..00e6959 100644 --- a/Rakefile +++ b/Rakefile @@ -26,8 +26,8 @@ namespace :build do jar_dir = File.join(java_dir, 'jar') jruby_jar = File.join(jar_dir, 'jruby.jar') - mongo_jar = File.join(jar_dir, 'mongo.jar') - bson_jar = File.join(jar_dir, 'bson.jar') + mongo_jar = File.join(jar_dir, 'mongo-2.2.jar') + bson_jar = File.join(jar_dir, 'bson-2.2.jar') src_base = File.join(java_dir, 'src') @@ -65,7 +65,7 @@ namespace :test do Rake::Task['test:pooled_threading'].invoke Rake::Task['test:drop_databases'].invoke end - + Rake::TestTask.new(:unit) do |t| t.test_files = FileList['test/unit/*_test.rb'] t.verbose = true @@ -142,7 +142,7 @@ namespace :test do end Rake::TestTask.new(:bson) do |t| - t.test_files = FileList['test/mongo_bson/*_test.rb'] + t.test_files = FileList['test/bson/*_test.rb'] t.verbose = true end diff --git a/ext/java/jar/bson-2.2.jar b/ext/java/jar/bson-2.2.jar new file mode 100644 index 0000000..d30ba60 Binary files /dev/null and b/ext/java/jar/bson-2.2.jar differ diff --git a/ext/java/jar/bson.jar b/ext/java/jar/bson.jar deleted file mode 100644 index 806d22c..0000000 Binary files a/ext/java/jar/bson.jar and /dev/null differ diff --git a/ext/java/jar/jbson.jar b/ext/java/jar/jbson.jar index b0250cc..2c90cf3 100644 Binary files a/ext/java/jar/jbson.jar and b/ext/java/jar/jbson.jar differ diff --git a/ext/java/jar/mongo.jar b/ext/java/jar/mongo-2.2.jar similarity index 51% rename from ext/java/jar/mongo.jar rename to ext/java/jar/mongo-2.2.jar index 15b0a96..80e2e25 100644 Binary files a/ext/java/jar/mongo.jar and b/ext/java/jar/mongo-2.2.jar differ diff --git a/ext/java/src/org/jbson/RubyBSONEncoder.class b/ext/java/src/org/jbson/RubyBSONEncoder.class index 2dee06b..1073651 100644 Binary files a/ext/java/src/org/jbson/RubyBSONEncoder.class and b/ext/java/src/org/jbson/RubyBSONEncoder.class differ diff --git a/ext/java/src/org/jbson/RubyBSONEncoder.java b/ext/java/src/org/jbson/RubyBSONEncoder.java index fe4bb42..e107a96 100644 --- a/ext/java/src/org/jbson/RubyBSONEncoder.java +++ b/ext/java/src/org/jbson/RubyBSONEncoder.java @@ -165,6 +165,13 @@ public class RubyBSONEncoder extends BSONEncoder { if ( temp instanceof RubyArray ) transientFields = (RubyArray)temp; } + else { + if ( _rbHashHasKey( (RubyHash)o, "_id" ) && _rbHashHasKey( (RubyHash)o, _idAsSym ) ) { + ((RubyHash)o).fastDelete(_idAsSym); + } + + + } // Not sure we should invoke this way. Depends on if we can access the OrderedHash. RubyArray keys = (RubyArray)JavaEmbedUtils.invokeMethod( _runtime, o , "keys" , new Object[] {} @@ -383,9 +390,9 @@ public class RubyBSONEncoder extends BSONEncoder { String klass = JavaEmbedUtils.invokeMethod(_runtime, val, "class", new Object[] {}, Object.class).toString(); - _rbRaise( (RubyClass)_rbclsInvalidDocument, - "Cannot serialize " + klass + " as a BSON type; " + - "it either isn't supported or won't translate to BSON."); + _rbRaise( (RubyClass)_rbclsInvalidDocument, + "Cannot serialize " + klass + " as a BSON type; " + + "it either isn't supported or won't translate to BSON."); } } @@ -436,8 +443,30 @@ public class RubyBSONEncoder extends BSONEncoder { final int sizePos = _buf.getPosition(); _buf.writeInt( 0 ); - for ( Map.Entry entry : (Set)m.entrySet() ) - _putObjectField( entry.getKey().toString() , entry.getValue() ); + RubyArray keys = (RubyArray)JavaEmbedUtils.invokeMethod( _runtime, m , "keys" , new Object[] {} , Object.class); + + for (Iterator i = keys.iterator(); i.hasNext(); ) { + + Object hashKey = i.next(); + + // Convert the key into a Java String + String str = ""; + if( hashKey instanceof String) { + str = hashKey.toString(); + } + else if (hashKey instanceof RubyString) { + str = ((RubyString)hashKey).asJavaString(); + } + else if (hashKey instanceof RubySymbol) { + str = ((RubySymbol)hashKey).asJavaString(); + } + + RubyObject val = (RubyObject)_rbHashGet( (RubyHash)m, hashKey ); + _putObjectField( str , (Object)val ); + } + + //for ( Map.Entry entry : (Set)m.entrySet() ) + // _putObjectField( entry.getKey().toString() , entry.getValue() ); _buf.write( EOO ); _buf.writeInt( sizePos , _buf.getPosition() - sizePos ); diff --git a/lib/bson.rb b/lib/bson.rb index a405c2a..37e0569 100644 --- a/lib/bson.rb +++ b/lib/bson.rb @@ -34,8 +34,8 @@ end if RUBY_PLATFORM =~ /java/ jar_dir = File.join(File.dirname(__FILE__), '..', 'ext', 'java', 'jar') - require File.join(jar_dir, 'mongo.jar') - require File.join(jar_dir, 'bson.jar') + require File.join(jar_dir, 'mongo-2.2.jar') + require File.join(jar_dir, 'bson-2.2.jar') require File.join(jar_dir, 'jbson.jar') require 'bson/bson_java' module BSON diff --git a/test/bson/bson_test.rb b/test/bson/bson_test.rb index b2f3c88..c91a510 100644 --- a/test/bson/bson_test.rb +++ b/test/bson/bson_test.rb @@ -23,23 +23,19 @@ class BSONTest < Test::Unit::TestCase include BSON - # This setup allows us to change the decoders for - # cross-coder compatibility testing def setup @encoder = BSON::BSON_CODER - @decoder = @encoder end def assert_doc_pass(doc, options={}) bson = @encoder.serialize(doc) if options[:debug] - puts "DEBUGGIN DOC:" + puts "DEBUGGING DOC:" p bson.to_a puts "DESERIALIZES TO:" - p @decoder.deserialize(bson) end - assert_equal @decoder.serialize(doc).to_a, bson.to_a - assert_equal doc, @decoder.deserialize(bson) + assert_equal @encoder.serialize(doc).to_a, bson.to_a + assert_equal doc, @encoder.deserialize(bson) end def test_require_hash @@ -81,20 +77,23 @@ class BSONTest < Test::Unit::TestCase # In 1.8 we test that other string encodings raise an exception. # In 1.9 we test that they get auto-converted. if RUBY_VERSION < '1.9' - require 'iconv' - def test_invalid_string - string = Iconv.conv('iso-8859-1', 'utf-8', 'aé') - doc = {'doc' => string} - assert_raise InvalidStringEncoding do - @encoder.serialize(doc) + if ! RUBY_PLATFORM =~ /java/ + require 'iconv' + def test_non_utf8_string + string = Iconv.conv('iso-8859-1', 'utf-8', 'aé') + doc = {'doc' => string} + assert_doc_pass(doc) + assert_raise InvalidStringEncoding do + @encoder.serialize(doc) + end end - end - def test_invalid_key - key = Iconv.conv('iso-8859-1', 'utf-8', 'aé') - doc = {key => 'hello'} - assert_raise InvalidStringEncoding do - @encoder.serialize(doc) + def test_non_utf8_key + key = Iconv.conv('iso-8859-1', 'utf-8', 'aé') + doc = {key => 'hello'} + assert_raise InvalidStringEncoding do + @encoder.serialize(doc) + end end end else @@ -176,6 +175,14 @@ class BSONTest < Test::Unit::TestCase assert_doc_pass(doc) end + def test_embedded_document_with_date + doc = {'doc' => {'age' => 42, 'date' => Time.now.utc, 'shoe_size' => 9.5}} + bson = @encoder.serialize(doc) + p doc + p doc['doc']['date'].class + assert_doc_pass(doc) + end + def test_oid doc = {'doc' => ObjectId.new} assert_doc_pass(doc) @@ -199,7 +206,7 @@ class BSONTest < Test::Unit::TestCase def test_date doc = {'date' => Time.now} bson = @encoder.serialize(doc) - doc2 = @decoder.deserialize(bson) + doc2 = @encoder.deserialize(bson) # Mongo only stores up to the millisecond assert_in_delta doc['date'], doc2['date'], 0.001 end @@ -207,7 +214,7 @@ class BSONTest < Test::Unit::TestCase def test_date_returns_as_utc doc = {'date' => Time.now} bson = @encoder.serialize(doc) - doc2 = @decoder.deserialize(bson) + doc2 = @encoder.deserialize(bson) assert doc2['date'].utc? end @@ -215,7 +222,7 @@ class BSONTest < Test::Unit::TestCase begin doc = {'date' => Time.utc(1600)} bson = @encoder.serialize(doc) - doc2 = @decoder.deserialize(bson) + doc2 = @encoder.deserialize(bson) # Mongo only stores up to the millisecond assert_in_delta doc['date'], doc2['date'], 2 rescue ArgumentError @@ -246,7 +253,7 @@ class BSONTest < Test::Unit::TestCase doc = {} doc['dbref'] = DBRef.new('namespace', oid) bson = @encoder.serialize(doc) - doc2 = @decoder.deserialize(bson) + doc2 = @encoder.deserialize(bson) # Java doesn't deserialize to DBRefs if RUBY_PLATFORM =~ /java/ @@ -261,7 +268,7 @@ class BSONTest < Test::Unit::TestCase def test_symbol doc = {'sym' => :foo} bson = @encoder.serialize(doc) - doc2 = @decoder.deserialize(bson) + doc2 = @encoder.deserialize(bson) assert_equal :foo, doc2['sym'] end @@ -271,7 +278,7 @@ class BSONTest < Test::Unit::TestCase doc = {'bin' => bin} bson = @encoder.serialize(doc) - doc2 = @decoder.deserialize(bson) + doc2 = @encoder.deserialize(bson) bin2 = doc2['bin'] assert_kind_of Binary, bin2 assert_equal 'binstring', bin2.to_s @@ -282,7 +289,7 @@ class BSONTest < Test::Unit::TestCase b = Binary.new('somebinarystring') doc = {'bin' => b} bson = @encoder.serialize(doc) - doc2 = @decoder.deserialize(bson) + doc2 = @encoder.deserialize(bson) bin2 = doc2['bin'] assert_kind_of Binary, bin2 assert_equal 'somebinarystring', bin2.to_s @@ -294,7 +301,7 @@ class BSONTest < Test::Unit::TestCase doc = {'bin' => bin} bson = @encoder.serialize(doc) - doc2 = @decoder.deserialize(bson) + doc2 = @encoder.deserialize(bson) bin2 = doc2['bin'] assert_kind_of Binary, bin2 assert_equal [1, 2, 3, 4, 5], bin2.to_a @@ -308,7 +315,7 @@ class BSONTest < Test::Unit::TestCase doc = {'bin' => bin} bson = @encoder.serialize(doc) - doc2 = @decoder.deserialize(bson) + doc2 = @encoder.deserialize(bson) bin2 = doc2['bin'] assert_kind_of Binary, bin2 assert_equal [1, 2, 3, 4, 5], bin2.to_a @@ -322,7 +329,7 @@ class BSONTest < Test::Unit::TestCase doc = {'bin' => bb} bson = @encoder.serialize(doc) - doc2 = @decoder.deserialize(bson) + doc2 = @encoder.deserialize(bson) bin2 = doc2['bin'] assert_kind_of Binary, bin2 assert_equal [1, 2, 3, 4, 5], bin2.to_a @@ -333,12 +340,12 @@ class BSONTest < Test::Unit::TestCase val = BSON::OrderedHash.new val['not_id'] = 1 val['_id'] = 2 - roundtrip = @decoder.deserialize(@encoder.serialize(val, false, true).to_s) + roundtrip = @encoder.deserialize(@encoder.serialize(val, false, true).to_s) assert_kind_of BSON::OrderedHash, roundtrip assert_equal '_id', roundtrip.keys.first val = {'a' => 'foo', 'b' => 'bar', :_id => 42, 'z' => 'hello'} - roundtrip = @decoder.deserialize(@encoder.serialize(val, false, true).to_s) + roundtrip = @encoder.deserialize(@encoder.serialize(val, false, true).to_s) assert_kind_of BSON::OrderedHash, roundtrip assert_equal '_id', roundtrip.keys.first end @@ -351,7 +358,7 @@ class BSONTest < Test::Unit::TestCase if !(RUBY_PLATFORM =~ /java/) def test_timestamp val = {"test" => [4, 20]} - assert_equal val, @decoder.deserialize([0x13, 0x00, 0x00, 0x00, + assert_equal val, @encoder.deserialize([0x13, 0x00, 0x00, 0x00, 0x11, 0x74, 0x65, 0x73, 0x74, 0x00, 0x04, 0x00, 0x00, 0x00, 0x14, 0x00, @@ -430,6 +437,14 @@ class BSONTest < Test::Unit::TestCase assert_equal @encoder.serialize(one).to_a, @encoder.serialize(dup).to_a end + def test_duplicate_keys + dup = {"_foo" => "foo", :_foo => "foo"} + one = {"_foo" => "foo"} + + assert_equal @encoder.serialize(one).to_a, @encoder.serialize(dup).to_a + end + + def test_no_duplicate_id_when_moving_id dup = {"_id" => "foo", :_id => "foo"} one = {:_id => "foo"} diff --git a/test/bson/hash_with_indifferent_access_test.rb b/test/bson/hash_with_indifferent_access_test.rb index 130d68f..0ef2ce2 100644 --- a/test/bson/hash_with_indifferent_access_test.rb +++ b/test/bson/hash_with_indifferent_access_test.rb @@ -1,6 +1,6 @@ # encoding:utf-8 require './test/test_helper' -require './test/support/hash_with_indifferent_access' +#require './test/support/hash_with_indifferent_access' class HashWithIndifferentAccessTest < Test::Unit::TestCase include BSON diff --git a/test/bson/json_test.rb b/test/bson/json_test.rb index 77833ba..541d62f 100644 --- a/test/bson/json_test.rb +++ b/test/bson/json_test.rb @@ -4,13 +4,12 @@ require 'json' class JSONTest < Test::Unit::TestCase - include Mongo - include BSON - def test_object_id_as_json - id = ObjectId.new + id = BSON::ObjectId.new + p id.to_json + obj = {'_id' => id} - assert_equal "{\"_id\":{\"$oid\": \"#{id.to_s}\"}}", obj.to_json + assert_equal "{\"_id\":#{id.to_json}}", obj.to_json end end diff --git a/test/support/hash_with_indifferent_access.rb b/test/support/hash_with_indifferent_access.rb index 45741a4..f79d0e2 100644 --- a/test/support/hash_with_indifferent_access.rb +++ b/test/support/hash_with_indifferent_access.rb @@ -1,12 +1,58 @@ # Note: HashWithIndifferentAccess is so commonly used # that we always need to make sure that the driver works # with it. -require File.join(File.dirname(__FILE__), 'keys.rb') +#require File.join(File.dirname(__FILE__), 'keys.rb') # This class has dubious semantics and we only have it so that # people can write params[:key] instead of params['key'] # and they get the same value for both keys. +class Hash + # Return a new hash with all keys converted to strings. + def stringify_keys + dup.stringify_keys! + end + + # Destructively convert all keys to strings. + def stringify_keys! + keys.each do |key| + self[key.to_s] = delete(key) + end + self + end + + # Return a new hash with all keys converted to symbols, as long as + # they respond to +to_sym+. + def symbolize_keys + dup.symbolize_keys! + end + + # Destructively convert all keys to symbols, as long as they respond + # to +to_sym+. + def symbolize_keys! + keys.each do |key| + self[(key.to_sym rescue key) || key] = delete(key) + end + self + end + + alias_method :to_options, :symbolize_keys + #alias_method :to_options!, :symbolize_keys! + + # Validate all keys in a hash match *valid keys, raising ArgumentError on a mismatch. + # Note that keys are NOT treated indifferently, meaning if you use strings for keys but assert symbols + # as keys, this will fail. + # + # ==== Examples + # { :name => "Rob", :years => "28" }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key(s): years" + # { :name => "Rob", :age => "28" }.assert_valid_keys("name", "age") # => raises "ArgumentError: Unknown key(s): name, age" + # { :name => "Rob", :age => "28" }.assert_valid_keys(:name, :age) # => passes, raises nothing + def assert_valid_keys(*valid_keys) + unknown_keys = keys - [valid_keys].flatten + raise(ArgumentError, "Unknown key(s): #{unknown_keys.join(", ")}") unless unknown_keys.empty? + end +end + module ActiveSupport class HashWithIndifferentAccess < Hash def extractable_options? diff --git a/test/support/keys.rb b/test/support/keys.rb index 045a694..7e73c4b 100644 --- a/test/support/keys.rb +++ b/test/support/keys.rb @@ -28,7 +28,7 @@ class Hash end alias_method :to_options, :symbolize_keys - alias_method :to_options!, :symbolize_keys! + #alias_method :to_options!, :symbolize_keys! # Validate all keys in a hash match *valid keys, raising ArgumentError on a mismatch. # Note that keys are NOT treated indifferently, meaning if you use strings for keys but assert symbols