RUBY-255 Support BSON Timestamp

This commit is contained in:
Kyle Banker 2011-03-28 14:36:49 -04:00
parent baa1d0b802
commit 05bf234bb8
9 changed files with 176 additions and 17 deletions

View File

@ -86,6 +86,7 @@ static VALUE DBRef;
static VALUE Code; static VALUE Code;
static VALUE MinKey; static VALUE MinKey;
static VALUE MaxKey; static VALUE MaxKey;
static VALUE Timestamp;
static VALUE Regexp; static VALUE Regexp;
static VALUE OrderedHash; static VALUE OrderedHash;
static VALUE InvalidKeyName; 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); write_name_and_type(buffer, key, 0xff);
break; 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) { if (strcmp(cls, "DateTime") == 0 || strcmp(cls, "Date") == 0 || strcmp(cls, "ActiveSupport::TimeWithZone") == 0) {
buffer_free(buffer); buffer_free(buffer);
rb_raise(InvalidDocument, "%s is not currently supported; use a UTC Time instance instead.", cls); 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: case 17:
{ {
int i; int sec, inc;
int j; VALUE argv[2];
memcpy(&i, buffer + *position, 4); memcpy(&inc, buffer + *position, 4);
memcpy(&j, buffer + *position + 4, 4); memcpy(&sec, buffer + *position + 4, 4);
value = rb_ary_new3(2, LL2NUM(i), LL2NUM(j)); argv[0] = INT2FIX(sec);
argv[1] = INT2FIX(inc);
value = rb_class_new_instance(2, argv, Timestamp);
*position += 8; *position += 8;
break; break;
} }
@ -940,6 +954,8 @@ void Init_cbson() {
rb_require("bson/types/min_max_keys"); rb_require("bson/types/min_max_keys");
MinKey = rb_const_get(bson, rb_intern("MinKey")); MinKey = rb_const_get(bson, rb_intern("MinKey"));
MaxKey = rb_const_get(bson, rb_intern("MaxKey")); 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")); Regexp = rb_const_get(rb_cObject, rb_intern("Regexp"));
rb_require("bson/exceptions"); rb_require("bson/exceptions");
InvalidKeyName = rb_const_get(bson, rb_intern("InvalidKeyName")); InvalidKeyName = rb_const_get(bson, rb_intern("InvalidKeyName"));

Binary file not shown.

View File

@ -32,6 +32,7 @@ public class RubyBSONCallback implements BSONCallback {
private RubyModule _rbclsBinary; private RubyModule _rbclsBinary;
private RubyModule _rbclsMinKey; private RubyModule _rbclsMinKey;
private RubyModule _rbclsMaxKey; private RubyModule _rbclsMaxKey;
private RubyModule _rbclsTimestamp;
private RubyModule _rbclsDBRef; private RubyModule _rbclsDBRef;
private RubyModule _rbclsCode; private RubyModule _rbclsCode;
private final LinkedList<RubyObject> _stack = new LinkedList<RubyObject>(); private final LinkedList<RubyObject> _stack = new LinkedList<RubyObject>();
@ -47,6 +48,7 @@ public class RubyBSONCallback implements BSONCallback {
_rbclsCode = _lookupConstant( _runtime, "BSON::Code" ); _rbclsCode = _lookupConstant( _runtime, "BSON::Code" );
_rbclsMinKey = _lookupConstant( _runtime, "BSON::MinKey" ); _rbclsMinKey = _lookupConstant( _runtime, "BSON::MinKey" );
_rbclsMaxKey = _lookupConstant( _runtime, "BSON::MaxKey" ); _rbclsMaxKey = _lookupConstant( _runtime, "BSON::MaxKey" );
_rbclsTimestamp = _lookupConstant( _runtime, "BSON::Timestamp" );
_rbclsObjectId = _lookupConstant( _runtime, "BSON::ObjectId"); _rbclsObjectId = _lookupConstant( _runtime, "BSON::ObjectId");
} }
@ -250,17 +252,16 @@ public class RubyBSONCallback implements BSONCallback {
_put( name , symbol ); _put( name , symbol );
} }
// Timestamp is currently rendered in Ruby as a two-element array.
public void gotTimestamp( String name , int time , int inc ){ public void gotTimestamp( String name , int time , int inc ){
RubyFixnum rtime = RubyFixnum.newFixnum( _runtime, time ); RubyFixnum rtime = RubyFixnum.newFixnum( _runtime, time );
RubyFixnum rinc = RubyFixnum.newFixnum( _runtime, inc ); RubyFixnum rinc = RubyFixnum.newFixnum( _runtime, inc );
RubyObject[] args = new RubyObject[2]; RubyObject[] args = new RubyObject[2];
args[0] = rinc; args[0] = rtime;
args[1] = 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 ){ public void gotObjectId( String name , ObjectId id ){

View File

@ -384,6 +384,9 @@ public class RubyBSONEncoder extends BSONEncoder {
else if ( klass.equals("BSON::MaxKey") ) { else if ( klass.equals("BSON::MaxKey") ) {
_put( MAXKEY, name ); _put( MAXKEY, name );
} }
else if ( klass.equals("BSON::Timestamp") ) {
putRubyTimestamp( name, (RubyObject)val );
}
else if ( klass.equals("BSON::DBRef") ) { else if ( klass.equals("BSON::DBRef") ) {
RubyHash ref = (RubyHash)JavaEmbedUtils.invokeMethod(_runtime, val, RubyHash ref = (RubyHash)JavaEmbedUtils.invokeMethod(_runtime, val,
"to_hash", new Object[] {}, Object.class); "to_hash", new Object[] {}, Object.class);
@ -504,6 +507,18 @@ public class RubyBSONEncoder extends BSONEncoder {
_buf.writeInt( ts.getTime() ); _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 ){ protected void putRubyCodeWScope( String name , RubyObject code ){
_put( CODE_W_SCOPE , name ); _put( CODE_W_SCOPE , name );
int temp = _buf.getPosition(); int temp = _buf.getPosition();

View File

@ -100,6 +100,7 @@ require 'bson/types/code'
require 'bson/types/dbref' require 'bson/types/dbref'
require 'bson/types/object_id' require 'bson/types/object_id'
require 'bson/types/min_max_keys' require 'bson/types/min_max_keys'
require 'bson/types/timestamp'
require 'base64' require 'base64'
require 'bson/ordered_hash' require 'bson/ordered_hash'

View File

@ -192,6 +192,8 @@ module BSON
serialize_max_key_element(@buf, k) serialize_max_key_element(@buf, k)
when MINKEY when MINKEY
serialize_min_key_element(@buf, k) serialize_min_key_element(@buf, k)
when TIMESTAMP
serialize_timestamp_element(@buf, k, v)
else else
raise "unhandled type #{type}" raise "unhandled type #{type}"
end end
@ -261,8 +263,7 @@ module BSON
doc[key] = deserialize_code_w_scope_data(@buf) doc[key] = deserialize_code_w_scope_data(@buf)
when TIMESTAMP when TIMESTAMP
key = deserialize_cstr(@buf) key = deserialize_cstr(@buf)
doc[key] = [deserialize_number_int_data(@buf), doc[key] = deserialize_timestamp_data(@buf)
deserialize_number_int_data(@buf)]
when MAXKEY when MAXKEY
key = deserialize_cstr(@buf) key = deserialize_cstr(@buf)
doc[key] = MaxKey.new doc[key] = MaxKey.new
@ -346,6 +347,12 @@ module BSON
Regexp.new(str, opts) Regexp.new(str, opts)
end end
def deserialize_timestamp_data(buf)
increment = buf.get_int
seconds = buf.get_int
Timestamp.new(seconds, increment)
end
def encoded_str(str) def encoded_str(str)
if RUBY_VERSION >= '1.9' if RUBY_VERSION >= '1.9'
str.force_encoding("utf-8") str.force_encoding("utf-8")
@ -510,6 +517,14 @@ module BSON
self.class.serialize_key(buf, key) self.class.serialize_key(buf, key)
end 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) def serialize_oid_element(buf, key, val)
buf.put(OID) buf.put(OID)
self.class.serialize_key(buf, key) self.class.serialize_key(buf, key)
@ -598,6 +613,8 @@ module BSON
MAXKEY MAXKEY
when MinKey when MinKey
MINKEY MINKEY
when Timestamp
TIMESTAMP
when Numeric when Numeric
raise InvalidDocument, "Cannot serialize the Numeric type #{o.class} as BSON; only Fixum, Bignum, and Float are supported." raise InvalidDocument, "Cannot serialize the Numeric type #{o.class} as BSON; only Fixum, Bignum, and Float are supported."
when Date, DateTime when Date, DateTime

View File

@ -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

View File

@ -410,15 +410,24 @@ class BSONTest < Test::Unit::TestCase
if !(RUBY_PLATFORM =~ /java/) if !(RUBY_PLATFORM =~ /java/)
def test_timestamp def test_timestamp
val = {"test" => [4, 20]} val = {"test" => [4, 20]}
assert_equal val, @encoder.deserialize([0x13, 0x00, 0x00, 0x00, result = @encoder.deserialize([0x13, 0x00, 0x00, 0x00,
0x11, 0x74, 0x65, 0x73, 0x11, 0x74, 0x65, 0x73,
0x74, 0x00, 0x04, 0x00, 0x74, 0x00, 0x04, 0x00,
0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00,
0x00, 0x00, 0x00]) 0x00, 0x00, 0x00])
assert_equal 4, result["test"][0]
assert_equal 20, result["test"][1]
end end
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 def test_overflow
doc = {"x" => 2**75} doc = {"x" => 2**75}
assert_raise RangeError do assert_raise RangeError do

View File

@ -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