diff --git a/bin/standard_benchmark b/bin/standard_benchmark index c1ecb97..44f64da 100755 --- a/bin/standard_benchmark +++ b/bin/standard_benchmark @@ -70,7 +70,7 @@ def profile(str) end end -def benchmark(str, n, coll_name, data, create_index=false) +def benchmark(str, n, coll_name, data, create_index=false, verbosity=true) coll = @db.collection(coll_name) coll.create_index('x') if create_index profile(str) do @@ -79,7 +79,7 @@ def benchmark(str, n, coll_name, data, create_index=false) td = tm.add do n.times { |i| yield(coll, i) } end - report(str, td.real, td.utime) + report(str, td.real, td.utime) if verbosity end end @@ -90,8 +90,8 @@ connection = Connection.new(host, port) connection.drop_database("benchmark") @db = connection.db('benchmark') -def benchmark_insert(desc, coll_name, data) - benchmark(desc, PER_TRIAL, coll_name, data) do |coll, i| +def benchmark_insert(desc, coll_name, data, verbosity=true) + benchmark(desc, PER_TRIAL, coll_name, data, verbosity) do |coll, i| data['x'] = i coll.insert(data) data.delete(:_id) @@ -109,10 +109,18 @@ end print_headings +if RUBY_PLATFORM =~ /java/ +puts "***WARMUP***" +benchmark_insert('insert (small, no index)', 'small_none', SMALL, false) +benchmark_insert('insert (medium, no index)', 'medium_none', MEDIUM, false) +benchmark_insert('insert (large, no index)', 'large_none', LARGE, false) +puts "***WARMUP***" +end benchmark_insert('insert (small, no index)', 'small_none', SMALL) benchmark_insert('insert (medium, no index)', 'medium_none', MEDIUM) benchmark_insert('insert (large, no index)', 'large_none', LARGE) + benchmark_insert_index('insert (small, index)', 'small_indexed', SMALL) benchmark_insert_index('insert (medium, index)', 'medium_indexed', MEDIUM) benchmark_insert_index('insert (large, index)', 'large_indexed', LARGE) @@ -138,6 +146,11 @@ end benchmark_find_one('find_one (small, no index)', 'small_none', PER_TRIAL / 2) benchmark_find_one('find_one (medium, no index)', 'medium_none', PER_TRIAL / 2) benchmark_find_one('find_one (large, no index)', 'large_none', PER_TRIAL / 2) +benchmark_find_one('find_one (small, no index)', 'small_none', PER_TRIAL / 2) +benchmark_find_one('find_one (medium, no index)', 'medium_none', PER_TRIAL / 2) +benchmark_find_one('find_one (large, no index)', 'large_none', PER_TRIAL / 2) + + benchmark_find_one('find_one (small, indexed)', 'small_indexed', PER_TRIAL / 2) benchmark_find_one('find_one (medium, indexed)', 'medium_indexed', PER_TRIAL / 2) diff --git a/ext/java/jar/jbson.jar b/ext/java/jar/jbson.jar index efb9db5..3d5dd01 100644 Binary files a/ext/java/jar/jbson.jar and b/ext/java/jar/jbson.jar differ diff --git a/ext/java/src/org/jbson/RubyBSONCallback.class b/ext/java/src/org/jbson/RubyBSONCallback.class index bca6824..660dbc2 100644 Binary files a/ext/java/src/org/jbson/RubyBSONCallback.class and b/ext/java/src/org/jbson/RubyBSONCallback.class differ diff --git a/ext/java/src/org/jbson/RubyBSONCallback.java b/ext/java/src/org/jbson/RubyBSONCallback.java index de91723..5d3df44 100644 --- a/ext/java/src/org/jbson/RubyBSONCallback.java +++ b/ext/java/src/org/jbson/RubyBSONCallback.java @@ -60,6 +60,7 @@ public class RubyBSONCallback implements BSONCallback { _nameStack.clear(); } + // Note: this actually creates an OrderedHash. public RubyHash createHash() { RubyHash h = (RubyHash)JavaEmbedUtils.invokeMethod(_runtime, _rbclsOrderedHash, "new", new Object[] { }, Object.class); @@ -108,6 +109,8 @@ public class RubyBSONCallback implements BSONCallback { _stack.addLast( (RubyObject)hash ); } + // Note: we use []= because we're dealing with an OrderedHash, which in 1.8 + // doesn't have an internal JRuby representation. public void writeRubyHash(String key, RubyHash hash, IRubyObject obj) { RubyString rkey = _runtime.newString(key); JavaEmbedUtils.invokeMethod(_runtime, hash, "[]=", @@ -115,9 +118,8 @@ public class RubyBSONCallback implements BSONCallback { } public void writeRubyArray(String key, RubyArray array, IRubyObject obj) { - Long rkey = Long.parseLong(key); - RubyFixnum index = new RubyFixnum(_runtime, rkey); - array.aset((IRubyObject)index, obj); + Long index = Long.parseLong(key); + array.store(index, obj); } public void arrayStart(String key){ @@ -281,7 +283,7 @@ public class RubyBSONCallback implements BSONCallback { RubyArray result = RubyArray.newArray( _runtime, b.length ); for ( int i=0; i _getRuntimeCache(Ruby runtime) { // each JRuby runtime may have different objects for these constants, // so cache them separately for each runtime diff --git a/ext/java/src/org/jbson/RubyBSONEncoder.class b/ext/java/src/org/jbson/RubyBSONEncoder.class index 0b3d96d..60f5955 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 ef695f6..18e95bc 100644 --- a/ext/java/src/org/jbson/RubyBSONEncoder.java +++ b/ext/java/src/org/jbson/RubyBSONEncoder.java @@ -19,6 +19,7 @@ import org.bson.BSONEncoder; import org.jruby.javasupport.JavaEmbedUtils; import org.jruby.javasupport.JavaUtil; +import org.jruby.java.proxies.JavaProxy; import org.jruby.*; import org.jruby.runtime.builtin.IRubyObject; @@ -225,20 +226,8 @@ public class RubyBSONEncoder extends BSONEncoder { if ( val instanceof String ) putString(name, val.toString() ); - // TODO: Clean up - else if ( val instanceof Number ) { - if ( val instanceof Double ) { - putNumber(name, (Number)val); - } - else { - long jval = ((Number)val).longValue(); - if (jval > Integer.MIN_VALUE && jval < Integer.MAX_VALUE) { - putNumber(name, (int)jval ); - } - else - putNumber(name, (Number)jval ); - } - } + else if ( val instanceof Number ) + putNumber(name, (Number)val); else if ( val instanceof Boolean ) putBoolean(name, (Boolean)val); @@ -265,7 +254,6 @@ public class RubyBSONEncoder extends BSONEncoder { putSymbol(name, new Symbol(val.toString())); } - // TODO: Clean up else if ( val instanceof RubyFixnum ) { long jval = ((RubyFixnum)val).getLongValue(); if (jval >= Integer.MIN_VALUE && jval <= Integer.MAX_VALUE) { @@ -275,12 +263,22 @@ public class RubyBSONEncoder extends BSONEncoder { putNumber(name, (Number)jval ); } - // TODO: Clean up else if ( val instanceof RubyFloat ) { double jval = ((RubyFloat)val).getValue(); putNumber(name, (Number)jval ); } + else if ( val instanceof JavaProxy ) { + Object obj = ((JavaProxy)val).getObject(); + if ( obj instanceof ArrayList ) { + putIterable( name, ((ArrayList)obj)); + } + else { + _rbRaise( (RubyClass)_rbclsInvalidDocument, + "Got a JavaProxy object which can't be serialized as a BSON type." ); + } + } + else if ( val instanceof RubyNil ) putNull(name); @@ -316,6 +314,9 @@ public class RubyBSONEncoder extends BSONEncoder { else if( klass.equals( "BSON::ObjectID" ) ) { putRubyObjectId(name, (RubyObject)val ); } + else if( klass.equals( "Java::JavaUtil::ArrayList" ) ) { + putIterable(name, (Iterable)val ); + } else if ( klass.equals( "BSON::Code" ) ) { putRubyCodeWScope(name, (RubyObject)val ); } diff --git a/ext/java/src/org/jbson/RubyBSONJavaCallback.class b/ext/java/src/org/jbson/RubyBSONJavaCallback.class new file mode 100644 index 0000000..504970b Binary files /dev/null and b/ext/java/src/org/jbson/RubyBSONJavaCallback.class differ diff --git a/ext/java/src/org/jbson/RubyBSONJavaCallback.java b/ext/java/src/org/jbson/RubyBSONJavaCallback.java new file mode 100644 index 0000000..ee157b1 --- /dev/null +++ b/ext/java/src/org/jbson/RubyBSONJavaCallback.java @@ -0,0 +1,379 @@ +// BSON Callback +// RubyBSONCallback.java +package org.jbson; + +import org.jruby.*; +import org.jruby.util.ByteList; +import org.jruby.RubyString; +import org.jruby.runtime.builtin.IRubyObject; +import org.jruby.runtime.Block; +import org.jruby.runtime.CallType; +import org.jruby.runtime.callsite.CacheEntry; + +import org.jruby.javasupport.JavaEmbedUtils; +import org.jruby.javasupport.JavaUtil; + +import org.jruby.parser.ReOptions; + +import org.jruby.RubyArray; + +import java.io.*; +import java.util.*; +import java.util.regex.*; + +import org.bson.*; +import org.bson.types.*; + +public class RubyBSONJavaCallback implements BSONCallback { + + private RubyHash _root; + private RubyModule _rbclsOrderedHash; + private RubyModule _rbclsObjectId; + private RubyModule _rbclsBinary; + private RubyModule _rbclsMinKey; + private RubyModule _rbclsMaxKey; + private RubyModule _rbclsDBRef; + private RubyModule _rbclsCode; + private final LinkedList _stack = new LinkedList(); + private final LinkedList _nameStack = new LinkedList(); + private Ruby _runtime; + static final HashMap _runtimeCache = new HashMap(); + + public RubyBSONJavaCallback(Ruby runtime) { + _runtime = runtime; + _rbclsOrderedHash = _lookupConstant( _runtime, "BSON::OrderedHash" ); + _rbclsBinary = _lookupConstant( _runtime, "BSON::Binary" ); + _rbclsDBRef = _lookupConstant( _runtime, "BSON::DBRef" ); + _rbclsCode = _lookupConstant( _runtime, "BSON::Code" ); + _rbclsMinKey = _lookupConstant( _runtime, "BSON::MinKey" ); + _rbclsMaxKey = _lookupConstant( _runtime, "BSON::MaxKey" ); + _rbclsObjectId = _lookupConstant( _runtime, "BSON::ObjectId"); + } + + public BSONCallback createBSONCallback(){ + return new RubyBSONCallback(_runtime); + } + + public void reset(){ + _root = null; + _stack.clear(); + _nameStack.clear(); + } + + public RubyHash createHash() { + RubyHash h = (RubyHash)JavaEmbedUtils.invokeMethod(_runtime, _rbclsOrderedHash, "new", + new Object[] { }, Object.class); + + return h; + } + + public Object create( boolean array , List path ){ + if ( array ) + return new ArrayList(); + return createHash(); + } + + public void objectStart(){ + if ( _stack.size() > 0 ) { + throw new IllegalStateException( "something is wrong" ); + } + + _root = createHash(); + _stack.add(_root); + } + + public void objectStart(boolean f) { + objectStart(); + } + + public void objectStart(String key){ + RubyHash hash = createHash(); + + _nameStack.addLast( key ); + + Object lastObject = _stack.getLast(); + + // Yes, this is a bit hacky. + if(lastObject instanceof RubyHash) { + writeRubyHash(key, (RubyHash)lastObject, (IRubyObject)hash); + } + else { + ((ArrayList)lastObject).add(Integer.parseInt(key), hash); + //writeRubyArray(key, (RubyArray)lastObject, (IRubyObject)hash); + } + + _stack.addLast( (RubyObject)hash ); + } + + public void writeRubyHash(String key, RubyHash hash, Object obj) { + RubyString rkey = _runtime.newString(key); + JavaEmbedUtils.invokeMethod(_runtime, hash, "[]=", + new Object[] { (IRubyObject)rkey, obj }, Object.class); + } + + public void writeRubyArray(String key, RubyArray array, IRubyObject obj) { + Long rkey = Long.parseLong(key); + RubyFixnum index = new RubyFixnum(_runtime, rkey); + array.aset((IRubyObject)index, obj); + } + + public void arrayStart(String key){ + ArrayList array = new ArrayList(); + + Object lastObject = _stack.getLast(); + _nameStack.addLast( key ); + + if(lastObject instanceof RubyHash) { + writeRubyHash(key, (RubyHash)lastObject, array); + } + else { + ((ArrayList)lastObject).add(Integer.parseInt(key), array); + } + + _stack.addLast( array ); + } + + public Object objectDone(){ + Object o =_stack.removeLast(); + if ( _nameStack.size() > 0 ) + _nameStack.removeLast(); + else if ( _stack.size() > 0 ) { + throw new IllegalStateException( "something is wrong" ); + } + return o; + } + + // Not used by Ruby decoder + public void arrayStart(){ + } + + public Object arrayDone(){ + return objectDone(); + } + + public void gotNull( String name ){ + _put(name, null); + } + + // Undefined should be represented as a lack of key / value. + public void gotUndefined( String name ){ + } + + // TODO: Handle this + public void gotUUID( String name , long part1, long part2) { + //_put( name , new UUID(part1, part2) ); + } + + public void gotCode( String name , String code ){ + Object rb_code_obj = JavaEmbedUtils.invokeMethod(_runtime, _rbclsCode, + "new", new Object[] { code }, Object.class); + _put( name , (RubyObject)rb_code_obj ); + } + + public void gotCodeWScope( String name , String code , Object scope ){ + Object rb_code_obj = JavaEmbedUtils.invokeMethod(_runtime, _rbclsCode, + "new", new Object[] { code, (RubyHash)scope }, Object.class); + + _put( name , (RubyObject)rb_code_obj ); + } + + public void gotMinKey( String name ){ + Object minkey = JavaEmbedUtils.invokeMethod(_runtime, _rbclsMinKey, "new", new Object[] {}, Object.class); + + _put( name, (RubyObject)minkey); + } + + public void gotMaxKey( String name ){ + Object maxkey = JavaEmbedUtils.invokeMethod(_runtime, _rbclsMaxKey, "new", new Object[] {}, Object.class); + + _put( name, (RubyObject)maxkey); + } + + public void gotBoolean( String name , boolean v ){ + _put(name , v); + } + + public void gotDouble( String name , double v ){ + _put(name , v); + } + + public void gotInt( String name , int v ){ + _put(name , v); + } + + public void gotLong( String name , long v ){ + _put(name , v); + } + + public void gotDate( String name , long millis ){ + RubyTime time = RubyTime.newTime(_runtime, millis).gmtime(); + _put( name , time ); + } + + public void gotRegex( String name , String pattern , String flags ){ + int f = 0; + ByteList b = new ByteList(pattern.getBytes()); + + if(flags.contains("i")) { + f = f | ReOptions.RE_OPTION_IGNORECASE; + } + if(flags.contains("m")) { + f = f | ReOptions.RE_OPTION_MULTILINE; + } + if(flags.contains("x")) { + f = f | ReOptions.RE_OPTION_EXTENDED; + } + + _put( name , RubyRegexp.newRegexp(_runtime, b, f) ); + } + + public void gotString( String name , String v ){ + _put( name , v ); + } + + public void gotSymbol( String name , String v ){ + ByteList bytes = new ByteList(v.getBytes()); + RubySymbol symbol = _runtime.getSymbolTable().getSymbol(bytes); + _put( name , symbol ); + } + + // Timestamp is currently rendered in Ruby as a two-element array. + public void gotTimestamp( String name , int time , int inc ){ + RubyFixnum rtime = RubyFixnum.newFixnum( _runtime, time ); + RubyFixnum rinc = RubyFixnum.newFixnum( _runtime, inc ); + RubyObject[] args = new RubyObject[2]; + args[0] = rinc; + args[1] = rtime; + + RubyArray result = RubyArray.newArray( _runtime, args ); + + _put ( name , result ); + } + + public void gotObjectId( String name , ObjectId id ){ + IRubyObject arg = (IRubyObject)RubyString.newString(_runtime, id.toString()); + // //System.out.println(id.toByteArray().length); + // byte[] b = id.toByteArray(); + // RubyArray a = _runtime.newArray(); + // for(int i=0; i < b.length; i++) { + // System.out.println(b[i]); + // a.append(_runtime.newFixnum(b[i])); + // } + Object[] args = new Object[] { arg }; + + Object result = JavaEmbedUtils.invokeMethod(_runtime, _rbclsObjectId, "from_string", args, Object.class); + + _put( name, (RubyObject)result ); + } + + // TODO: Incredibly annoying to deserialize to a Ruby DBRef. Might just + // stop supporting this altogether in the driver. + public void gotDBRef( String name , String ns , ObjectId id ){ + // _put( name , new BasicBSONObject( "$ns" , ns ).append( "$id" , id ) ); + } + + // TODO: I know that this is horrible. To be optimized. + private RubyArray ja2ra( byte[] b ) { + RubyArray result = RubyArray.newArray( _runtime, b.length ); + + for ( int i=0; i cache = _runtimeCache.get( runtime ); + + if(cache == null) { + cache = new HashMap(); + _runtimeCache.put( runtime, cache ); + } + return cache; + } + + static final RubyModule _lookupConstant(Ruby runtime, String name) + { + HashMap cache = _getRuntimeCache( runtime ); + RubyModule module = (RubyModule) cache.get( name ); + + if(module == null && !cache.containsKey( name )) { + module = runtime.getClassFromPath( name ); + cache.put( (String)name, (Object)module ); + } + return module; + } +} diff --git a/lib/bson/bson_java.rb b/lib/bson/bson_java.rb index 67ff8e7..caeba80 100644 --- a/lib/bson/bson_java.rb +++ b/lib/bson/bson_java.rb @@ -4,7 +4,7 @@ module BSON def self.serialize(obj, check_keys=false, move_id=false) raise InvalidDocument, "BSON_JAVA.serialize takes a Hash" unless obj.is_a?(Hash) - enc = get_encoder# Java::OrgJbson::RubyBSONEncoder.new(JRuby.runtime) + enc = Java::OrgJbson::RubyBSONEncoder.new(JRuby.runtime) ByteBuffer.new(enc.encode(obj)) end @@ -17,11 +17,8 @@ module BSON end def self.deserialize(buf) - if buf.is_a? String - buf = ByteBuffer.new(buf) if buf - end - dec = get_decoder - callback = Java::OrgJbson::RubyBSONCallback.new(JRuby.runtime) + dec = Java::OrgBson::BSONDecoder.new + callback = Java::OrgJbson::RubyBSONJavaCallback.new(JRuby.runtime) dec.decode(buf.to_s.to_java_bytes, callback) callback.get end diff --git a/lib/bson/types/object_id.rb b/lib/bson/types/object_id.rb index 7f43c87..7ff8b6b 100644 --- a/lib/bson/types/object_id.rb +++ b/lib/bson/types/object_id.rb @@ -44,6 +44,8 @@ module BSON @data = data || generate end + attr_accessor :data + # Determine if the supplied string is legal. Legal strings will # consist of 24 hexadecimal characters. # @@ -132,6 +134,13 @@ module BSON str end + def to_e + @data.each do |i| + print i + print ' ' + end + end + def inspect "BSON::ObjectId('#{to_s}')" end diff --git a/test/mongo_bson/jbson_test.rb b/test/mongo_bson/jbson_test.rb index fdcf8b0..af633b4 100644 --- a/test/mongo_bson/jbson_test.rb +++ b/test/mongo_bson/jbson_test.rb @@ -23,7 +23,7 @@ class BSONTest < Test::Unit::TestCase def setup @encoder = BSON::BSON_JAVA - @decoder = BSON::BSON_RUBY#BSON::BSON_JAVA + @decoder = BSON::BSON_JAVA end def assert_doc_pass(doc, options={}) @@ -338,13 +338,16 @@ class BSONTest < Test::Unit::TestCase assert_doc_pass(doc) end - def test_timestamp - val = {"test" => [4, 20]} - assert_equal val, @decoder.deserialize([0x13, 0x00, 0x00, 0x00, - 0x11, 0x74, 0x65, 0x73, - 0x74, 0x00, 0x04, 0x00, - 0x00, 0x00, 0x14, 0x00, - 0x00, 0x00, 0x00]) + if !(RUBY_PLATFORM =~ /java/) + def test_timestamp + val = {"test" => [4, 20]} + assert_equal val, @decoder.deserialize([0x13, 0x00, 0x00, 0x00, + 0x11, 0x74, 0x65, 0x73, + 0x74, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x14, 0x00, + 0x00, 0x00, 0x00]) + + end end def test_overflow