static VALUE method_serialize(VALUE self, VALUE doc, VALUE check_keys, VALUE move_id, VALUE max_size) { VALUE result; bson_buffer_t buffer = bson_buffer_new(); if (buffer == NULL) { rb_raise(rb_eNoMemError, "failed to allocate memory in buffer.c"); } bson_buffer_set_max_size(buffer, FIX2INT(max_size)); write_doc(buffer, doc, check_keys, move_id); result = rb_str_new(bson_buffer_get_buffer(buffer), bson_buffer_get_position(buffer)); if (bson_buffer_free(buffer) != 0) { rb_raise(rb_eRuntimeError, "failed to free buffer"); } return result; }
static int write_element(VALUE key, VALUE value, VALUE extra, int allow_id) { bson_buffer_t buffer = (bson_buffer_t)NUM2LL(rb_ary_entry(extra, 0)); VALUE check_keys = rb_ary_entry(extra, 1); if (TYPE(key) == T_SYMBOL) { // TODO better way to do this... ? key = rb_str_new2(rb_id2name(SYM2ID(key))); } if (TYPE(key) != T_STRING) { bson_buffer_free(buffer); rb_raise(rb_eTypeError, "keys must be strings or symbols"); } if (allow_id == 0 && strcmp("_id", RSTRING_PTR(key)) == 0) { return ST_CONTINUE; } if (check_keys == Qtrue) { int i; if (RSTRING_LEN(key) > 0 && RSTRING_PTR(key)[0] == '$') { bson_buffer_free(buffer); rb_raise(InvalidKeyName, "key %s must not start with '$'", RSTRING_PTR(key)); } for (i = 0; i < RSTRING_LEN(key); i++) { if (RSTRING_PTR(key)[i] == '.') { bson_buffer_free(buffer); rb_raise(InvalidKeyName, "key %s must not contain '.'", RSTRING_PTR(key)); } } } switch(TYPE(value)) { case T_BIGNUM: { if (rb_funcall(value, gt_operator, 1, LL2NUM(9223372036854775807LL)) == Qtrue || rb_funcall(value, lt_operator, 1, LL2NUM(-9223372036854775808ULL)) == Qtrue) { bson_buffer_free(buffer); rb_raise(rb_eRangeError, "MongoDB can only handle 8-byte ints"); } } // NOTE: falls through to T_FIXNUM code case T_FIXNUM: { long long ll_value; ll_value = NUM2LL(value); if (ll_value > 2147483647LL || ll_value < -2147483648LL) { write_name_and_type(buffer, key, 0x12); SAFE_WRITE(buffer, (char*)&ll_value, 8); } else { int int_value; write_name_and_type(buffer, key, 0x10); int_value = (int)ll_value; SAFE_WRITE(buffer, (char*)&int_value, 4); } break; } case T_TRUE: { write_name_and_type(buffer, key, 0x08); SAFE_WRITE(buffer, &one, 1); break; } case T_FALSE: { write_name_and_type(buffer, key, 0x08); SAFE_WRITE(buffer, &zero, 1); break; } case T_FLOAT: { double d = NUM2DBL(value); write_name_and_type(buffer, key, 0x01); SAFE_WRITE(buffer, (char*)&d, 8); break; } case T_NIL: { write_name_and_type(buffer, key, 0x0A); break; } case T_HASH: { write_name_and_type(buffer, key, 0x03); write_doc(buffer, value, check_keys, Qfalse); break; } case T_ARRAY: { bson_buffer_position length_location, start_position, obj_length; int items, i; write_name_and_type(buffer, key, 0x04); start_position = bson_buffer_get_position(buffer); // save space for length length_location = bson_buffer_save_space(buffer, 4); if (length_location == -1) { rb_raise(rb_eNoMemError, "failed to allocate memory in buffer.c"); } items = RARRAY_LENINT(value); for(i = 0; i < items; i++) { char* name; VALUE key; INT2STRING(&name, i); key = rb_str_new2(name); write_element_with_id(key, rb_ary_entry(value, i), pack_extra(buffer, check_keys)); FREE_INTSTRING(name); } // write null byte and fill in length SAFE_WRITE(buffer, &zero, 1); obj_length = bson_buffer_get_position(buffer) - start_position; SAFE_WRITE_AT_POS(buffer, length_location, (const char*)&obj_length, 4); break; } case T_STRING: { int length; write_name_and_type(buffer, key, 0x02); length = RSTRING_LENINT(value) + 1; SAFE_WRITE(buffer, (char*)&length, 4); write_utf8(buffer, value, 0); SAFE_WRITE(buffer, &zero, 1); break; } case T_SYMBOL: { const char* str_value = rb_id2name(SYM2ID(value)); int length = (int)strlen(str_value) + 1; write_name_and_type(buffer, key, 0x0E); SAFE_WRITE(buffer, (char*)&length, 4); SAFE_WRITE(buffer, str_value, length); break; } case T_OBJECT: { // TODO there has to be a better way to do these checks... const char* cls = rb_obj_classname(value); if (strcmp(cls, "BSON::Binary") == 0 || strcmp(cls, "ByteBuffer") == 0) { const char subtype = strcmp(cls, "ByteBuffer") ? (const char)FIX2INT(rb_funcall(value, rb_intern("subtype"), 0)) : 2; VALUE string_data = rb_funcall(value, rb_intern("to_s"), 0); int length = RSTRING_LENINT(string_data); write_name_and_type(buffer, key, 0x05); if (subtype == 2) { const int other_length = length + 4; SAFE_WRITE(buffer, (const char*)&other_length, 4); SAFE_WRITE(buffer, &subtype, 1); } SAFE_WRITE(buffer, (const char*)&length, 4); if (subtype != 2) { SAFE_WRITE(buffer, &subtype, 1); } SAFE_WRITE(buffer, RSTRING_PTR(string_data), length); break; } if (strcmp(cls, "BSON::ObjectId") == 0) { VALUE as_array = rb_funcall(value, rb_intern("to_a"), 0); int i; write_name_and_type(buffer, key, 0x07); for (i = 0; i < 12; i++) { char byte = (char)FIX2INT(rb_ary_entry(as_array, i)); SAFE_WRITE(buffer, &byte, 1); } break; } if (strcmp(cls, "BSON::DBRef") == 0) { bson_buffer_position length_location, start_position, obj_length; VALUE ns, oid; write_name_and_type(buffer, key, 0x03); start_position = bson_buffer_get_position(buffer); // save space for length length_location = bson_buffer_save_space(buffer, 4); if (length_location == -1) { rb_raise(rb_eNoMemError, "failed to allocate memory in buffer.c"); } ns = rb_funcall(value, rb_intern("namespace"), 0); write_element_with_id(rb_str_new2("$ref"), ns, pack_extra(buffer, Qfalse)); oid = rb_funcall(value, rb_intern("object_id"), 0); write_element_with_id(rb_str_new2("$id"), oid, pack_extra(buffer, Qfalse)); // write null byte and fill in length SAFE_WRITE(buffer, &zero, 1); obj_length = bson_buffer_get_position(buffer) - start_position; SAFE_WRITE_AT_POS(buffer, length_location, (const char*)&obj_length, 4); break; } if (strcmp(cls, "BSON::Code") == 0) { bson_buffer_position length_location, start_position, total_length; int length; VALUE code_str; write_name_and_type(buffer, key, 0x0F); start_position = bson_buffer_get_position(buffer); length_location = bson_buffer_save_space(buffer, 4); if (length_location == -1) { rb_raise(rb_eNoMemError, "failed to allocate memory in buffer.c"); } code_str = rb_funcall(value, rb_intern("code"), 0); length = RSTRING_LENINT(code_str) + 1; SAFE_WRITE(buffer, (char*)&length, 4); SAFE_WRITE(buffer, RSTRING_PTR(code_str), length - 1); SAFE_WRITE(buffer, &zero, 1); write_doc(buffer, rb_funcall(value, rb_intern("scope"), 0), Qfalse, Qfalse); total_length = bson_buffer_get_position(buffer) - start_position; SAFE_WRITE_AT_POS(buffer, length_location, (const char*)&total_length, 4); break; } if (strcmp(cls, "BSON::MaxKey") == 0) { write_name_and_type(buffer, key, 0x7f); break; } if (strcmp(cls, "BSON::MinKey") == 0) { write_name_and_type(buffer, key, 0xff); break; } if (strcmp(cls, "BSON::Timestamp") == 0) { unsigned int seconds; unsigned int increment; write_name_and_type(buffer, key, 0x11); seconds = NUM2UINT( rb_funcall(value, rb_intern("seconds"), 0)); increment = NUM2UINT( 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) { bson_buffer_free(buffer); rb_raise(InvalidDocument, "%s is not currently supported; use a UTC Time instance instead.", cls); break; } if(strcmp(cls, "Complex") == 0 || strcmp(cls, "Rational") == 0 || strcmp(cls, "BigDecimal") == 0) { bson_buffer_free(buffer); rb_raise(InvalidDocument, "Cannot serialize the Numeric type %s as BSON; only Bignum, Fixnum, and Float are supported.", cls); break; } if (strcmp(cls, "ActiveSupport::Multibyte::Chars") == 0) { int length; VALUE str = StringValue(value); write_name_and_type(buffer, key, 0x02); length = RSTRING_LENINT(str) + 1; SAFE_WRITE(buffer, (char*)&length, 4); write_utf8(buffer, str, 0); SAFE_WRITE(buffer, &zero, 1); break; } bson_buffer_free(buffer); rb_raise(InvalidDocument, "Cannot serialize an object of class %s into BSON.", cls); break; } case T_DATA: { const char* cls = rb_obj_classname(value); if (strcmp(cls, "Time") == 0) { double t = NUM2DBL(rb_funcall(value, rb_intern("to_f"), 0)); long long time_since_epoch = (long long)round(t * 1000); write_name_and_type(buffer, key, 0x09); SAFE_WRITE(buffer, (const char*)&time_since_epoch, 8); break; } // Date classes are TYPE T_DATA in Ruby >= 1.9.3 if (strcmp(cls, "DateTime") == 0 || strcmp(cls, "Date") == 0 || strcmp(cls, "ActiveSupport::TimeWithZone") == 0) { bson_buffer_free(buffer); rb_raise(InvalidDocument, "%s is not currently supported; use a UTC Time instance instead.", cls); break; } if(strcmp(cls, "BigDecimal") == 0) { bson_buffer_free(buffer); rb_raise(InvalidDocument, "Cannot serialize the Numeric type %s as BSON; only Bignum, Fixnum, and Float are supported.", cls); break; } bson_buffer_free(buffer); rb_raise(InvalidDocument, "Cannot serialize an object of class %s into BSON.", cls); break; } case T_REGEXP: { VALUE pattern = RREGEXP_SRC(value); long flags = RREGEXP_OPTIONS(value); VALUE has_extra; write_name_and_type(buffer, key, 0x0B); write_utf8(buffer, pattern, 1); SAFE_WRITE(buffer, &zero, 1); if (flags & IGNORECASE) { char ignorecase = 'i'; SAFE_WRITE(buffer, &ignorecase, 1); } if (flags & MULTILINE) { char multiline = 'm'; char dotall = 's'; SAFE_WRITE(buffer, &multiline, 1); SAFE_WRITE(buffer, &dotall, 1); } if (flags & EXTENDED) { char extended = 'x'; SAFE_WRITE(buffer, &extended, 1); } has_extra = rb_funcall(value, rb_intern("respond_to?"), 1, rb_str_new2("extra_options_str")); if (TYPE(has_extra) == T_TRUE) { VALUE extra = rb_funcall(value, rb_intern("extra_options_str"), 0); bson_buffer_position old_position = bson_buffer_get_position(buffer); SAFE_WRITE(buffer, RSTRING_PTR(extra), RSTRING_LENINT(extra)); qsort(bson_buffer_get_buffer(buffer) + old_position, RSTRING_LEN(extra), sizeof(char), cmp_char); } SAFE_WRITE(buffer, &zero, 1); break; } default: { const char* cls = rb_obj_classname(value); bson_buffer_free(buffer); rb_raise(InvalidDocument, "Cannot serialize an object of class %s (type %d) into BSON.", cls, TYPE(value)); break; } } return ST_CONTINUE; }
static void serialize_regex(bson_buffer_t buffer, VALUE key, VALUE pattern, long flags, VALUE value, int native) { VALUE has_extra; write_name_and_type(buffer, key, 0x0B); write_utf8(buffer, pattern, 0); SAFE_WRITE(buffer, &zero, 1); if (native == 1) { // Ruby regular expressions always use multiline mode char multiline = 'm'; SAFE_WRITE(buffer, &multiline, 1); if (flags & IGNORECASE) { char ignorecase = 'i'; SAFE_WRITE(buffer, &ignorecase, 1); } // dotall on the server is multiline in Ruby if (flags & MULTILINE) { char dotall = 's'; SAFE_WRITE(buffer, &dotall, 1); } if (flags & EXTENDED) { char extended = 'x'; SAFE_WRITE(buffer, &extended, 1); } } else { if (flags & BSONRegex_IGNORECASE) { char ignorecase = 'i'; SAFE_WRITE(buffer, &ignorecase, 1); } if (flags & BSONRegex_LOCALE_DEPENDENT) { char locale_dependent = 'l'; SAFE_WRITE(buffer, &locale_dependent, 1); } if (flags & BSONRegex_MULTILINE) { char multiline = 'm'; SAFE_WRITE(buffer, &multiline, 1); } if (flags & BSONRegex_DOTALL) { char dotall = 's'; SAFE_WRITE(buffer, &dotall, 1); } if (flags & BSONRegex_UNICODE) { char unicode = 'u'; SAFE_WRITE(buffer, &unicode, 1); } if (flags & BSONRegex_EXTENDED) { char extended = 'x'; SAFE_WRITE(buffer, &extended, 1); } } has_extra = rb_funcall(value, rb_intern("respond_to?"), 1, rb_str_new2("extra_options_str")); if (TYPE(has_extra) == T_TRUE) { VALUE extra = rb_funcall(value, rb_intern("extra_options_str"), 0); bson_buffer_position old_position = bson_buffer_get_position(buffer); SAFE_WRITE(buffer, RSTRING_PTR(extra), RSTRING_LENINT(extra)); qsort(bson_buffer_get_buffer(buffer) + old_position, RSTRING_LEN(extra), sizeof(char), cmp_char); } SAFE_WRITE(buffer, &zero, 1); }