mongo_ext 0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Rakefile ADDED
@@ -0,0 +1,62 @@
1
+ require 'rubygems'
2
+ require 'rubygems/specification'
3
+ require 'fileutils'
4
+ require 'rake'
5
+ require 'rake/testtask'
6
+ require 'rake/gempackagetask'
7
+ begin
8
+ require 'rake/contrib/rubyforgepublisher'
9
+ rescue LoadError
10
+ end
11
+ require 'rbconfig'
12
+ include Config
13
+
14
+ gem_command = "gem"
15
+ gem_command = "gem1.9" if $0.match(/1\.9$/) # use gem1.9 if we used rake1.9
16
+
17
+ # NOTE: some of the tests assume Mongo is running
18
+ Rake::TestTask.new do |t|
19
+ t.test_files = FileList['test/test*.rb']
20
+ end
21
+
22
+ desc "Generate documentation"
23
+ task :rdoc do
24
+ version = eval(File.read("mongo-ruby-driver.gemspec")).version
25
+ out = File.join('html', version.to_s)
26
+ FileUtils.rm_rf('html')
27
+ system "rdoc --main README.rdoc --op #{out} --inline-source --quiet README.rdoc `find lib -name '*.rb'`"
28
+ end
29
+
30
+ desc "Publish documentation to mongo.rubyforge.org"
31
+ task :publish => [:rdoc] do
32
+ # Assumes docs are in ./html
33
+ Rake::RubyForgePublisher.new(GEM, RUBYFORGE_USER).upload
34
+ end
35
+
36
+ namespace :gem do
37
+
38
+ desc "Install the gem locally"
39
+ task :install do
40
+ sh <<EOS
41
+ #{gem_command} build mongo-ruby-driver.gemspec &&
42
+ sudo #{gem_command} install mongo-*.gem &&
43
+ rm mongo-*.gem
44
+ EOS
45
+ end
46
+
47
+ desc "Install the optional c extensions"
48
+ task :install_extensions do
49
+ sh <<EOS
50
+ #{gem_command} build mongo-extensions.gemspec &&
51
+ sudo #{gem_command} install mongo_ext-*.gem &&
52
+ rm mongo_ext-*.gem
53
+ EOS
54
+ end
55
+
56
+ end
57
+
58
+ task :default => :list
59
+
60
+ task :list do
61
+ system 'rake -T'
62
+ end
data/ext/cbson/cbson.c ADDED
@@ -0,0 +1,764 @@
1
+ /*
2
+ * Copyright 2009 10gen, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ /*
18
+ * This file contains C implementations of some of the functions needed by the
19
+ * bson module. If possible, these implementations should be used to speed up
20
+ * BSON encoding and decoding.
21
+ */
22
+
23
+ #include "ruby.h"
24
+
25
+ #if HAVE_RUBY_ST_H
26
+ #include "ruby/st.h"
27
+ #endif
28
+ #if HAVE_ST_H
29
+ #include "st.h"
30
+ #endif
31
+
32
+ #if HAVE_RUBY_REGEX_H
33
+ #include "ruby/regex.h"
34
+ #endif
35
+ #if HAVE_REGEX_H
36
+ #include "regex.h"
37
+ #endif
38
+
39
+ #include <assert.h>
40
+ #include <math.h>
41
+
42
+ #define INITIAL_BUFFER_SIZE 256
43
+
44
+ static VALUE Binary;
45
+ static VALUE Time;
46
+ static VALUE ObjectID;
47
+ static VALUE DBRef;
48
+ static VALUE Code;
49
+ static VALUE RegexpOfHolding;
50
+ static VALUE OrderedHash;
51
+ static VALUE InvalidName;
52
+
53
+ #if HAVE_RUBY_ENCODING_H
54
+ #include "ruby/encoding.h"
55
+ #define STR_NEW(p,n) rb_enc_str_new((p), (n), rb_utf8_encoding())
56
+ #else
57
+ #define STR_NEW(p,n) rb_str_new((p), (n))
58
+ #endif
59
+
60
+ // this sucks. but for some reason these moved around between 1.8 and 1.9
61
+ #ifdef ONIGURUMA_H
62
+ #define IGNORECASE ONIG_OPTION_IGNORECASE
63
+ #define MULTILINE ONIG_OPTION_MULTILINE
64
+ #define EXTENDED ONIG_OPTION_EXTEND
65
+ #else
66
+ #define IGNORECASE RE_OPTION_IGNORECASE
67
+ #define MULTILINE RE_OPTION_MULTILINE
68
+ #define EXTENDED RE_OPTION_EXTENDED
69
+ #endif
70
+
71
+ /* TODO we ought to check that the malloc or asprintf was successful
72
+ * and raise an exception if not. */
73
+ #ifdef _MSC_VER
74
+ #define INT2STRING(buffer, i) \
75
+ { \
76
+ int vslength = _scprintf("%d", i) + 1; \
77
+ *buffer = malloc(vslength); \
78
+ _snprintf(*buffer, vslength, "%d", i); \
79
+ }
80
+ #else
81
+ #define INT2STRING(buffer, i) asprintf(buffer, "%d", i);
82
+ #endif
83
+
84
+ // this sucks too.
85
+ #ifndef RREGEXP_SRC_PTR
86
+ #define RREGEXP_SRC_PTR(r) RREGEXP(r)->str
87
+ #define RREGEXP_SRC_LEN(r) RREGEXP(r)->len
88
+ #endif
89
+
90
+ typedef struct {
91
+ char* buffer;
92
+ int size;
93
+ int position;
94
+ } bson_buffer;
95
+
96
+ static char zero = 0;
97
+ static char one = 1;
98
+
99
+ static int cmp_char(const void* a, const void* b) {
100
+ return *(char*)a - *(char*)b;
101
+ }
102
+
103
+ static void write_doc(bson_buffer* buffer, VALUE hash, VALUE check_keys);
104
+ static int write_element(VALUE key, VALUE value, VALUE extra);
105
+ static VALUE elements_to_hash(const char* buffer, int max);
106
+
107
+ static bson_buffer* buffer_new(void) {
108
+ bson_buffer* buffer;
109
+ buffer = ALLOC(bson_buffer);
110
+ assert(buffer);
111
+
112
+ buffer->size = INITIAL_BUFFER_SIZE;
113
+ buffer->position = 0;
114
+ buffer->buffer = ALLOC_N(char, INITIAL_BUFFER_SIZE);
115
+ assert(buffer->buffer);
116
+
117
+ return buffer;
118
+ }
119
+
120
+ static void buffer_free(bson_buffer* buffer) {
121
+ assert(buffer);
122
+ assert(buffer->buffer);
123
+
124
+ free(buffer->buffer);
125
+ free(buffer);
126
+ }
127
+
128
+ static void buffer_resize(bson_buffer* buffer, int min_length) {
129
+ int size = buffer->size;
130
+ if (size >= min_length) {
131
+ return;
132
+ }
133
+ while (size < min_length) {
134
+ size *= 2;
135
+ }
136
+ buffer->buffer = REALLOC_N(buffer->buffer, char, size);
137
+ assert(buffer->buffer);
138
+ buffer->size = size;
139
+ }
140
+
141
+ static void buffer_assure_space(bson_buffer* buffer, int size) {
142
+ if (buffer->position + size <= buffer->size) {
143
+ return;
144
+ }
145
+ buffer_resize(buffer, buffer->position + size);
146
+ }
147
+
148
+ /* returns offset for writing */
149
+ static int buffer_save_bytes(bson_buffer* buffer, int size) {
150
+ int position = buffer->position;
151
+ buffer_assure_space(buffer, size);
152
+ buffer->position += size;
153
+ return position;
154
+ }
155
+
156
+ static void buffer_write_bytes(bson_buffer* buffer, const char* bytes, int size) {
157
+ buffer_assure_space(buffer, size);
158
+
159
+ memcpy(buffer->buffer + buffer->position, bytes, size);
160
+ buffer->position += size;
161
+ }
162
+
163
+ static VALUE pack_extra(bson_buffer* buffer, VALUE check_keys) {
164
+ return rb_ary_new3(2, LL2NUM((long long)buffer), check_keys);
165
+ }
166
+
167
+ static void write_name_and_type(bson_buffer* buffer, VALUE name, char type) {
168
+ buffer_write_bytes(buffer, &type, 1);
169
+ buffer_write_bytes(buffer, RSTRING_PTR(name), RSTRING_LEN(name));
170
+ buffer_write_bytes(buffer, &zero, 1);
171
+ }
172
+
173
+ static int write_element_allow_id(VALUE key, VALUE value, VALUE extra, int allow_id) {
174
+ bson_buffer* buffer = (bson_buffer*)NUM2LL(rb_ary_entry(extra, 0));
175
+ VALUE check_keys = rb_ary_entry(extra, 1);
176
+
177
+ if (TYPE(key) == T_SYMBOL) {
178
+ // TODO better way to do this... ?
179
+ key = rb_str_new2(rb_id2name(SYM2ID(key)));
180
+ }
181
+
182
+ if (TYPE(key) != T_STRING) {
183
+ rb_raise(rb_eTypeError, "keys must be strings or symbols");
184
+ }
185
+
186
+ if (!allow_id && strcmp("_id", RSTRING_PTR(key)) == 0) {
187
+ return ST_CONTINUE;
188
+ }
189
+
190
+ if (check_keys == Qtrue) {
191
+ int i;
192
+ if (RSTRING_LEN(key) > 0 && RSTRING_PTR(key)[0] == '$') {
193
+ rb_raise(InvalidName, "key must not start with '$'");
194
+ }
195
+ for (i = 0; i < RSTRING_LEN(key); i++) {
196
+ if (RSTRING_PTR(key)[i] == '.') {
197
+ rb_raise(InvalidName, "key must not contain '.'");
198
+ }
199
+ }
200
+ }
201
+
202
+ switch(TYPE(value)) {
203
+ case T_BIGNUM:
204
+ case T_FIXNUM:
205
+ {
206
+ if (rb_funcall(value, rb_intern(">"), 1, LL2NUM(9223372036854775807LL)) == Qtrue ||
207
+ rb_funcall(value, rb_intern("<"), 1, LL2NUM(-9223372036854775808LL)) == Qtrue) {
208
+ rb_raise(rb_eRangeError, "MongoDB can only handle 8-byte ints");
209
+ }
210
+ if (rb_funcall(value, rb_intern(">"), 1, INT2NUM(2147483647L)) == Qtrue ||
211
+ rb_funcall(value, rb_intern("<"), 1, INT2NUM(-2147483648L)) == Qtrue) {
212
+ long long ll_value;
213
+ write_name_and_type(buffer, key, 0x12);
214
+ ll_value = NUM2LL(value);
215
+ buffer_write_bytes(buffer, (char*)&ll_value, 8);
216
+ } else {
217
+ int int_value;
218
+ write_name_and_type(buffer, key, 0x10);
219
+ int_value = NUM2LL(value);
220
+ buffer_write_bytes(buffer, (char*)&int_value, 4);
221
+ }
222
+ break;
223
+ }
224
+ case T_TRUE:
225
+ {
226
+ write_name_and_type(buffer, key, 0x08);
227
+ buffer_write_bytes(buffer, &one, 1);
228
+ break;
229
+ }
230
+ case T_FALSE:
231
+ {
232
+ write_name_and_type(buffer, key, 0x08);
233
+ buffer_write_bytes(buffer, &zero, 1);
234
+ break;
235
+ }
236
+ case T_FLOAT:
237
+ {
238
+ double d = NUM2DBL(value);
239
+ write_name_and_type(buffer, key, 0x01);
240
+ buffer_write_bytes(buffer, (char*)&d, 8);
241
+ break;
242
+ }
243
+ case T_NIL:
244
+ {
245
+ write_name_and_type(buffer, key, 0x0A);
246
+ break;
247
+ }
248
+ case T_HASH:
249
+ {
250
+ write_name_and_type(buffer, key, 0x03);
251
+ write_doc(buffer, value, check_keys);
252
+ break;
253
+ }
254
+ case T_ARRAY:
255
+ {
256
+ int start_position, length_location, items, i, obj_length;
257
+ VALUE* values;
258
+
259
+ write_name_and_type(buffer, key, 0x04);
260
+ start_position = buffer->position;
261
+
262
+ // save space for length
263
+ length_location = buffer_save_bytes(buffer, 4);
264
+
265
+ items = RARRAY_LEN(value);
266
+ values = RARRAY_PTR(value);
267
+ for(i = 0; i < items; i++) {
268
+ char* name;
269
+ VALUE key;
270
+ INT2STRING(&name, i);
271
+ key = rb_str_new2(name);
272
+ write_element(key, values[i], pack_extra(buffer, check_keys));
273
+ free(name);
274
+ }
275
+
276
+ // write null byte and fill in length
277
+ buffer_write_bytes(buffer, &zero, 1);
278
+ obj_length = buffer->position - start_position;
279
+ memcpy(buffer->buffer + length_location, &obj_length, 4);
280
+ break;
281
+ }
282
+ case T_STRING:
283
+ {
284
+ if (strcmp(rb_class2name(RBASIC(value)->klass),
285
+ "Mongo::Code") == 0) {
286
+ int start_position, length_location, length, total_length;
287
+ write_name_and_type(buffer, key, 0x0F);
288
+
289
+ start_position = buffer->position;
290
+ length_location = buffer_save_bytes(buffer, 4);
291
+
292
+ length = RSTRING_LEN(value) + 1;
293
+ buffer_write_bytes(buffer, (char*)&length, 4);
294
+ buffer_write_bytes(buffer, RSTRING_PTR(value), length - 1);
295
+ buffer_write_bytes(buffer, &zero, 1);
296
+ write_doc(buffer, rb_funcall(value, rb_intern("scope"), 0), Qfalse);
297
+
298
+ total_length = buffer->position - start_position;
299
+ memcpy(buffer->buffer + length_location, &total_length, 4);
300
+
301
+ break;
302
+ } else {
303
+ int length = RSTRING_LEN(value) + 1;
304
+ write_name_and_type(buffer, key, 0x02);
305
+ buffer_write_bytes(buffer, (char*)&length, 4);
306
+ buffer_write_bytes(buffer, RSTRING_PTR(value), length - 1);
307
+ buffer_write_bytes(buffer, &zero, 1);
308
+ break;
309
+ }
310
+ }
311
+ case T_SYMBOL:
312
+ {
313
+ const char* str_value = rb_id2name(SYM2ID(value));
314
+ int length = strlen(str_value) + 1;
315
+ write_name_and_type(buffer, key, 0x0E);
316
+ buffer_write_bytes(buffer, (char*)&length, 4);
317
+ buffer_write_bytes(buffer, str_value, length);
318
+ break;
319
+ }
320
+ case T_OBJECT:
321
+ {
322
+ // TODO there has to be a better way to do these checks...
323
+ const char* cls = rb_class2name(RBASIC(value)->klass);
324
+ if (strcmp(cls, "Mongo::Binary") == 0 ||
325
+ strcmp(cls, "ByteBuffer") == 0) {
326
+ const char subtype = strcmp(cls, "ByteBuffer") ?
327
+ (const char)FIX2INT(rb_funcall(value, rb_intern("subtype"), 0)) : 2;
328
+ VALUE string_data = rb_funcall(value, rb_intern("to_s"), 0);
329
+ int length = RSTRING_LEN(string_data);
330
+ write_name_and_type(buffer, key, 0x05);
331
+ if (subtype == 2) {
332
+ const int other_length = length + 4;
333
+ buffer_write_bytes(buffer, (const char*)&other_length, 4);
334
+ buffer_write_bytes(buffer, &subtype, 1);
335
+ }
336
+ buffer_write_bytes(buffer, (const char*)&length, 4);
337
+ if (subtype != 2) {
338
+ buffer_write_bytes(buffer, &subtype, 1);
339
+ }
340
+ buffer_write_bytes(buffer, RSTRING_PTR(string_data), length);
341
+ break;
342
+ }
343
+ if (strcmp(cls, "Mongo::ObjectID") == 0) {
344
+ VALUE as_array = rb_funcall(value, rb_intern("to_a"), 0);
345
+ int i;
346
+ write_name_and_type(buffer, key, 0x07);
347
+ for (i = 0; i < 12; i++) {
348
+ char byte = (char)FIX2INT(RARRAY_PTR(as_array)[i]);
349
+ buffer_write_bytes(buffer, &byte, 1);
350
+ }
351
+ break;
352
+ }
353
+ if (strcmp(cls, "Mongo::DBRef") == 0) {
354
+ int start_position, length_location, obj_length;
355
+ VALUE ns, oid;
356
+ write_name_and_type(buffer, key, 0x03);
357
+
358
+ start_position = buffer->position;
359
+
360
+ // save space for length
361
+ length_location = buffer_save_bytes(buffer, 4);
362
+
363
+ ns = rb_funcall(value, rb_intern("namespace"), 0);
364
+ write_element(rb_str_new2("$ref"), ns, pack_extra(buffer, Qfalse));
365
+ oid = rb_funcall(value, rb_intern("object_id"), 0);
366
+ write_element(rb_str_new2("$id"), oid, pack_extra(buffer, Qfalse));
367
+
368
+ // write null byte and fill in length
369
+ buffer_write_bytes(buffer, &zero, 1);
370
+ obj_length = buffer->position - start_position;
371
+ memcpy(buffer->buffer + length_location, &obj_length, 4);
372
+ break;
373
+ }
374
+ }
375
+ case T_DATA:
376
+ {
377
+ // TODO again, is this really the only way to do this?
378
+ const char* cls = rb_class2name(RBASIC(value)->klass);
379
+ if (strcmp(cls, "Time") == 0) {
380
+ double t = NUM2DBL(rb_funcall(value, rb_intern("to_f"), 0));
381
+ long long time_since_epoch = (long long)round(t * 1000);
382
+ write_name_and_type(buffer, key, 0x09);
383
+ buffer_write_bytes(buffer, (const char*)&time_since_epoch, 8);
384
+ break;
385
+ }
386
+ }
387
+ case T_REGEXP:
388
+ {
389
+ int length = RREGEXP_SRC_LEN(value);
390
+ char* pattern = (char*)RREGEXP_SRC_PTR(value);
391
+ long flags = RREGEXP(value)->ptr->options;
392
+ VALUE has_extra;
393
+
394
+ write_name_and_type(buffer, key, 0x0B);
395
+
396
+ buffer_write_bytes(buffer, pattern, length);
397
+ buffer_write_bytes(buffer, &zero, 1);
398
+
399
+ if (flags & IGNORECASE) {
400
+ char ignorecase = 'i';
401
+ buffer_write_bytes(buffer, &ignorecase, 1);
402
+ }
403
+ if (flags & MULTILINE) {
404
+ char multiline = 'm';
405
+ buffer_write_bytes(buffer, &multiline, 1);
406
+ }
407
+ if (flags & EXTENDED) {
408
+ char extended = 'x';
409
+ buffer_write_bytes(buffer, &extended, 1);
410
+ }
411
+
412
+ has_extra = rb_funcall(value, rb_intern("respond_to?"), 1, rb_str_new2("extra_options_str"));
413
+ if (TYPE(has_extra) == T_TRUE) {
414
+ VALUE extra = rb_funcall(value, rb_intern("extra_options_str"), 0);
415
+ int old_position = buffer->position;
416
+ buffer_write_bytes(buffer, RSTRING_PTR(extra), RSTRING_LEN(extra));
417
+ qsort(buffer->buffer + old_position, RSTRING_LEN(extra), sizeof(char), cmp_char);
418
+ }
419
+ buffer_write_bytes(buffer, &zero, 1);
420
+
421
+ break;
422
+ }
423
+ default:
424
+ {
425
+ rb_raise(rb_eTypeError, "no c encoder for this type yet (%d)", TYPE(value));
426
+ break;
427
+ }
428
+ }
429
+ return ST_CONTINUE;
430
+ }
431
+
432
+ static int write_element(VALUE key, VALUE value, VALUE extra) {
433
+ return write_element_allow_id(key, value, extra, 0);
434
+ }
435
+
436
+ static void write_doc(bson_buffer* buffer, VALUE hash, VALUE check_keys) {
437
+ int start_position = buffer->position;
438
+ int length_location = buffer_save_bytes(buffer, 4);
439
+ int length;
440
+
441
+ VALUE key = rb_str_new2("_id");
442
+ if (rb_funcall(hash, rb_intern("has_key?"), 1, key) == Qtrue) {
443
+ VALUE id = rb_hash_aref(hash, key);
444
+ write_element_allow_id(key, id, pack_extra(buffer, check_keys), 1);
445
+ }
446
+ key = ID2SYM(rb_intern("_id"));
447
+ if (rb_funcall(hash, rb_intern("has_key?"), 1, key) == Qtrue) {
448
+ VALUE id = rb_hash_aref(hash, key);
449
+ write_element_allow_id(key, id, pack_extra(buffer, check_keys), 1);
450
+ }
451
+
452
+ // we have to check for an OrderedHash and handle that specially
453
+ if (strcmp(rb_class2name(RBASIC(hash)->klass), "OrderedHash") == 0) {
454
+ VALUE keys = rb_funcall(hash, rb_intern("keys"), 0);
455
+ int i;
456
+ for(i = 0; i < RARRAY_LEN(keys); i++) {
457
+ VALUE key = RARRAY_PTR(keys)[i];
458
+ VALUE value = rb_hash_aref(hash, key);
459
+
460
+ write_element(key, value, pack_extra(buffer, check_keys));
461
+ }
462
+ } else {
463
+ rb_hash_foreach(hash, write_element, pack_extra(buffer, check_keys));
464
+ }
465
+
466
+ // write null byte and fill in length
467
+ buffer_write_bytes(buffer, &zero, 1);
468
+ length = buffer->position - start_position;
469
+ memcpy(buffer->buffer + length_location, &length, 4);
470
+ }
471
+
472
+ static VALUE method_serialize(VALUE self, VALUE doc, VALUE check_keys) {
473
+ VALUE result;
474
+ bson_buffer* buffer = buffer_new();
475
+ assert(buffer);
476
+
477
+ write_doc(buffer, doc, check_keys);
478
+
479
+ result = rb_str_new(buffer->buffer, buffer->position);
480
+ buffer_free(buffer);
481
+ return result;
482
+ }
483
+
484
+ static VALUE get_value(const char* buffer, int* position, int type) {
485
+ VALUE value;
486
+ switch (type) {
487
+ case 1:
488
+ {
489
+ double d;
490
+ memcpy(&d, buffer + *position, 8);
491
+ value = rb_float_new(d);
492
+ *position += 8;
493
+ break;
494
+ }
495
+ case 2:
496
+ case 13:
497
+ {
498
+ int value_length;
499
+ *position += 4;
500
+ value_length = strlen(buffer + *position);
501
+ value = STR_NEW(buffer + *position, value_length);
502
+ *position += value_length + 1;
503
+ break;
504
+ }
505
+ case 3:
506
+ {
507
+ int size;
508
+ memcpy(&size, buffer + *position, 4);
509
+ if (strcmp(buffer + *position + 5, "$ref") == 0) { // DBRef
510
+ int offset = *position + 14;
511
+ VALUE argv[2];
512
+ int collection_length = strlen(buffer + offset);
513
+ char id_type;
514
+
515
+ argv[0] = STR_NEW(buffer + offset, collection_length);
516
+ offset += collection_length + 1;
517
+ id_type = buffer[offset];
518
+ offset += 5;
519
+ argv[1] = get_value(buffer, &offset, (int)id_type);
520
+ value = rb_class_new_instance(2, argv, DBRef);
521
+ } else {
522
+ value = elements_to_hash(buffer + *position + 4, size - 5);
523
+ }
524
+ *position += size;
525
+ break;
526
+ }
527
+ case 4:
528
+ {
529
+ int size, end;
530
+ memcpy(&size, buffer + *position, 4);
531
+ end = *position + size - 1;
532
+ *position += 4;
533
+
534
+ value = rb_ary_new();
535
+ while (*position < end) {
536
+ int type = (int)buffer[(*position)++];
537
+ int key_size = strlen(buffer + *position);
538
+ VALUE to_append;
539
+
540
+ *position += key_size + 1; // just skip the key, they're in order.
541
+ to_append = get_value(buffer, position, type);
542
+ rb_ary_push(value, to_append);
543
+ }
544
+ (*position)++;
545
+ break;
546
+ }
547
+ case 5:
548
+ {
549
+ int length, subtype;
550
+ VALUE data, st;
551
+ VALUE argv[2];
552
+ memcpy(&length, buffer + *position, 4);
553
+ subtype = (unsigned char)buffer[*position + 4];
554
+ if (subtype == 2) {
555
+ data = rb_str_new(buffer + *position + 9, length - 4);
556
+ } else {
557
+ data = rb_str_new(buffer + *position + 5, length);
558
+ }
559
+ st = INT2FIX(subtype);
560
+ argv[0] = data;
561
+ argv[1] = st;
562
+ value = rb_class_new_instance(2, argv, Binary);
563
+ *position += length + 5;
564
+ break;
565
+ }
566
+ case 6:
567
+ {
568
+ value = Qnil;
569
+ break;
570
+ }
571
+ case 7:
572
+ {
573
+ VALUE str = rb_str_new(buffer + *position, 12);
574
+ VALUE oid = rb_funcall(str, rb_intern("unpack"), 1, rb_str_new2("C*"));
575
+ value = rb_class_new_instance(1, &oid, ObjectID);
576
+ *position += 12;
577
+ break;
578
+ }
579
+ case 8:
580
+ {
581
+ value = buffer[(*position)++] ? Qtrue : Qfalse;
582
+ break;
583
+ }
584
+ case 9:
585
+ {
586
+ long long millis;
587
+ VALUE seconds, microseconds;
588
+ memcpy(&millis, buffer + *position, 8);
589
+ seconds = INT2NUM(millis / 1000);
590
+ microseconds = INT2NUM((millis % 1000) * 1000);
591
+
592
+ value = rb_funcall(Time, rb_intern("at"), 2, seconds, microseconds);
593
+ value = rb_funcall(value, rb_intern("utc"), 0);
594
+ *position += 8;
595
+ break;
596
+ }
597
+ case 10:
598
+ {
599
+ value = Qnil;
600
+ break;
601
+ }
602
+ case 11:
603
+ {
604
+ int pattern_length = strlen(buffer + *position);
605
+ VALUE pattern = STR_NEW(buffer + *position, pattern_length);
606
+ int flags_length, flags = 0, i = 0;
607
+ char extra[10];
608
+ VALUE argv[3];
609
+ *position += pattern_length + 1;
610
+
611
+ flags_length = strlen(buffer + *position);
612
+ extra[0] = 0;
613
+ for (i = 0; i < flags_length; i++) {
614
+ char flag = buffer[*position + i];
615
+ if (flag == 'i') {
616
+ flags |= IGNORECASE;
617
+ }
618
+ else if (flag == 'm') {
619
+ flags |= MULTILINE;
620
+ }
621
+ else if (flag == 'x') {
622
+ flags |= EXTENDED;
623
+ }
624
+ else if (strlen(extra) < 9) {
625
+ strncat(extra, &flag, 1);
626
+ }
627
+ }
628
+ argv[0] = pattern;
629
+ argv[1] = INT2FIX(flags);
630
+ argv[2] = rb_str_new2(extra);
631
+ value = rb_class_new_instance(3, argv, RegexpOfHolding);
632
+ *position += flags_length + 1;
633
+ break;
634
+ }
635
+ case 12:
636
+ {
637
+ int collection_length;
638
+ VALUE collection, str, oid, id, argv[2];
639
+ *position += 4;
640
+ collection_length = strlen(buffer + *position);
641
+ collection = STR_NEW(buffer + *position, collection_length);
642
+ *position += collection_length + 1;
643
+
644
+ str = rb_str_new(buffer + *position, 12);
645
+ oid = rb_funcall(str, rb_intern("unpack"), 1, rb_str_new2("C*"));
646
+ id = rb_class_new_instance(1, &oid, ObjectID);
647
+ *position += 12;
648
+
649
+ argv[0] = collection;
650
+ argv[1] = id;
651
+ value = rb_class_new_instance(2, argv, DBRef);
652
+ break;
653
+ }
654
+ case 14:
655
+ {
656
+ int value_length;
657
+ memcpy(&value_length, buffer + *position, 4);
658
+ value = ID2SYM(rb_intern(buffer + *position + 4));
659
+ *position += value_length + 4;
660
+ break;
661
+ }
662
+ case 15:
663
+ {
664
+ int code_length, scope_size;
665
+ VALUE code, scope, argv[2];
666
+ *position += 8;
667
+ code_length = strlen(buffer + *position);
668
+ code = STR_NEW(buffer + *position, code_length);
669
+ *position += code_length + 1;
670
+
671
+ memcpy(&scope_size, buffer + *position, 4);
672
+ scope = elements_to_hash(buffer + *position + 4, scope_size - 5);
673
+ *position += scope_size;
674
+
675
+ argv[0] = code;
676
+ argv[1] = scope;
677
+ value = rb_class_new_instance(2, argv, Code);
678
+ break;
679
+ }
680
+ case 16:
681
+ {
682
+ int i;
683
+ memcpy(&i, buffer + *position, 4);
684
+ value = LL2NUM(i);
685
+ *position += 4;
686
+ break;
687
+ }
688
+ case 17:
689
+ {
690
+ int i;
691
+ int j;
692
+ memcpy(&i, buffer + *position, 4);
693
+ memcpy(&j, buffer + *position + 4, 4);
694
+ value = rb_ary_new3(2, LL2NUM(i), LL2NUM(j));
695
+ *position += 8;
696
+ break;
697
+ }
698
+ case 18:
699
+ {
700
+ long long ll;
701
+ memcpy(&ll, buffer + *position, 8);
702
+ value = LL2NUM(ll);
703
+ *position += 8;
704
+ break;
705
+ }
706
+ default:
707
+ {
708
+ rb_raise(rb_eTypeError, "no c decoder for this type yet (%d)", type);
709
+ break;
710
+ }
711
+ }
712
+ return value;
713
+ }
714
+
715
+ static VALUE elements_to_hash(const char* buffer, int max) {
716
+ VALUE hash = rb_class_new_instance(0, NULL, OrderedHash);
717
+ int position = 0;
718
+ while (position < max) {
719
+ int type = (int)buffer[position++];
720
+ int name_length = strlen(buffer + position);
721
+ VALUE name = STR_NEW(buffer + position, name_length);
722
+ VALUE value;
723
+ position += name_length + 1;
724
+ value = get_value(buffer, &position, type);
725
+ rb_funcall(hash, rb_intern("[]="), 2, name, value);
726
+ }
727
+ return hash;
728
+ }
729
+
730
+ static VALUE method_deserialize(VALUE self, VALUE bson) {
731
+ const char* buffer = RSTRING_PTR(bson);
732
+ int remaining = RSTRING_LEN(bson);
733
+
734
+ // NOTE we just swallow the size and end byte here
735
+ buffer += 4;
736
+ remaining -= 5;
737
+
738
+ return elements_to_hash(buffer, remaining);
739
+ }
740
+
741
+ void Init_cbson() {
742
+ VALUE mongo, CBson;
743
+ Time = rb_const_get(rb_cObject, rb_intern("Time"));
744
+
745
+ mongo = rb_const_get(rb_cObject, rb_intern("Mongo"));
746
+ rb_require("mongo/types/binary");
747
+ Binary = rb_const_get(mongo, rb_intern("Binary"));
748
+ rb_require("mongo/types/objectid");
749
+ ObjectID = rb_const_get(mongo, rb_intern("ObjectID"));
750
+ rb_require("mongo/types/dbref");
751
+ DBRef = rb_const_get(mongo, rb_intern("DBRef"));
752
+ rb_require("mongo/types/code");
753
+ Code = rb_const_get(mongo, rb_intern("Code"));
754
+ rb_require("mongo/types/regexp_of_holding");
755
+ RegexpOfHolding = rb_const_get(mongo, rb_intern("RegexpOfHolding"));
756
+ rb_require("mongo/errors");
757
+ InvalidName = rb_const_get(mongo, rb_intern("InvalidName"));
758
+ rb_require("mongo/util/ordered_hash");
759
+ OrderedHash = rb_const_get(rb_cObject, rb_intern("OrderedHash"));
760
+
761
+ CBson = rb_define_module("CBson");
762
+ rb_define_module_function(CBson, "serialize", method_serialize, 2);
763
+ rb_define_module_function(CBson, "deserialize", method_deserialize, 1);
764
+ }
@@ -0,0 +1,8 @@
1
+ require 'mkmf'
2
+
3
+ have_header("ruby/st.h") || have_header("st.h")
4
+ have_header("ruby/regex.h") || have_header("regex.h")
5
+ have_header("ruby/encoding.h")
6
+
7
+ dir_config('cbson')
8
+ create_makefile('mongo_ext/cbson')
@@ -0,0 +1,28 @@
1
+ # We need to list all of the included files because we aren't allowed to use
2
+ # Dir[...] in the github sandbox.
3
+ PACKAGE_FILES = ['Rakefile', 'mongo-extensions.gemspec',
4
+ 'ext/cbson/cbson.c',
5
+ 'ext/cbson/extconf.rb']
6
+ TEST_FILES = []
7
+
8
+ Gem::Specification.new do |s|
9
+ s.name = 'mongo_ext'
10
+
11
+ s.version = '0.15'
12
+ s.add_dependency('mongo', ['= 0.15'])
13
+
14
+ s.platform = Gem::Platform::RUBY
15
+ s.summary = 'C extensions for the MongoDB Ruby driver'
16
+ s.description = 'C extensions to accelerate the MongoDB Ruby driver. For more information about Mongo, see http://www.mongodb.org.'
17
+
18
+ s.require_paths = ['ext']
19
+ s.files = PACKAGE_FILES
20
+ s.test_files = TEST_FILES
21
+
22
+ s.has_rdoc = false
23
+ s.extensions << 'ext/cbson/extconf.rb'
24
+
25
+ s.author = 'Mike Dirolf'
26
+ s.email = 'mongodb-dev@googlegroups.com'
27
+ s.homepage = 'http://www.mongodb.org'
28
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mongo_ext
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.15"
5
+ platform: ruby
6
+ authors:
7
+ - Mike Dirolf
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-10-05 00:00:00 -04:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: mongo
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - "="
22
+ - !ruby/object:Gem::Version
23
+ version: "0.15"
24
+ version:
25
+ description: C extensions to accelerate the MongoDB Ruby driver. For more information about Mongo, see http://www.mongodb.org.
26
+ email: mongodb-dev@googlegroups.com
27
+ executables: []
28
+
29
+ extensions:
30
+ - ext/cbson/extconf.rb
31
+ extra_rdoc_files: []
32
+
33
+ files:
34
+ - Rakefile
35
+ - mongo-extensions.gemspec
36
+ - ext/cbson/cbson.c
37
+ - ext/cbson/extconf.rb
38
+ has_rdoc: true
39
+ homepage: http://www.mongodb.org
40
+ licenses: []
41
+
42
+ post_install_message:
43
+ rdoc_options: []
44
+
45
+ require_paths:
46
+ - ext
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: "0"
52
+ version:
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ version:
59
+ requirements: []
60
+
61
+ rubyforge_project:
62
+ rubygems_version: 1.3.5
63
+ signing_key:
64
+ specification_version: 3
65
+ summary: C extensions for the MongoDB Ruby driver
66
+ test_files: []
67
+