mongo_ext 0.15

Sign up to get free protection for your applications and to get access to all the features.
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
+