BSON for JRuby
This commit is contained in:
parent
4141331f79
commit
2a7b089a9b
23
Rakefile
23
Rakefile
|
@ -13,6 +13,29 @@ require 'rbconfig'
|
|||
include Config
|
||||
ENV['TEST_MODE'] = 'TRUE'
|
||||
|
||||
task :java do
|
||||
Rake::Task['build:java'].invoke
|
||||
Rake::Task['test:ruby'].invoke
|
||||
end
|
||||
|
||||
namespace :build do
|
||||
desc "Build the java extensions."
|
||||
task :java do
|
||||
puts "Building Java extensions..."
|
||||
java_dir = File.join(File.dirname(__FILE__), 'ext', 'java')
|
||||
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')
|
||||
|
||||
src_base = File.join(java_dir, 'src')
|
||||
|
||||
system("javac -Xlint:unchecked -classpath #{jruby_jar}:#{mongo_jar}:#{bson_jar} #{File.join(src_base, 'org', 'jbson', '*.java')}")
|
||||
system("cd #{src_base} && jar cf #{File.join(jar_dir, 'jbson.jar')} #{File.join('.', 'org', 'jbson', '*.class')}")
|
||||
end
|
||||
end
|
||||
|
||||
desc "Test the MongoDB Ruby driver."
|
||||
task :test do
|
||||
puts "\nThis option has changed."
|
||||
|
|
|
@ -14,6 +14,7 @@ Gem::Specification.new do |s|
|
|||
s.files = ['Rakefile', 'bson.gemspec', 'LICENSE.txt']
|
||||
s.files += ['lib/bson.rb'] + Dir['lib/bson/**/*.rb']
|
||||
s.files += ['bin/b2json', 'bin/j2bson']
|
||||
s.files += Dir['ext/java/jar/**/*.jar']
|
||||
s.test_files = Dir['test/mongo_bson/*.rb']
|
||||
|
||||
s.executables = ['b2json', 'j2bson']
|
||||
|
|
Binary file not shown.
|
@ -0,0 +1,382 @@
|
|||
// 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 RubyBSONCallback 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<RubyObject> _stack = new LinkedList<RubyObject>();
|
||||
private final LinkedList<String> _nameStack = new LinkedList<String>();
|
||||
private Ruby _runtime;
|
||||
static final HashMap<Ruby, HashMap> _runtimeCache = new HashMap<Ruby, HashMap>();
|
||||
|
||||
public RubyBSONCallback(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 RubyArray createArray() {
|
||||
return RubyArray.newArray(_runtime);
|
||||
}
|
||||
|
||||
public RubyObject create( boolean array , List<String> path ){
|
||||
if ( array )
|
||||
return createArray();
|
||||
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 );
|
||||
|
||||
RubyObject lastObject = _stack.getLast();
|
||||
|
||||
// Yes, this is a bit hacky.
|
||||
if(lastObject instanceof RubyHash) {
|
||||
writeRubyHash(key, (RubyHash)lastObject, (IRubyObject)hash);
|
||||
}
|
||||
else {
|
||||
writeRubyArray(key, (RubyArray)lastObject, (IRubyObject)hash);
|
||||
}
|
||||
|
||||
_stack.addLast( (RubyObject)hash );
|
||||
}
|
||||
|
||||
public void writeRubyHash(String key, RubyHash hash, IRubyObject 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){
|
||||
RubyArray array = createArray();
|
||||
|
||||
RubyObject lastObject = _stack.getLast();
|
||||
_nameStack.addLast( key );
|
||||
|
||||
if(lastObject instanceof RubyHash) {
|
||||
writeRubyHash(key, (RubyHash)lastObject, (IRubyObject)array);
|
||||
}
|
||||
else {
|
||||
writeRubyArray(key, (RubyArray)lastObject, (IRubyObject)array);
|
||||
}
|
||||
|
||||
_stack.addLast( (RubyObject)array );
|
||||
}
|
||||
|
||||
public RubyObject objectDone(){
|
||||
RubyObject 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 RubyObject arrayDone(){
|
||||
return objectDone();
|
||||
}
|
||||
|
||||
public void gotNull( String name ){
|
||||
_put(name, (RubyObject)_runtime.getNil());
|
||||
}
|
||||
|
||||
// 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 ){
|
||||
RubyString code_string = _runtime.newString( code );
|
||||
Object rb_code_obj = JavaEmbedUtils.invokeMethod(_runtime, _rbclsCode,
|
||||
"new", new Object[] { code_string }, Object.class);
|
||||
_put( name , (RubyObject)rb_code_obj );
|
||||
}
|
||||
|
||||
public void gotCodeWScope( String name , String code , Object scope ){
|
||||
RubyString code_string = _runtime.newString( code );
|
||||
|
||||
Object rb_code_obj = JavaEmbedUtils.invokeMethod(_runtime, _rbclsCode,
|
||||
"new", new Object[] { code_string, (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 ){
|
||||
RubyBoolean b = RubyBoolean.newBoolean( _runtime, v );
|
||||
_put(name , b);
|
||||
}
|
||||
|
||||
public void gotDouble( String name , double v ){
|
||||
RubyFloat f = new RubyFloat( _runtime, v );
|
||||
_put(name , (RubyObject)f);
|
||||
}
|
||||
|
||||
public void gotInt( String name , int v ){
|
||||
RubyFixnum f = new RubyFixnum( _runtime, v );
|
||||
_put(name , (RubyObject)f);
|
||||
}
|
||||
|
||||
public void gotLong( String name , long v ){
|
||||
RubyFixnum f = new RubyFixnum( _runtime, v );
|
||||
_put(name , (RubyObject)f);
|
||||
}
|
||||
|
||||
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 ){
|
||||
RubyString str = RubyString.newString(_runtime, v);
|
||||
_put( name , str );
|
||||
}
|
||||
|
||||
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());
|
||||
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<b.length; i++ ) {
|
||||
result.aset( RubyNumeric.dbl2num( _runtime, (double)i ), RubyNumeric.dbl2num( _runtime, (double)b[i] ) );
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void gotBinaryArray( String name , byte[] b ) {
|
||||
RubyArray a = ja2ra( b );
|
||||
|
||||
Object[] args = new Object[] { a, 2 };
|
||||
|
||||
Object result = JavaEmbedUtils.invokeMethod(_runtime, _rbclsBinary, "new", args, Object.class);
|
||||
|
||||
_put( name, (RubyObject)result );
|
||||
}
|
||||
|
||||
// TODO: fix abs stuff here. some kind of bad type issue
|
||||
public void gotBinary( String name , byte type , byte[] data ){
|
||||
RubyArray a = ja2ra( data );
|
||||
|
||||
Object[] args = new Object[] { a, RubyFixnum.newFixnum(_runtime, Math.abs( type )) };
|
||||
|
||||
Object result = JavaEmbedUtils.invokeMethod(_runtime, _rbclsBinary, "new", args, Object.class);
|
||||
|
||||
_put( name, (RubyObject)result );
|
||||
}
|
||||
|
||||
protected void _put( String name , RubyObject o ){
|
||||
RubyObject current = cur();
|
||||
if(current instanceof RubyArray) {
|
||||
RubyArray a = (RubyArray)current;
|
||||
Long n = Long.parseLong(name);
|
||||
RubyFixnum index = new RubyFixnum(_runtime, n);
|
||||
a.aset((IRubyObject)index, (IRubyObject)o);
|
||||
}
|
||||
else {
|
||||
RubyString rkey = RubyString.newString(_runtime, name);
|
||||
JavaEmbedUtils.invokeMethod(_runtime, current, "[]=",
|
||||
new Object[] { (IRubyObject)rkey, o }, Object.class);
|
||||
}
|
||||
}
|
||||
|
||||
protected RubyObject cur(){
|
||||
return _stack.getLast();
|
||||
}
|
||||
|
||||
public Object get(){
|
||||
return _root;
|
||||
}
|
||||
|
||||
protected void setRoot(RubyHash o) {
|
||||
_root = o;
|
||||
}
|
||||
|
||||
protected boolean isStackEmpty() {
|
||||
return _stack.size() < 1;
|
||||
}
|
||||
|
||||
// Helper method for checking whether a Ruby hash has a certain key.
|
||||
private boolean _rbHashHasKey(RubyHash hash, String key) {
|
||||
RubyBoolean b = hash.has_key_p( _runtime.newString( key ) );
|
||||
return b == _runtime.getTrue();
|
||||
}
|
||||
|
||||
// Helper method for getting a value from a Ruby hash.
|
||||
private IRubyObject _rbHashGet(RubyHash hash, Object key) {
|
||||
if (key instanceof String) {
|
||||
return hash.op_aref( _runtime.getCurrentContext(), _runtime.newString((String)key) );
|
||||
}
|
||||
else {
|
||||
return hash.op_aref( _runtime.getCurrentContext(), (RubyObject)key );
|
||||
}
|
||||
}
|
||||
|
||||
static final HashMap<String, Object> _getRuntimeCache(Ruby runtime) {
|
||||
// each JRuby runtime may have different objects for these constants,
|
||||
// so cache them separately for each runtime
|
||||
@SuppressWarnings("unchecked") // aargh! Java!
|
||||
HashMap<String, Object> cache = _runtimeCache.get( runtime );
|
||||
|
||||
if(cache == null) {
|
||||
cache = new HashMap<String, Object>();
|
||||
_runtimeCache.put( runtime, cache );
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
static final RubyModule _lookupConstant(Ruby runtime, String name)
|
||||
{
|
||||
HashMap<String, Object> 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;
|
||||
}
|
||||
}
|
Binary file not shown.
|
@ -0,0 +1,27 @@
|
|||
// RubyBSONDecoder.java
|
||||
|
||||
package org.jbson;
|
||||
|
||||
import static org.bson.BSON.*;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import org.jruby.*;
|
||||
|
||||
import org.bson.*;
|
||||
import org.bson.io.*;
|
||||
import org.bson.types.*;
|
||||
|
||||
public class RubyBSONDecoder extends BSONDecoder {
|
||||
|
||||
// public int decode( RubyString s , BSONCallback callback ){
|
||||
// byte[] b = s.getBytes();
|
||||
// try {
|
||||
// return decode( new Input( new ByteArrayInputStream(b) ) , callback );
|
||||
// }
|
||||
// catch ( IOException ioe ){
|
||||
// throw new RuntimeException( "should be impossible" , ioe );
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
Binary file not shown.
|
@ -0,0 +1,764 @@
|
|||
// BSONEncoder.java
|
||||
|
||||
package org.jbson;
|
||||
|
||||
import static org.bson.BSON.*;
|
||||
|
||||
import java.nio.*;
|
||||
import java.nio.charset.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.*;
|
||||
import java.util.regex.*;
|
||||
import java.io.*;
|
||||
import org.jruby.util.Pack;
|
||||
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
import org.bson.BSONEncoder;
|
||||
|
||||
import org.jruby.javasupport.JavaEmbedUtils;
|
||||
import org.jruby.javasupport.JavaUtil;
|
||||
|
||||
import org.jruby.*;
|
||||
import org.jruby.runtime.builtin.IRubyObject;
|
||||
|
||||
import org.jruby.parser.ReOptions;
|
||||
|
||||
import org.jcodings.Encoding;
|
||||
|
||||
import org.bson.BSONObject;
|
||||
import org.bson.io.*;
|
||||
import org.bson.types.*;
|
||||
import org.bson.BSON;
|
||||
|
||||
import org.jruby.exceptions.RaiseException;
|
||||
|
||||
import org.jruby.java.addons.ArrayJavaAddons;
|
||||
|
||||
/**
|
||||
* this is meant to be pooled or cached
|
||||
* there is some per instance memory for string conversion, etc...
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public class RubyBSONEncoder extends BSONEncoder {
|
||||
|
||||
static final boolean DEBUG = false;
|
||||
static final Map _runtimeCache = new HashMap();
|
||||
|
||||
private Ruby _runtime;
|
||||
|
||||
private RubyModule _rbclsByteBuffer;
|
||||
private RubyModule _rbclsDBRef;
|
||||
private RubyModule _rbclsInvalidDocument;
|
||||
private RubyModule _rbclsInvalidKeyName;
|
||||
private RubyModule _rbclsRangeError;
|
||||
private RubySymbol _idAsSym;
|
||||
private RubyString _idAsString;
|
||||
private RubyString _tfAsString;
|
||||
|
||||
private static final int BIT_SIZE = 64;
|
||||
private static final long MAX = (1L << (BIT_SIZE - 1)) - 1;
|
||||
private static final BigInteger LONG_MAX = BigInteger.valueOf(MAX);
|
||||
private static final BigInteger LONG_MIN = BigInteger.valueOf(-MAX - 1);
|
||||
|
||||
|
||||
public RubyBSONEncoder(Ruby runtime){
|
||||
_runtime = runtime;
|
||||
_rbclsByteBuffer = _lookupConstant( _runtime, "BSON::ByteBuffer" );
|
||||
_rbclsDBRef = _lookupConstant( _runtime, "BSON::DBRef" );
|
||||
_rbclsInvalidDocument = _lookupConstant( _runtime, "BSON::InvalidDocument" );
|
||||
_rbclsInvalidKeyName = _lookupConstant( _runtime, "BSON::InvalidKeyName" );
|
||||
_rbclsRangeError = _lookupConstant( _runtime, "RangeError" );
|
||||
_idAsSym = _lookupSymbol( _runtime, "_id" );
|
||||
_tfAsString = _lookupString( _runtime, "_transientFields" );
|
||||
|
||||
if(_idAsString == null) {
|
||||
_idAsString = _runtime.newString( "_id" );
|
||||
}
|
||||
}
|
||||
|
||||
public RubyString encode( Object arg ) {
|
||||
RubyHash o = (RubyHash)arg;
|
||||
BasicOutputBuffer buf = new BasicOutputBuffer();
|
||||
set( buf );
|
||||
putObject( o );
|
||||
done();
|
||||
|
||||
RubyString b = RubyString.newString(_runtime, buf.toByteArray());
|
||||
return b;
|
||||
}
|
||||
|
||||
public void set( OutputBuffer out ){
|
||||
if ( _buf != null ) {
|
||||
done();
|
||||
throw new IllegalStateException( "in the middle of something" );
|
||||
}
|
||||
|
||||
_buf = out;
|
||||
}
|
||||
|
||||
public void done(){
|
||||
_buf = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if object was handled
|
||||
*/
|
||||
protected boolean handleSpecialObjects( String name , RubyObject o ){
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Encodes a <code>BSONObject</code>.
|
||||
* This is for the higher level api calls
|
||||
* @param o the object to encode
|
||||
* @return the number of characters in the encoding
|
||||
*/
|
||||
public int putObject( RubyObject o ) {
|
||||
return putObject( null, o );
|
||||
}
|
||||
|
||||
/**
|
||||
* this is really for embedded objects
|
||||
*/
|
||||
int putObject( String name , RubyObject o ){
|
||||
if ( o == null )
|
||||
throw new NullPointerException( "can't save a null object" );
|
||||
|
||||
final int start = _buf.getPosition();
|
||||
|
||||
byte myType = OBJECT;
|
||||
if ( o instanceof RubyArray )
|
||||
myType = ARRAY;
|
||||
|
||||
if ( handleSpecialObjects( name , o ) )
|
||||
return _buf.getPosition() - start;
|
||||
|
||||
if ( name != null ){
|
||||
_put( myType , name );
|
||||
}
|
||||
|
||||
final int sizePos = _buf.getPosition();
|
||||
_buf.writeInt( 0 ); // leaving space for sthis. set it at the end
|
||||
|
||||
List transientFields = null;
|
||||
boolean rewriteID = ( myType == OBJECT && name == null );
|
||||
|
||||
|
||||
if ( myType == OBJECT ) {
|
||||
|
||||
if ( rewriteID ) {
|
||||
|
||||
if ( _rbHashHasKey( (RubyHash)o, "_id" ) ) {
|
||||
_putObjectField( "_id" , _rbHashGet( (RubyHash)o, _idAsString ) );
|
||||
}
|
||||
else if ( ( _rbHashHasKey( (RubyHash)o, _idAsSym )) ) {
|
||||
_putObjectField( "_id" , _rbHashGet( (RubyHash)o, _idAsSym ) );
|
||||
}
|
||||
|
||||
RubyObject temp = (RubyObject)_rbHashGet( (RubyHash)o, _tfAsString );
|
||||
if ( temp instanceof RubyArray )
|
||||
transientFields = (RubyArray)temp;
|
||||
}
|
||||
|
||||
// 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[] {}
|
||||
, Object.class);
|
||||
|
||||
for (Iterator<RubyObject> 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();
|
||||
}
|
||||
|
||||
testNull(str);
|
||||
|
||||
// If we're rewriting the _id, we can move on.
|
||||
if ( rewriteID && str.equals( "_id" ) )
|
||||
continue;
|
||||
|
||||
RubyObject val = (RubyObject)_rbHashGet( (RubyHash)o, hashKey );
|
||||
_putObjectField( str , (Object)val );
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we're within the 4MB limit
|
||||
if ( _buf.size() > 4 * 1024 * 1024 ) {
|
||||
_rbRaise( (RubyClass)_rbclsInvalidDocument,
|
||||
"Document is too large (" + _buf.size() + "). BSON documents are limited to 4MB (" +
|
||||
4 * 1024 * 1024 + ").");
|
||||
}
|
||||
|
||||
_buf.write( EOO );
|
||||
|
||||
_buf.writeInt( sizePos , _buf.getPosition() - sizePos );
|
||||
return _buf.getPosition() - start;
|
||||
}
|
||||
|
||||
protected void _putObjectField( String name , Object val ){
|
||||
|
||||
if ( name.equals( "_transientFields" ) )
|
||||
return;
|
||||
|
||||
if ( DEBUG ) {
|
||||
System.out.println( "\t put thing : " + name );
|
||||
System.out.println( "\t class : " + val.getClass().getName() );
|
||||
}
|
||||
|
||||
if ( name.equals( "$where") && val instanceof String ){
|
||||
_put( CODE , name );
|
||||
_putValueString( val.toString() );
|
||||
return;
|
||||
}
|
||||
|
||||
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 Boolean )
|
||||
putBoolean(name, (Boolean)val);
|
||||
|
||||
else if ( val instanceof Map )
|
||||
putMap( name , (Map)val );
|
||||
|
||||
else if ( val instanceof Iterable)
|
||||
putIterable( name , (Iterable)val );
|
||||
|
||||
else if ( val instanceof byte[] )
|
||||
putBinary( name , (byte[])val );
|
||||
|
||||
else if ( val.getClass().isArray() )
|
||||
putIterable( name , Arrays.asList( (Object[])val ) );
|
||||
|
||||
else if ( val instanceof RubyObject ) {
|
||||
|
||||
if ( val instanceof RubyString ) {
|
||||
putRubyString(name, ((RubyString)val).getUnicodeValue() );
|
||||
}
|
||||
|
||||
else if (val instanceof RubySymbol) {
|
||||
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) {
|
||||
putNumber(name, (int)jval );
|
||||
}
|
||||
else
|
||||
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 RubyNil )
|
||||
putNull(name);
|
||||
|
||||
else if ( val instanceof RubyTime ) {
|
||||
putDate( name , ((RubyTime)val).getDateTime().getMillis() );
|
||||
}
|
||||
|
||||
else if ( val instanceof RubyBoolean )
|
||||
putBoolean(name, (Boolean)((RubyBoolean)val).toJava(Boolean.class));
|
||||
|
||||
else if ( val instanceof RubyRegexp )
|
||||
putRubyRegexp(name, (RubyRegexp)val );
|
||||
|
||||
else if (val instanceof RubyBignum) {
|
||||
BigInteger big = ((RubyBignum)val).getValue();
|
||||
if( big.compareTo(LONG_MAX) > 0 || big.compareTo(LONG_MIN) < 0 ) {
|
||||
_rbRaise( (RubyClass)_rbclsRangeError , "MongoDB can only handle 8-byte ints" );
|
||||
}
|
||||
else {
|
||||
long jval = big.longValue();
|
||||
putNumber(name, (Number)jval );
|
||||
}
|
||||
}
|
||||
|
||||
// This is where we handle special types defined in the Ruby BSON.
|
||||
else {
|
||||
String klass = JavaEmbedUtils.invokeMethod(_runtime, val,
|
||||
"class", new Object[] {}, Object.class).toString();
|
||||
|
||||
if( klass.equals( "BSON::ObjectId" ) ) {
|
||||
putRubyObjectId(name, (RubyObject)val );
|
||||
}
|
||||
else if( klass.equals( "BSON::ObjectID" ) ) {
|
||||
putRubyObjectId(name, (RubyObject)val );
|
||||
}
|
||||
else if ( klass.equals( "BSON::Code" ) ) {
|
||||
putRubyCodeWScope(name, (RubyObject)val );
|
||||
}
|
||||
else if ( klass.equals( "BSON::Binary" ) ) {
|
||||
putRubyBinary( name , (RubyObject)val );
|
||||
}
|
||||
else if ( klass.equals("BSON::MinKey") ) {
|
||||
_put( MINKEY, name );
|
||||
}
|
||||
else if ( klass.equals("BSON::MaxKey") ) {
|
||||
_put( MAXKEY, name );
|
||||
}
|
||||
else if ( klass.equals("BSON::DBRef") ) {
|
||||
RubyHash ref = (RubyHash)JavaEmbedUtils.invokeMethod(_runtime, val,
|
||||
"to_hash", new Object[] {}, Object.class);
|
||||
putMap( name , (Map)ref );
|
||||
}
|
||||
else if ( klass.equals("Date") || klass.equals("DateTime") ||
|
||||
klass.equals("ActiveSupport::TimeWithZone") ) {
|
||||
|
||||
_rbRaise( (RubyClass)_rbclsInvalidDocument,
|
||||
klass + " is not currently supported; use a UTC Time instance instead.");
|
||||
}
|
||||
else {
|
||||
_rbRaise( (RubyClass)_rbclsInvalidDocument,
|
||||
"Cannot serialize " + klass + " as a BSON type; " +
|
||||
"it either isn't supported or won't translate to BSON.");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
||||
private void testNull(String str) {
|
||||
byte[] bytes = str.getBytes();
|
||||
|
||||
for(int j = 0; j < bytes.length; j++ ) {
|
||||
if(bytes[j] == '\u0000') {
|
||||
_rbRaise( (RubyClass)_rbclsInvalidDocument, "Null not allowed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void putIterable( String name , Iterable l ){
|
||||
_put( ARRAY , name );
|
||||
final int sizePos = _buf.getPosition();
|
||||
_buf.writeInt( 0 );
|
||||
|
||||
int i=0;
|
||||
for ( Object obj: l ) {
|
||||
_putObjectField( String.valueOf( i ) , obj );
|
||||
i++;
|
||||
}
|
||||
|
||||
|
||||
_buf.write( EOO );
|
||||
_buf.writeInt( sizePos , _buf.getPosition() - sizePos );
|
||||
}
|
||||
|
||||
private void putMap( String name , Map m ){
|
||||
_put( OBJECT , name );
|
||||
final int sizePos = _buf.getPosition();
|
||||
_buf.writeInt( 0 );
|
||||
|
||||
for ( Map.Entry entry : (Set<Map.Entry>)m.entrySet() )
|
||||
_putObjectField( entry.getKey().toString() , entry.getValue() );
|
||||
|
||||
_buf.write( EOO );
|
||||
_buf.writeInt( sizePos , _buf.getPosition() - sizePos );
|
||||
}
|
||||
|
||||
|
||||
protected void putNull( String name ){
|
||||
_put( NULL , name );
|
||||
}
|
||||
|
||||
protected void putUndefined(String name){
|
||||
_put(UNDEFINED, name);
|
||||
}
|
||||
|
||||
protected void putTimestamp(String name, BSONTimestamp ts ){
|
||||
_put( TIMESTAMP , name );
|
||||
_buf.writeInt( ts.getInc() );
|
||||
_buf.writeInt( ts.getTime() );
|
||||
}
|
||||
|
||||
protected void putRubyCodeWScope( String name , RubyObject code ){
|
||||
_put( CODE_W_SCOPE , name );
|
||||
int temp = _buf.getPosition();
|
||||
_buf.writeInt( 0 );
|
||||
|
||||
String code_string = (String)JavaEmbedUtils.invokeMethod(_runtime, code,
|
||||
"code", new Object[] {}, Object.class);
|
||||
|
||||
_putValueString( code_string );
|
||||
putObject( (RubyObject)JavaEmbedUtils.invokeMethod(_runtime, code, "scope", new Object[] {}, Object.class) );
|
||||
_buf.writeInt( temp , _buf.getPosition() - temp );
|
||||
}
|
||||
|
||||
protected void putCodeWScope( String name , CodeWScope code ){
|
||||
_put( CODE_W_SCOPE , name );
|
||||
int temp = _buf.getPosition();
|
||||
_buf.writeInt( 0 );
|
||||
_putValueString( code.getCode() );
|
||||
_buf.writeInt( temp , _buf.getPosition() - temp );
|
||||
}
|
||||
|
||||
protected void putBoolean( String name , Boolean b ){
|
||||
_put( BOOLEAN , name );
|
||||
_buf.write( b ? (byte)0x1 : (byte)0x0 );
|
||||
}
|
||||
|
||||
protected void putDate( String name , long millis ){
|
||||
_put( DATE , name );
|
||||
_buf.writeLong( millis );
|
||||
}
|
||||
|
||||
protected void putNumber( String name , Number n ){
|
||||
if ( n instanceof Integer ||
|
||||
n instanceof Short ||
|
||||
n instanceof Byte ||
|
||||
n instanceof AtomicInteger ){
|
||||
_put( NUMBER_INT , name );
|
||||
_buf.writeInt( n.intValue() );
|
||||
}
|
||||
else if ( n instanceof Long ||
|
||||
n instanceof AtomicLong ) {
|
||||
_put( NUMBER_LONG , name );
|
||||
_buf.writeLong( n.longValue() );
|
||||
}
|
||||
else {
|
||||
_put( NUMBER , name );
|
||||
_buf.writeDouble( n.doubleValue() );
|
||||
}
|
||||
}
|
||||
|
||||
private void putRubyBinary( String name , RubyObject binary ) {
|
||||
RubyArray rarray = (RubyArray)JavaEmbedUtils.invokeMethod(_runtime,
|
||||
binary, "to_a", new Object[] {}, Object.class);
|
||||
Long rbSubtype = (Long)JavaEmbedUtils.invokeMethod(_runtime,
|
||||
binary, "subtype", new Object[] {}, Object.class);
|
||||
long subtype = rbSubtype.longValue();
|
||||
byte[] data = ra2ba( rarray );
|
||||
if ( subtype == 2 ) {
|
||||
putBinary( name, data );
|
||||
}
|
||||
else {
|
||||
_put( BINARY , name );
|
||||
_buf.writeInt( data.length );
|
||||
_buf.write( (byte)subtype );
|
||||
_buf.write( data );
|
||||
}
|
||||
}
|
||||
|
||||
protected void putBinary( String name , byte[] data ){
|
||||
_put( BINARY , name );
|
||||
_buf.writeInt( 4 + data.length );
|
||||
|
||||
_buf.write( B_BINARY );
|
||||
_buf.writeInt( data.length );
|
||||
int before = _buf.getPosition();
|
||||
_buf.write( data );
|
||||
int after = _buf.getPosition();
|
||||
|
||||
com.mongodb.util.MyAsserts.assertEquals( after - before , data.length );
|
||||
}
|
||||
|
||||
protected void putBinary( String name , Binary val ){
|
||||
_put( BINARY , name );
|
||||
_buf.writeInt( val.length() );
|
||||
_buf.write( val.getType() );
|
||||
_buf.write( val.getData() );
|
||||
}
|
||||
|
||||
protected void putUUID( String name , UUID val ){
|
||||
_put( BINARY , name );
|
||||
_buf.writeInt( 4 + 64*2);
|
||||
_buf.write( 3 );// B_UUID );
|
||||
_buf.writeLong( val.getMostSignificantBits());
|
||||
_buf.writeLong( val.getLeastSignificantBits());
|
||||
}
|
||||
|
||||
protected void putSymbol( String name , Symbol s ){
|
||||
_putString(name, s.getSymbol(), SYMBOL);
|
||||
}
|
||||
|
||||
protected void putRubyString( String name , String s ) {
|
||||
_put( STRING , name );
|
||||
_putValueString( s );
|
||||
}
|
||||
|
||||
protected void putString(String name, String s) {
|
||||
_putString(name, s, STRING);
|
||||
}
|
||||
|
||||
private void _putString( String name , String s, byte type ){
|
||||
_put( type , name );
|
||||
_putValueString( s );
|
||||
}
|
||||
|
||||
private void putRubyObjectId( String name, RubyObject oid ) {
|
||||
_put( OID , name );
|
||||
|
||||
RubyArray roid = (RubyArray)JavaEmbedUtils.invokeMethod(_runtime, oid,
|
||||
"to_a", new Object[] {}, Object.class);
|
||||
byte[] joid = ra2ba( (RubyArray)roid );
|
||||
|
||||
_buf.writeInt( convertToInt(joid, 0) );
|
||||
_buf.writeInt( convertToInt(joid, 4) );
|
||||
_buf.writeInt( convertToInt(joid, 8) );
|
||||
}
|
||||
|
||||
private void putRubyRegexp( String name, RubyRegexp r ) {
|
||||
RubyString source = (RubyString)r.source();
|
||||
testNull(source.toString());
|
||||
|
||||
_put( REGEX , name );
|
||||
_put( (String)((RubyString)source).toJava(String.class) );
|
||||
|
||||
int regexOptions = (int)((RubyFixnum)r.options()).getLongValue();
|
||||
String options = "";
|
||||
|
||||
if( (regexOptions & ReOptions.RE_OPTION_IGNORECASE) != 0 )
|
||||
options = options.concat( "i" );
|
||||
|
||||
if( (regexOptions & ReOptions.RE_OPTION_MULTILINE) != 0 )
|
||||
options = options.concat( "m" );
|
||||
|
||||
if( (regexOptions & ReOptions.RE_OPTION_EXTENDED) != 0 )
|
||||
options = options.concat( "x" );
|
||||
|
||||
_put( options );
|
||||
}
|
||||
|
||||
// ---------------------------------------------
|
||||
// Ruby-based helper methods.
|
||||
|
||||
// Converts four bytes from a byte array to an int
|
||||
private int convertToInt(byte[] b, int offset) {
|
||||
int intVal = ((b[offset + 3] & 0xff) << 24) | ((b[offset + 2] & 0xff) << 16) | ((b[offset + 1] & 0xff) << 8) | ((b[offset] & 0xff));
|
||||
|
||||
return intVal;
|
||||
}
|
||||
|
||||
// Ruby array to byte array
|
||||
private byte[] ra2ba( RubyArray rArray ) {
|
||||
int len = rArray.getLength();
|
||||
byte[] b = new byte[len];
|
||||
int n = 0;
|
||||
|
||||
for ( Iterator<Object> i = rArray.iterator(); i.hasNext(); ) {
|
||||
Object value = i.next();
|
||||
b[n] = (byte)((Long)value).intValue();
|
||||
n++;
|
||||
}
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
// Helper method for getting a value from a Ruby hash.
|
||||
private IRubyObject _rbHashGet(RubyHash hash, Object key) {
|
||||
if (key instanceof String) {
|
||||
return hash.op_aref( _runtime.getCurrentContext(), _runtime.newString((String)key) );
|
||||
}
|
||||
else {
|
||||
return hash.op_aref( _runtime.getCurrentContext(), (RubyObject)key );
|
||||
}
|
||||
}
|
||||
|
||||
// Helper method for checking whether a Ruby hash has a certain key.
|
||||
private boolean _rbHashHasKey(RubyHash hash, String key) {
|
||||
RubyBoolean b = hash.has_key_p( _runtime.newString( key ) );
|
||||
return b == _runtime.getTrue();
|
||||
}
|
||||
|
||||
private boolean _rbHashHasKey(RubyHash hash, RubySymbol sym) {
|
||||
RubyBoolean b = hash.has_key_p( sym );
|
||||
return b == _runtime.getTrue();
|
||||
}
|
||||
|
||||
// Helper method for setting a value in a Ruby hash.
|
||||
private IRubyObject _rbHashSet(RubyHash hash, String key, IRubyObject value) {
|
||||
return hash.op_aset( _runtime.getCurrentContext(), _runtime.newString( key ), value );
|
||||
}
|
||||
|
||||
|
||||
// Helper method for returning all keys from a Ruby hash.
|
||||
private RubyArray _rbHashKeys(RubyHash hash) {
|
||||
return hash.keys();
|
||||
}
|
||||
|
||||
// Helper for raising a Ruby exception and aborting serialization.
|
||||
private RaiseException _rbRaise( RubyClass exceptionClass , String message ) {
|
||||
done();
|
||||
throw new RaiseException( _runtime, exceptionClass, message, true );
|
||||
}
|
||||
|
||||
// ----------------------------------------------
|
||||
|
||||
/**
|
||||
* Encodes the type and key.
|
||||
*
|
||||
*/
|
||||
protected void _put( byte type , String name ){
|
||||
_buf.write( type );
|
||||
_put( name );
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the type and key without checking the validity of the key.
|
||||
*
|
||||
*/
|
||||
protected void _putWithoutCheck( byte type , String name ){
|
||||
_buf.write( type );
|
||||
_put( name );
|
||||
}
|
||||
|
||||
protected void _putValueString( String s ){
|
||||
int lenPos = _buf.getPosition();
|
||||
_buf.writeInt( 0 ); // making space for size
|
||||
int strLen = _put( s );
|
||||
_buf.writeInt( lenPos , strLen );
|
||||
}
|
||||
|
||||
void _reset( Buffer b ){
|
||||
b.position(0);
|
||||
b.limit( b.capacity() );
|
||||
}
|
||||
|
||||
/**
|
||||
* puts as utf-8 string
|
||||
*/
|
||||
protected int _put( String str ){
|
||||
int total = 0;
|
||||
|
||||
final int len = str.length();
|
||||
int pos = 0;
|
||||
while ( pos < len ){
|
||||
_reset( _stringC );
|
||||
_reset( _stringB );
|
||||
|
||||
int toEncode = Math.min( _stringC.capacity() - 1, len - pos );
|
||||
_stringC.put( str , pos , pos + toEncode );
|
||||
_stringC.flip();
|
||||
|
||||
CoderResult cr = _encoder.encode( _stringC , _stringB , false );
|
||||
|
||||
if ( cr.isMalformed() || cr.isUnmappable() )
|
||||
throw new IllegalArgumentException( "malforumed string" );
|
||||
|
||||
if ( cr.isOverflow() )
|
||||
throw new RuntimeException( "overflor should be impossible" );
|
||||
|
||||
if ( cr.isError() )
|
||||
throw new RuntimeException( "should never get here" );
|
||||
|
||||
if ( ! cr.isUnderflow() )
|
||||
throw new RuntimeException( "this should always be true" );
|
||||
|
||||
total += _stringB.position();
|
||||
_buf.write( _stringB.array() , 0 , _stringB.position() );
|
||||
|
||||
pos += toEncode;
|
||||
}
|
||||
|
||||
_buf.write( (byte)0 );
|
||||
total++;
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
public void writeInt( int x ){
|
||||
_buf.writeInt( x );
|
||||
}
|
||||
|
||||
public void writeLong( long x ){
|
||||
_buf.writeLong( x );
|
||||
}
|
||||
|
||||
public void writeCString( String s ){
|
||||
_put( s );
|
||||
}
|
||||
|
||||
protected OutputBuffer _buf;
|
||||
|
||||
private CharBuffer _stringC = CharBuffer.wrap( new char[256 + 1] );
|
||||
private ByteBuffer _stringB = ByteBuffer.wrap( new byte[1024 + 1] );
|
||||
private CharsetEncoder _encoder = Charset.forName( "UTF-8" ).newEncoder();
|
||||
|
||||
static final Map _getRuntimeCache(Ruby runtime) {
|
||||
// each JRuby runtime may have different objects for these constants,
|
||||
// so cache them separately for each runtime
|
||||
Map cache = (Map) _runtimeCache.get( runtime );
|
||||
|
||||
if(cache == null) {
|
||||
cache = new HashMap();
|
||||
_runtimeCache.put( runtime, cache );
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
static final RubyModule _lookupConstant(Ruby runtime, String name)
|
||||
{
|
||||
Map cache = (Map) _getRuntimeCache( runtime );
|
||||
RubyModule module = (RubyModule) cache.get( name );
|
||||
|
||||
if(module == null && !cache.containsKey( name )) {
|
||||
module = runtime.getClassFromPath( name );
|
||||
cache.put( name, module );
|
||||
}
|
||||
return module;
|
||||
}
|
||||
|
||||
static final RubySymbol _lookupSymbol(Ruby runtime, String name)
|
||||
{
|
||||
Map cache = (Map) _getRuntimeCache( runtime );
|
||||
RubySymbol symbol = (RubySymbol) cache.get( name );
|
||||
|
||||
if(symbol == null && !cache.containsKey( name )) {
|
||||
symbol = runtime.newSymbol( name );
|
||||
cache.put( name, symbol );
|
||||
}
|
||||
return symbol;
|
||||
}
|
||||
|
||||
static final RubyString _lookupString(Ruby runtime, String name)
|
||||
{
|
||||
Map cache = (Map) _getRuntimeCache( runtime );
|
||||
RubyString string = (RubyString) cache.get( name );
|
||||
|
||||
if(string == null && !cache.containsKey( name )) {
|
||||
string = runtime.newString( name );
|
||||
cache.put( name, string );
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
}
|
49
lib/bson.rb
49
lib/bson.rb
|
@ -32,28 +32,39 @@ module BSON
|
|||
end
|
||||
end
|
||||
|
||||
begin
|
||||
# Need this for running test with and without c ext in Ruby 1.9.
|
||||
raise LoadError if ENV['TEST_MODE'] && !ENV['C_EXT']
|
||||
require 'bson_ext/cbson'
|
||||
raise LoadError unless defined?(CBson::VERSION)
|
||||
if CBson::VERSION < MINIMUM_BSON_EXT_VERSION
|
||||
puts "Able to load bson_ext version #{CBson::VERSION}, but >= #{MINIMUM_BSON_EXT_VERSION} is required."
|
||||
raise LoadError
|
||||
end
|
||||
require 'bson/bson_c'
|
||||
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, 'jbson.jar')
|
||||
require 'bson/bson_java'
|
||||
module BSON
|
||||
BSON_CODER = BSON_C
|
||||
BSON_CODER = BSON_JAVA
|
||||
end
|
||||
rescue LoadError
|
||||
require 'bson/bson_ruby'
|
||||
module BSON
|
||||
BSON_CODER = BSON_RUBY
|
||||
else
|
||||
begin
|
||||
# Need this for running test with and without c ext in Ruby 1.9.
|
||||
raise LoadError if ENV['TEST_MODE'] && !ENV['C_EXT']
|
||||
require 'bson_ext/cbson'
|
||||
raise LoadError unless defined?(CBson::VERSION)
|
||||
if CBson::VERSION < MINIMUM_BSON_EXT_VERSION
|
||||
puts "Able to load bson_ext version #{CBson::VERSION}, but >= #{MINIMUM_BSON_EXT_VERSION} is required."
|
||||
raise LoadError
|
||||
end
|
||||
require 'bson/bson_c'
|
||||
module BSON
|
||||
BSON_CODER = BSON_C
|
||||
end
|
||||
rescue LoadError
|
||||
require 'bson/bson_ruby'
|
||||
module BSON
|
||||
BSON_CODER = BSON_RUBY
|
||||
end
|
||||
warn "\n**Notice: C extension not loaded. This is required for optimum MongoDB Ruby driver performance."
|
||||
warn " You can install the extension as follows:\n gem install bson_ext\n"
|
||||
warn " If you continue to receive this message after installing, make sure that the"
|
||||
warn " bson_ext gem is in your load path and that the bson_ext and mongo gems are of the same version.\n"
|
||||
end
|
||||
warn "\n**Notice: C extension not loaded. This is required for optimum MongoDB Ruby driver performance."
|
||||
warn " You can install the extension as follows:\n gem install bson_ext\n"
|
||||
warn " If you continue to receive this message after installing, make sure that the"
|
||||
warn " bson_ext gem is in your load path and that the bson_ext and mongo gems are of the same version.\n"
|
||||
end
|
||||
|
||||
require 'bson/types/binary'
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
include Java
|
||||
module BSON
|
||||
class BSON_JAVA
|
||||
|
||||
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)
|
||||
ByteBuffer.new(enc.encode(obj))
|
||||
end
|
||||
|
||||
def self.get_encoder
|
||||
@@enc ||= Java::OrgJbson::RubyBSONEncoder.new(JRuby.runtime)
|
||||
end
|
||||
|
||||
def self.get_decoder
|
||||
@@dec ||= Java::OrgBson::BSONDecoder.new
|
||||
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.decode(buf.to_s.to_java_bytes, callback)
|
||||
callback.get
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -44,6 +44,7 @@ module BSON
|
|||
|
||||
def initialize
|
||||
@buf = ByteBuffer.new
|
||||
@encoder = BSON_RUBY
|
||||
end
|
||||
|
||||
if RUBY_VERSION >= '1.9'
|
||||
|
@ -303,7 +304,7 @@ module BSON
|
|||
def deserialize_object_data(buf)
|
||||
size = buf.get_int
|
||||
buf.position -= 4
|
||||
object = BSON_CODER.new().deserialize(buf.get(size))
|
||||
object = @encoder.new().deserialize(buf.get(size))
|
||||
if object.has_key? "$ref"
|
||||
DBRef.new(object["$ref"], object["$id"])
|
||||
else
|
||||
|
@ -358,7 +359,7 @@ module BSON
|
|||
|
||||
scope_size = buf.get_int
|
||||
buf.position -= 4
|
||||
scope = BSON_CODER.new().deserialize(buf.get(scope_size))
|
||||
scope = @encoder.new().deserialize(buf.get(scope_size))
|
||||
|
||||
Code.new(encoded_str(code), scope)
|
||||
end
|
||||
|
@ -452,7 +453,7 @@ module BSON
|
|||
def serialize_object_element(buf, key, val, check_keys, opcode=OBJECT)
|
||||
buf.put(opcode)
|
||||
self.class.serialize_key(buf, key)
|
||||
buf.put_array(BSON_CODER.new.serialize(val, check_keys).to_a)
|
||||
buf.put_array(@encoder.new.serialize(val, check_keys).to_a)
|
||||
end
|
||||
|
||||
def serialize_array_element(buf, key, val, check_keys)
|
||||
|
@ -527,9 +528,9 @@ module BSON
|
|||
len_pos = buf.position
|
||||
buf.put_int(0)
|
||||
|
||||
buf.put_int(val.length + 1)
|
||||
self.class.serialize_cstr(buf, val)
|
||||
buf.put_array(BSON_CODER.new.serialize(val.scope).to_a)
|
||||
buf.put_int(val.code.length + 1)
|
||||
self.class.serialize_cstr(buf, val.code)
|
||||
buf.put_array(@encoder.new.serialize(val.scope).to_a)
|
||||
|
||||
end_pos = buf.position
|
||||
buf.put_int(end_pos - len_pos, len_pos)
|
||||
|
|
|
@ -19,10 +19,10 @@
|
|||
module BSON
|
||||
|
||||
# JavaScript code to be evaluated by MongoDB.
|
||||
class Code < String
|
||||
class Code
|
||||
|
||||
# Hash mapping identifiers to their values
|
||||
attr_accessor :scope
|
||||
attr_accessor :scope, :code
|
||||
|
||||
# Wrap code to be evaluated by MongoDB.
|
||||
#
|
||||
|
@ -30,9 +30,22 @@ module BSON
|
|||
# @param [Hash] a document mapping identifiers to values, which
|
||||
# represent the scope in which the code is to be executed.
|
||||
def initialize(code, scope={})
|
||||
super(code)
|
||||
@code = code
|
||||
@scope = scope
|
||||
end
|
||||
|
||||
def length
|
||||
@code.length
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
self.class == other.class &&
|
||||
@code == other.code && @scope == other.scope
|
||||
end
|
||||
|
||||
def inspect
|
||||
"<BSON::Code:#{object_id} @data=\"#{@code}\" @scope=\"#{@scope.inspect}\">"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -38,5 +38,9 @@ module BSON
|
|||
"ns: #{namespace}, id: #{object_id}"
|
||||
end
|
||||
|
||||
def to_hash
|
||||
{"$ns" => @namespace, "$id" => @object_id }
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -477,7 +477,7 @@ class TestCollection < Test::Unit::TestCase
|
|||
def test_saving_dates_pre_epoch
|
||||
begin
|
||||
@@test.save({'date' => Time.utc(1600)})
|
||||
assert_in_delta Time.utc(1600), @@test.find_one()["date"], 0.001
|
||||
assert_in_delta Time.utc(1600), @@test.find_one()["date"], 2
|
||||
rescue ArgumentError
|
||||
# See note in test_date_before_epoch (BSONTest)
|
||||
end
|
||||
|
|
|
@ -631,6 +631,7 @@ class DBAPITest < Test::Unit::TestCase
|
|||
assert_equal("mike", @@coll.find_one()["hello"])
|
||||
end
|
||||
|
||||
if !RUBY_PLATFORM =~ /java/
|
||||
def test_invalid_key_names
|
||||
@@coll.remove
|
||||
|
||||
|
@ -640,6 +641,7 @@ class DBAPITest < Test::Unit::TestCase
|
|||
assert_raise BSON::InvalidKeyName do
|
||||
@@coll.insert({"$hello" => "world"})
|
||||
end
|
||||
|
||||
assert_raise BSON::InvalidKeyName do
|
||||
@@coll.insert({"hello" => {"$hello" => "world"}})
|
||||
end
|
||||
|
@ -666,6 +668,7 @@ class DBAPITest < Test::Unit::TestCase
|
|||
@@coll.insert({"hello" => {"hel.lo" => "world"}})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_collection_names
|
||||
assert_raise TypeError do
|
||||
|
|
|
@ -322,7 +322,7 @@ class BSONTest < Test::Unit::TestCase
|
|||
assert_equal Binary::SUBTYPE_USER_DEFINED, bin2.subtype
|
||||
end
|
||||
|
||||
def test_binary_binary_subtype_0
|
||||
def test_binary_subtype_0
|
||||
bin = Binary.new([1, 2, 3, 4, 5], Binary::SUBTYPE_SIMPLE)
|
||||
|
||||
doc = {'bin' => bin}
|
||||
|
|
|
@ -0,0 +1,521 @@
|
|||
# encoding:utf-8
|
||||
require './test/test_helper'
|
||||
require 'complex'
|
||||
require 'bigdecimal'
|
||||
require 'rational'
|
||||
|
||||
begin
|
||||
require 'active_support/core_ext'
|
||||
Time.zone = "Pacific Time (US & Canada)"
|
||||
Zone = Time.zone.now
|
||||
rescue LoadError
|
||||
warn 'Mocking time with zone'
|
||||
module ActiveSupport
|
||||
class TimeWithZone
|
||||
end
|
||||
end
|
||||
Zone = ActiveSupport::TimeWithZone.new
|
||||
end
|
||||
|
||||
class BSONTest < Test::Unit::TestCase
|
||||
|
||||
include BSON
|
||||
|
||||
def setup
|
||||
@encoder = BSON::BSON_JAVA
|
||||
@decoder = BSON::BSON_RUBY#BSON::BSON_JAVA
|
||||
end
|
||||
|
||||
def assert_doc_pass(doc, options={})
|
||||
bson = @encoder.serialize(doc)
|
||||
if options[:debug]
|
||||
puts "DEBUGGIN 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)
|
||||
end
|
||||
|
||||
def test_require_hash
|
||||
assert_raise_error InvalidDocument, "takes a Hash" do
|
||||
BSON.serialize('foo')
|
||||
end
|
||||
|
||||
assert_raise_error InvalidDocument, "takes a Hash" do
|
||||
BSON.serialize(Object.new)
|
||||
end
|
||||
|
||||
assert_raise_error InvalidDocument, "takes a Hash" do
|
||||
BSON.serialize(Set.new)
|
||||
end
|
||||
end
|
||||
|
||||
def test_string
|
||||
doc = {'doc' => 'hello, world'}
|
||||
assert_doc_pass(doc)
|
||||
end
|
||||
|
||||
def test_valid_utf8_string
|
||||
doc = {'doc' => 'aé'}
|
||||
assert_doc_pass(doc)
|
||||
end
|
||||
|
||||
def test_valid_utf8_key
|
||||
doc = {'aé' => 'hello'}
|
||||
assert_doc_pass(doc)
|
||||
end
|
||||
|
||||
def test_document_length
|
||||
doc = {'name' => 'a' * 5 * 1024 * 1024}
|
||||
assert_raise InvalidDocument do
|
||||
assert @encoder.serialize(doc)
|
||||
end
|
||||
end
|
||||
|
||||
# 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)
|
||||
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)
|
||||
end
|
||||
end
|
||||
else
|
||||
def test_non_utf8_string
|
||||
bson = BSON::BSON_CODER.serialize({'str' => 'aé'.encode('iso-8859-1')})
|
||||
result = BSON::BSON_CODER.deserialize(bson)['str']
|
||||
assert_equal 'aé', result
|
||||
assert_equal 'UTF-8', result.encoding.name
|
||||
end
|
||||
|
||||
def test_non_utf8_key
|
||||
bson = BSON::BSON_CODER.serialize({'aé'.encode('iso-8859-1') => 'hello'})
|
||||
assert_equal 'hello', BSON::BSON_CODER.deserialize(bson)['aé']
|
||||
end
|
||||
|
||||
# Based on a test from sqlite3-ruby
|
||||
def test_default_internal_is_honored
|
||||
before_enc = Encoding.default_internal
|
||||
|
||||
str = "壁に耳あり、障子に目あり"
|
||||
bson = BSON::BSON_CODER.serialize("x" => str)
|
||||
|
||||
Encoding.default_internal = 'EUC-JP'
|
||||
out = BSON::BSON_CODER.deserialize(bson)["x"]
|
||||
|
||||
assert_equal Encoding.default_internal, out.encoding
|
||||
assert_equal str.encode('EUC-JP'), out
|
||||
assert_equal str, out.encode(str.encoding)
|
||||
ensure
|
||||
Encoding.default_internal = before_enc
|
||||
end
|
||||
end
|
||||
|
||||
def test_code
|
||||
doc = {'$where' => Code.new('this.a.b < this.b')}
|
||||
assert_doc_pass(doc)
|
||||
end
|
||||
|
||||
def test_code_with_scope
|
||||
doc = {'$where' => Code.new('this.a.b < this.b', {'foo' => 1})}
|
||||
assert_doc_pass(doc)
|
||||
end
|
||||
|
||||
def test_double
|
||||
doc = {'doc' => 41.25}
|
||||
assert_doc_pass(doc)
|
||||
end
|
||||
|
||||
def test_int
|
||||
doc = {'doc' => 42}
|
||||
assert_doc_pass(doc)
|
||||
|
||||
doc = {"doc" => -5600}
|
||||
assert_doc_pass(doc)
|
||||
|
||||
doc = {"doc" => 2147483647}
|
||||
assert_doc_pass(doc)
|
||||
|
||||
doc = {"doc" => -2147483648}
|
||||
assert_doc_pass(doc)
|
||||
end
|
||||
|
||||
def test_ordered_hash
|
||||
doc = BSON::OrderedHash.new
|
||||
doc["b"] = 1
|
||||
doc["a"] = 2
|
||||
doc["c"] = 3
|
||||
doc["d"] = 4
|
||||
assert_doc_pass(doc)
|
||||
end
|
||||
|
||||
def test_object
|
||||
doc = {'doc' => {'age' => 42, 'name' => 'Spongebob', 'shoe_size' => 9.5}}
|
||||
assert_doc_pass(doc)
|
||||
bson = BSON::BSON_CODER.serialize(doc)
|
||||
end
|
||||
|
||||
def test_oid
|
||||
doc = {'doc' => ObjectId.new}
|
||||
assert_doc_pass(doc)
|
||||
end
|
||||
|
||||
def test_array
|
||||
doc = {'doc' => [1, 2, 'a', 'b']}
|
||||
assert_doc_pass(doc)
|
||||
end
|
||||
|
||||
def test_regex
|
||||
doc = {'doc' => /foobar/i}
|
||||
assert_doc_pass(doc)
|
||||
end
|
||||
|
||||
def test_boolean
|
||||
doc = {'doc' => true}
|
||||
assert_doc_pass(doc)
|
||||
end
|
||||
|
||||
def test_date
|
||||
doc = {'date' => Time.now}
|
||||
bson = @encoder.serialize(doc)
|
||||
doc2 = @decoder.deserialize(bson)
|
||||
# Mongo only stores up to the millisecond
|
||||
assert_in_delta doc['date'], doc2['date'], 0.001
|
||||
end
|
||||
|
||||
def test_date_returns_as_utc
|
||||
doc = {'date' => Time.now}
|
||||
bson = @encoder.serialize(doc)
|
||||
doc2 = @decoder.deserialize(bson)
|
||||
assert doc2['date'].utc?
|
||||
end
|
||||
|
||||
def test_date_before_epoch
|
||||
begin
|
||||
doc = {'date' => Time.utc(1600)}
|
||||
bson = @encoder.serialize(doc)
|
||||
doc2 = @decoder.deserialize(bson)
|
||||
# Mongo only stores up to the millisecond
|
||||
assert_in_delta doc['date'], doc2['date'], 2
|
||||
rescue ArgumentError
|
||||
# some versions of Ruby won't let you create pre-epoch Time instances
|
||||
#
|
||||
# TODO figure out how that will work if somebady has saved data
|
||||
# w/ early dates already and is just querying for it.
|
||||
end
|
||||
end
|
||||
|
||||
def test_exeption_on_using_unsupported_date_class
|
||||
[DateTime.now, Date.today, Zone].each do |invalid_date|
|
||||
doc = {:date => invalid_date}
|
||||
begin
|
||||
bson = BSON::BSON_CODER.serialize(doc)
|
||||
rescue => e
|
||||
ensure
|
||||
if !invalid_date.is_a? Time
|
||||
assert_equal InvalidDocument, e.class
|
||||
assert_match /UTC Time/, e.message
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_dbref
|
||||
oid = ObjectId.new
|
||||
doc = {}
|
||||
doc['dbref'] = DBRef.new('namespace', oid)
|
||||
bson = @encoder.serialize(doc)
|
||||
doc2 = @decoder.deserialize(bson)
|
||||
if RUBY_PLATFORM =~ /java/
|
||||
assert_equal 'namespace', doc2['dbref']['$ns']
|
||||
assert_equal oid, doc2['dbref']['$id']
|
||||
else
|
||||
assert_equal 'namespace', doc2['dbref'].namespace
|
||||
assert_equal oid, doc2['dbref'].object_id
|
||||
end
|
||||
end
|
||||
|
||||
def test_symbol
|
||||
doc = {'sym' => :foo}
|
||||
bson = @encoder.serialize(doc)
|
||||
doc2 = @decoder.deserialize(bson)
|
||||
assert_equal :foo, doc2['sym']
|
||||
end
|
||||
|
||||
def test_binary
|
||||
bin = Binary.new
|
||||
'binstring'.each_byte { |b| bin.put(b) }
|
||||
|
||||
doc = {'bin' => bin}
|
||||
bson = @encoder.serialize(doc)
|
||||
doc2 = @decoder.deserialize(bson)
|
||||
bin2 = doc2['bin']
|
||||
assert_kind_of Binary, bin2
|
||||
assert_equal 'binstring', bin2.to_s
|
||||
assert_equal Binary::SUBTYPE_BYTES, bin2.subtype
|
||||
end
|
||||
|
||||
def test_binary_with_string
|
||||
b = Binary.new('somebinarystring')
|
||||
doc = {'bin' => b}
|
||||
bson = @encoder.serialize(doc)
|
||||
doc2 = @decoder.deserialize(bson)
|
||||
bin2 = doc2['bin']
|
||||
assert_kind_of Binary, bin2
|
||||
assert_equal 'somebinarystring', bin2.to_s
|
||||
assert_equal Binary::SUBTYPE_BYTES, bin2.subtype
|
||||
end
|
||||
|
||||
def test_binary_type
|
||||
bin = Binary.new([1, 2, 3, 4, 5], Binary::SUBTYPE_USER_DEFINED)
|
||||
|
||||
doc = {'bin' => bin}
|
||||
bson = @encoder.serialize(doc)
|
||||
doc2 = @decoder.deserialize(bson)
|
||||
bin2 = doc2['bin']
|
||||
assert_kind_of Binary, bin2
|
||||
assert_equal [1, 2, 3, 4, 5], bin2.to_a
|
||||
assert_equal Binary::SUBTYPE_USER_DEFINED, bin2.subtype
|
||||
end
|
||||
|
||||
# Java doesn't support this yet
|
||||
if !(RUBY_PLATFORM =~ /java/)
|
||||
def test_binary_subtype_0
|
||||
bin = Binary.new([1, 2, 3, 4, 5], Binary::SUBTYPE_SIMPLE)
|
||||
|
||||
doc = {'bin' => bin}
|
||||
bson = @encoder.serialize(doc)
|
||||
doc2 = @decoder.deserialize(bson)
|
||||
bin2 = doc2['bin']
|
||||
assert_kind_of Binary, bin2
|
||||
assert_equal [1, 2, 3, 4, 5], bin2.to_a
|
||||
assert_equal Binary::SUBTYPE_SIMPLE, bin2.subtype
|
||||
end
|
||||
end
|
||||
|
||||
def test_binary_byte_buffer
|
||||
bb = Binary.new
|
||||
5.times { |i| bb.put(i + 1) }
|
||||
|
||||
doc = {'bin' => bb}
|
||||
bson = @encoder.serialize(doc)
|
||||
doc2 = @decoder.deserialize(bson)
|
||||
bin2 = doc2['bin']
|
||||
assert_kind_of Binary, bin2
|
||||
assert_equal [1, 2, 3, 4, 5], bin2.to_a
|
||||
assert_equal Binary::SUBTYPE_BYTES, bin2.subtype
|
||||
end
|
||||
|
||||
def test_put_id_first
|
||||
val = BSON::OrderedHash.new
|
||||
val['not_id'] = 1
|
||||
val['_id'] = 2
|
||||
roundtrip = @decoder.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)
|
||||
assert_kind_of BSON::OrderedHash, roundtrip
|
||||
assert_equal '_id', roundtrip.keys.first
|
||||
end
|
||||
|
||||
def test_nil_id
|
||||
doc = {"_id" => nil}
|
||||
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])
|
||||
end
|
||||
|
||||
def test_overflow
|
||||
doc = {"x" => 2**75}
|
||||
assert_raise RangeError do
|
||||
bson = @encoder.serialize(doc)
|
||||
end
|
||||
|
||||
doc = {"x" => 9223372036854775}
|
||||
assert_doc_pass(doc)
|
||||
|
||||
doc = {"x" => 9223372036854775807}
|
||||
assert_doc_pass(doc)
|
||||
|
||||
doc["x"] = doc["x"] + 1
|
||||
assert_raise RangeError do
|
||||
bson = @encoder.serialize(doc)
|
||||
end
|
||||
|
||||
doc = {"x" => -9223372036854775}
|
||||
assert_doc_pass(doc)
|
||||
|
||||
doc = {"x" => -9223372036854775808}
|
||||
assert_doc_pass(doc)
|
||||
|
||||
doc["x"] = doc["x"] - 1
|
||||
assert_raise RangeError do
|
||||
bson = BSON::BSON_CODER.serialize(doc)
|
||||
end
|
||||
end
|
||||
|
||||
def test_invalid_numeric_types
|
||||
[BigDecimal.new("1.0"), Complex(0, 1), Rational(2, 3)].each do |type|
|
||||
doc = {"x" => type}
|
||||
begin
|
||||
@encoder.serialize(doc)
|
||||
rescue => e
|
||||
ensure
|
||||
assert_equal InvalidDocument, e.class
|
||||
assert_match /Cannot serialize/, e.message
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_do_not_change_original_object
|
||||
val = BSON::OrderedHash.new
|
||||
val['not_id'] = 1
|
||||
val['_id'] = 2
|
||||
assert val.keys.include?('_id')
|
||||
@encoder.serialize(val)
|
||||
assert val.keys.include?('_id')
|
||||
|
||||
val = {'a' => 'foo', 'b' => 'bar', :_id => 42, 'z' => 'hello'}
|
||||
assert val.keys.include?(:_id)
|
||||
@encoder.serialize(val)
|
||||
assert val.keys.include?(:_id)
|
||||
end
|
||||
|
||||
# note we only test for _id here because in the general case we will
|
||||
# write duplicates for :key and "key". _id is a special case because
|
||||
# we call has_key? to check for it's existence rather than just iterating
|
||||
# over it like we do for the rest of the keys. thus, things like
|
||||
# HashWithIndifferentAccess can cause problems for _id but not for other
|
||||
# keys. rather than require rails to test with HWIA directly, we do this
|
||||
# somewhat hacky test.
|
||||
def test_no_duplicate_id
|
||||
dup = {"_id" => "foo", :_id => "foo"}
|
||||
one = {"_id" => "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"}
|
||||
|
||||
assert_equal @encoder.serialize(one, false, true).to_s, @encoder.serialize(dup, false, true).to_s
|
||||
end
|
||||
|
||||
def test_null_character
|
||||
doc = {"a" => "\x00"}
|
||||
|
||||
assert_doc_pass(doc)
|
||||
|
||||
assert_raise InvalidDocument do
|
||||
@encoder.serialize({"\x00" => "a"})
|
||||
end
|
||||
|
||||
assert_raise InvalidDocument do
|
||||
@encoder.serialize({"a" => (Regexp.compile "ab\x00c")})
|
||||
end
|
||||
end
|
||||
|
||||
def test_max_key
|
||||
doc = {"a" => MaxKey.new}
|
||||
assert_doc_pass(doc)
|
||||
end
|
||||
|
||||
def test_min_key
|
||||
doc = {"a" => MinKey.new}
|
||||
assert_doc_pass(doc)
|
||||
end
|
||||
|
||||
def test_invalid_object
|
||||
o = Object.new
|
||||
assert_raise InvalidDocument do
|
||||
@encoder.serialize({:foo => o})
|
||||
end
|
||||
|
||||
assert_raise InvalidDocument do
|
||||
@encoder.serialize({:foo => Date.today})
|
||||
end
|
||||
end
|
||||
|
||||
def test_move_id
|
||||
a = BSON::OrderedHash.new
|
||||
a['text'] = 'abc'
|
||||
a['key'] = 'abc'
|
||||
a['_id'] = 1
|
||||
|
||||
|
||||
assert_equal ")\000\000\000\020_id\000\001\000\000\000\002text" +
|
||||
"\000\004\000\000\000abc\000\002key\000\004\000\000\000abc\000\000",
|
||||
@encoder.serialize(a, false, true).to_s
|
||||
|
||||
# Java doesn't support this. Isn't actually necessary.
|
||||
if !(RUBY_PLATFORM =~ /java/)
|
||||
assert_equal ")\000\000\000\002text\000\004\000\000\000abc\000\002key" +
|
||||
"\000\004\000\000\000abc\000\020_id\000\001\000\000\000\000",
|
||||
@encoder.serialize(a, false, false).to_s
|
||||
end
|
||||
end
|
||||
|
||||
def test_move_id_with_nested_doc
|
||||
b = BSON::OrderedHash.new
|
||||
b['text'] = 'abc'
|
||||
b['_id'] = 2
|
||||
c = BSON::OrderedHash.new
|
||||
c['text'] = 'abc'
|
||||
c['hash'] = b
|
||||
c['_id'] = 3
|
||||
assert_equal ">\000\000\000\020_id\000\003\000\000\000\002text" +
|
||||
"\000\004\000\000\000abc\000\003hash\000\034\000\000" +
|
||||
"\000\002text\000\004\000\000\000abc\000\020_id\000\002\000\000\000\000\000",
|
||||
@encoder.serialize(c, false, true).to_s
|
||||
|
||||
# Java doesn't support this. Isn't actually necessary.
|
||||
if !(RUBY_PLATFORM =~ /java/)
|
||||
assert_equal ">\000\000\000\002text\000\004\000\000\000abc\000\003hash" +
|
||||
"\000\034\000\000\000\002text\000\004\000\000\000abc\000\020_id" +
|
||||
"\000\002\000\000\000\000\020_id\000\003\000\000\000\000",
|
||||
@encoder.serialize(c, false, false).to_s
|
||||
end
|
||||
end
|
||||
|
||||
# Mocking this class for testing
|
||||
class ::HashWithIndifferentAccess < Hash; end
|
||||
|
||||
def test_keep_id_with_hash_with_indifferent_access
|
||||
doc = HashWithIndifferentAccess.new
|
||||
embedded = HashWithIndifferentAccess.new
|
||||
embedded['_id'] = ObjectId.new
|
||||
doc['_id'] = ObjectId.new
|
||||
doc['embedded'] = [embedded]
|
||||
@encoder.serialize(doc, false, true).to_a
|
||||
assert doc.has_key?("_id")
|
||||
assert doc['embedded'][0].has_key?("_id")
|
||||
|
||||
doc['_id'] = ObjectId.new
|
||||
@encoder.serialize(doc, false, true).to_a
|
||||
assert doc.has_key?("_id")
|
||||
end
|
||||
|
||||
end
|
Loading…
Reference in New Issue