From 05bf234bb8662bf54a01b2c4006700ecc804d8d8 Mon Sep 17 00:00:00 2001 From: Kyle Banker Date: Mon, 28 Mar 2011 14:36:49 -0400 Subject: [PATCH] RUBY-255 Support BSON Timestamp --- ext/cbson/cbson.c | 26 +++++-- ext/java/jar/jbson.jar | Bin 13877 -> 14014 bytes ext/java/src/org/jbson/RubyBSONCallback.java | 11 +-- ext/java/src/org/jbson/RubyBSONEncoder.java | 15 ++++ lib/bson.rb | 1 + lib/bson/bson_ruby.rb | 21 ++++- lib/bson/types/timestamp.rb | 76 +++++++++++++++++++ test/bson/bson_test.rb | 19 +++-- test/bson/timestamp_test.rb | 24 ++++++ 9 files changed, 176 insertions(+), 17 deletions(-) create mode 100644 lib/bson/types/timestamp.rb create mode 100644 test/bson/timestamp_test.rb diff --git a/ext/cbson/cbson.c b/ext/cbson/cbson.c index 5fe6dfe..e75a56e 100644 --- a/ext/cbson/cbson.c +++ b/ext/cbson/cbson.c @@ -86,6 +86,7 @@ static VALUE DBRef; static VALUE Code; static VALUE MinKey; static VALUE MaxKey; +static VALUE Timestamp; static VALUE Regexp; static VALUE OrderedHash; static VALUE InvalidKeyName; @@ -433,6 +434,17 @@ static int write_element(VALUE key, VALUE value, VALUE extra, int allow_id) { write_name_and_type(buffer, key, 0xff); break; } + if (strcmp(cls, "BSON::Timestamp") == 0) { + write_name_and_type(buffer, key, 0x11); + int seconds = FIX2INT( + rb_funcall(value, rb_intern("seconds"), 0)); + int increment = FIX2INT( + rb_funcall(value, rb_intern("increment"), 0)); + + SAFE_WRITE(buffer, (const char*)&increment, 4); + SAFE_WRITE(buffer, (const char*)&seconds, 4); + break; + } if (strcmp(cls, "DateTime") == 0 || strcmp(cls, "Date") == 0 || strcmp(cls, "ActiveSupport::TimeWithZone") == 0) { buffer_free(buffer); rb_raise(InvalidDocument, "%s is not currently supported; use a UTC Time instance instead.", cls); @@ -813,11 +825,13 @@ static VALUE get_value(const char* buffer, int* position, int type) { } case 17: { - int i; - int j; - memcpy(&i, buffer + *position, 4); - memcpy(&j, buffer + *position + 4, 4); - value = rb_ary_new3(2, LL2NUM(i), LL2NUM(j)); + int sec, inc; + VALUE argv[2]; + memcpy(&inc, buffer + *position, 4); + memcpy(&sec, buffer + *position + 4, 4); + argv[0] = INT2FIX(sec); + argv[1] = INT2FIX(inc); + value = rb_class_new_instance(2, argv, Timestamp); *position += 8; break; } @@ -940,6 +954,8 @@ void Init_cbson() { rb_require("bson/types/min_max_keys"); MinKey = rb_const_get(bson, rb_intern("MinKey")); MaxKey = rb_const_get(bson, rb_intern("MaxKey")); + rb_require("bson/types/timestamp"); + Timestamp = rb_const_get(bson, rb_intern("Timestamp")); Regexp = rb_const_get(rb_cObject, rb_intern("Regexp")); rb_require("bson/exceptions"); InvalidKeyName = rb_const_get(bson, rb_intern("InvalidKeyName")); diff --git a/ext/java/jar/jbson.jar b/ext/java/jar/jbson.jar index ffcc70f57db5ab596dd3f87a13428c0531c37395..dc5a7d8adac3d58058b5ec029bd0036eac56943b 100644 GIT binary patch delta 13445 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 Ag#Z8m diff --git a/ext/java/src/org/jbson/RubyBSONCallback.java b/ext/java/src/org/jbson/RubyBSONCallback.java index 03d2f2b..b20ec2c 100644 --- a/ext/java/src/org/jbson/RubyBSONCallback.java +++ b/ext/java/src/org/jbson/RubyBSONCallback.java @@ -32,6 +32,7 @@ public class RubyBSONCallback implements BSONCallback { private RubyModule _rbclsBinary; private RubyModule _rbclsMinKey; private RubyModule _rbclsMaxKey; + private RubyModule _rbclsTimestamp; private RubyModule _rbclsDBRef; private RubyModule _rbclsCode; private final LinkedList _stack = new LinkedList(); @@ -47,6 +48,7 @@ public class RubyBSONCallback implements BSONCallback { _rbclsCode = _lookupConstant( _runtime, "BSON::Code" ); _rbclsMinKey = _lookupConstant( _runtime, "BSON::MinKey" ); _rbclsMaxKey = _lookupConstant( _runtime, "BSON::MaxKey" ); + _rbclsTimestamp = _lookupConstant( _runtime, "BSON::Timestamp" ); _rbclsObjectId = _lookupConstant( _runtime, "BSON::ObjectId"); } @@ -250,17 +252,16 @@ public class RubyBSONCallback implements BSONCallback { _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; + args[0] = rtime; + args[1] = rinc; - RubyArray result = RubyArray.newArray( _runtime, args ); + Object result = JavaEmbedUtils.invokeMethod(_runtime, _rbclsTimestamp, "new", args, Object.class); - _put ( name , result ); + _put ( name , (RubyObject)result ); } public void gotObjectId( String name , ObjectId id ){ diff --git a/ext/java/src/org/jbson/RubyBSONEncoder.java b/ext/java/src/org/jbson/RubyBSONEncoder.java index 51263f2..5b63be9 100644 --- a/ext/java/src/org/jbson/RubyBSONEncoder.java +++ b/ext/java/src/org/jbson/RubyBSONEncoder.java @@ -384,6 +384,9 @@ public class RubyBSONEncoder extends BSONEncoder { else if ( klass.equals("BSON::MaxKey") ) { _put( MAXKEY, name ); } + else if ( klass.equals("BSON::Timestamp") ) { + putRubyTimestamp( name, (RubyObject)val ); + } else if ( klass.equals("BSON::DBRef") ) { RubyHash ref = (RubyHash)JavaEmbedUtils.invokeMethod(_runtime, val, "to_hash", new Object[] {}, Object.class); @@ -504,6 +507,18 @@ public class RubyBSONEncoder extends BSONEncoder { _buf.writeInt( ts.getTime() ); } + protected void putRubyTimestamp(String name, RubyObject ts ){ + _put( TIMESTAMP , name ); + + Number inc = (Number)JavaEmbedUtils.invokeMethod(_runtime, ts, + "increment", new Object[] {}, Object.class); + Number sec = (Number)JavaEmbedUtils.invokeMethod(_runtime, ts, + "seconds", new Object[] {}, Object.class); + + _buf.writeInt( (int)inc.longValue() ); + _buf.writeInt( (int)sec.longValue() ); + } + protected void putRubyCodeWScope( String name , RubyObject code ){ _put( CODE_W_SCOPE , name ); int temp = _buf.getPosition(); diff --git a/lib/bson.rb b/lib/bson.rb index 60504de..b0a5ba9 100644 --- a/lib/bson.rb +++ b/lib/bson.rb @@ -100,6 +100,7 @@ require 'bson/types/code' require 'bson/types/dbref' require 'bson/types/object_id' require 'bson/types/min_max_keys' +require 'bson/types/timestamp' require 'base64' require 'bson/ordered_hash' diff --git a/lib/bson/bson_ruby.rb b/lib/bson/bson_ruby.rb index 17d03a7..de3440f 100644 --- a/lib/bson/bson_ruby.rb +++ b/lib/bson/bson_ruby.rb @@ -192,6 +192,8 @@ module BSON serialize_max_key_element(@buf, k) when MINKEY serialize_min_key_element(@buf, k) + when TIMESTAMP + serialize_timestamp_element(@buf, k, v) else raise "unhandled type #{type}" end @@ -261,8 +263,7 @@ module BSON doc[key] = deserialize_code_w_scope_data(@buf) when TIMESTAMP key = deserialize_cstr(@buf) - doc[key] = [deserialize_number_int_data(@buf), - deserialize_number_int_data(@buf)] + doc[key] = deserialize_timestamp_data(@buf) when MAXKEY key = deserialize_cstr(@buf) doc[key] = MaxKey.new @@ -346,6 +347,12 @@ module BSON Regexp.new(str, opts) end + def deserialize_timestamp_data(buf) + increment = buf.get_int + seconds = buf.get_int + Timestamp.new(seconds, increment) + end + def encoded_str(str) if RUBY_VERSION >= '1.9' str.force_encoding("utf-8") @@ -510,6 +517,14 @@ module BSON self.class.serialize_key(buf, key) end + def serialize_timestamp_element(buf, key, val) + buf.put(TIMESTAMP) + self.class.serialize_key(buf, key) + + buf.put_int(val.increment) + buf.put_int(val.seconds) + end + def serialize_oid_element(buf, key, val) buf.put(OID) self.class.serialize_key(buf, key) @@ -598,6 +613,8 @@ module BSON MAXKEY when MinKey MINKEY + when Timestamp + TIMESTAMP when Numeric raise InvalidDocument, "Cannot serialize the Numeric type #{o.class} as BSON; only Fixum, Bignum, and Float are supported." when Date, DateTime diff --git a/lib/bson/types/timestamp.rb b/lib/bson/types/timestamp.rb new file mode 100644 index 0000000..0b32715 --- /dev/null +++ b/lib/bson/types/timestamp.rb @@ -0,0 +1,76 @@ +# encoding: UTF-8 + +# -- +# Copyright (C) 2008-2011 10gen Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ++ + +module BSON + + # A class for representing BSON Timestamps. The Timestamp type is used + # by MongoDB internally; thus, it should be used by application developers + # for diagnostic purposes only. + class Timestamp + include Enumerable + + attr_reader :seconds, :increment + + # Create a new BSON Timestamp. + # + # @param [Integer] seconds The number of seconds + # @param increment + def initialize(seconds, increment) + @seconds = seconds + @increment = increment + end + + def to_s + "seconds: #{seconds}, increment: #{increment}" + end + + def ==(other) + self.seconds == other.seconds && + self.increment == other.increment + end + + # This is for backward-compatibility. Timestamps in the Ruby + # driver used to deserialize as arrays. So we provide + # an equivalent interface. + # + # @deprecated + def [](index) + warn "Timestamps are no longer deserialized as arrays. If you're working " + + "with BSON Timestamp objects, see the BSON::Timestamp class. This usage will " + + "be deprecated in Ruby Driver v2.0." + if index == 0 + self.increment + elsif index == 1 + self.seconds + else + nil + end + end + + # This method exists only for backward-compatibility. + # + # @deprecated + def each + i = 0 + while i < 2 + yield self[i] + i += 1 + end + end + end +end diff --git a/test/bson/bson_test.rb b/test/bson/bson_test.rb index f178442..9e02696 100644 --- a/test/bson/bson_test.rb +++ b/test/bson/bson_test.rb @@ -410,15 +410,24 @@ class BSONTest < Test::Unit::TestCase if !(RUBY_PLATFORM =~ /java/) def test_timestamp val = {"test" => [4, 20]} - assert_equal val, @encoder.deserialize([0x13, 0x00, 0x00, 0x00, - 0x11, 0x74, 0x65, 0x73, - 0x74, 0x00, 0x04, 0x00, - 0x00, 0x00, 0x14, 0x00, - 0x00, 0x00, 0x00]) + result = @encoder.deserialize([0x13, 0x00, 0x00, 0x00, + 0x11, 0x74, 0x65, 0x73, + 0x74, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x14, 0x00, + 0x00, 0x00, 0x00]) + assert_equal 4, result["test"][0] + assert_equal 20, result["test"][1] end end + def test_timestamp_type + ts = Timestamp.new(5000, 100) + doc = {:ts => ts} + bson = @encoder.serialize(doc) + assert_equal ts, @encoder.deserialize(bson)["ts"] + end + def test_overflow doc = {"x" => 2**75} assert_raise RangeError do diff --git a/test/bson/timestamp_test.rb b/test/bson/timestamp_test.rb new file mode 100644 index 0000000..37cfec3 --- /dev/null +++ b/test/bson/timestamp_test.rb @@ -0,0 +1,24 @@ +require './test/test_helper' + +class TiumestampTest < Test::Unit::TestCase + include Mongo + + def test_timestamp_equality + t1 = Timestamp.new(5000, 200) + t2 = Timestamp.new(5000, 200) + assert_equal t2, t1 + end + + def test_implements_array_for_backward_compatibility + ts = Timestamp.new(5000, 200) + assert_equal 200, ts[0] + assert_equal 5000, ts[1] + + array = ts.map {|t| t } + assert_equal 2, array.length + + assert_equal 200, array[0] + assert_equal 5000, array[1] + end + +end