bson_ext_ns 1.3.1

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,215 @@
1
+ # -*- mode: ruby; -*-
2
+ require 'rubygems'
3
+ require 'rubygems/specification'
4
+ require 'fileutils'
5
+ require 'rake'
6
+ require 'rake/testtask'
7
+ require 'rake/gempackagetask'
8
+ require 'rbconfig'
9
+ include Config
10
+ ENV['TEST_MODE'] = 'TRUE'
11
+
12
+ task :java do
13
+ Rake::Task['build:java'].invoke
14
+ Rake::Task['test:ruby'].invoke
15
+ end
16
+
17
+ namespace :build do
18
+ desc "Build the java extensions."
19
+ task :java do
20
+ puts "Building Java extensions..."
21
+ java_dir = File.join(File.dirname(__FILE__), 'ext', 'java')
22
+ jar_dir = File.join(java_dir, 'jar')
23
+
24
+ jruby_jar = File.join(jar_dir, 'jruby.jar')
25
+ mongo_jar = File.join(jar_dir, 'mongo-2.4.jar')
26
+ bson_jar = File.join(jar_dir, 'bson-2.2.jar')
27
+
28
+ src_base = File.join(java_dir, 'src')
29
+
30
+ system("javac -Xlint:unchecked -classpath #{jruby_jar}:#{mongo_jar}:#{bson_jar} #{File.join(src_base, 'org', 'jbson', '*.java')}")
31
+ system("cd #{src_base} && jar cf #{File.join(jar_dir, 'jbson.jar')} #{File.join('.', 'org', 'jbson', '*.class')}")
32
+ end
33
+ end
34
+
35
+ desc "Test the MongoDB Ruby driver."
36
+ task :test do
37
+ puts "\nTo test the driver with the C-extensions:\nrake test:c\n\n"
38
+ puts "To test the pure ruby driver: \nrake test:ruby\n\n"
39
+ end
40
+
41
+ namespace :test do
42
+
43
+ desc "Test the driver with the C extension enabled."
44
+ task :c do
45
+ ENV['C_EXT'] = 'TRUE'
46
+ if ENV['TEST']
47
+ Rake::Task['test:functional'].invoke
48
+ else
49
+ Rake::Task['test:unit'].invoke
50
+ Rake::Task['test:functional'].invoke
51
+ Rake::Task['test:bson'].invoke
52
+ Rake::Task['test:pooled_threading'].invoke
53
+ Rake::Task['test:drop_databases'].invoke
54
+ end
55
+ ENV['C_EXT'] = nil
56
+ end
57
+
58
+ desc "Test the driver using pure ruby (no C extension)"
59
+ task :ruby do
60
+ ENV['C_EXT'] = nil
61
+ if ENV['TEST']
62
+ Rake::Task['test:functional'].invoke
63
+ else
64
+ Rake::Task['test:unit'].invoke
65
+ Rake::Task['test:functional'].invoke
66
+ Rake::Task['test:bson'].invoke
67
+ Rake::Task['test:pooled_threading'].invoke
68
+ Rake::Task['test:drop_databases'].invoke
69
+ end
70
+ end
71
+
72
+ desc "Run the replica set test suite"
73
+ Rake::TestTask.new(:rs) do |t|
74
+ t.test_files = FileList['test/replica_sets/*_test.rb']
75
+ t.verbose = true
76
+ t.ruby_opts << '-w'
77
+ end
78
+
79
+ Rake::TestTask.new(:unit) do |t|
80
+ t.test_files = FileList['test/unit/*_test.rb']
81
+ t.verbose = true
82
+ t.ruby_opts << '-w'
83
+ end
84
+
85
+ Rake::TestTask.new(:functional) do |t|
86
+ t.test_files = FileList['test/*_test.rb']
87
+ t.verbose = true
88
+ t.ruby_opts << '-w'
89
+ end
90
+
91
+ Rake::TestTask.new(:pooled_threading) do |t|
92
+ t.test_files = FileList['test/threading/*_test.rb']
93
+ t.verbose = true
94
+ t.ruby_opts << '-w'
95
+ end
96
+
97
+ Rake::TestTask.new(:auto_reconnect) do |t|
98
+ t.test_files = FileList['test/auxillary/autoreconnect_test.rb']
99
+ t.verbose = true
100
+ t.ruby_opts << '-w'
101
+ end
102
+
103
+ Rake::TestTask.new(:authentication) do |t|
104
+ t.test_files = FileList['test/auxillary/authentication_test.rb']
105
+ t.verbose = true
106
+ t.ruby_opts << '-w'
107
+ end
108
+
109
+ Rake::TestTask.new(:new_features) do |t|
110
+ t.test_files = FileList['test/auxillary/1.4_features.rb']
111
+ t.verbose = true
112
+ t.ruby_opts << '-w'
113
+ end
114
+
115
+ Rake::TestTask.new(:bson) do |t|
116
+ t.test_files = FileList['test/bson/*_test.rb']
117
+ t.verbose = true
118
+ t.ruby_opts << '-w'
119
+ end
120
+
121
+ task :drop_databases do |t|
122
+ puts "Dropping test databases..."
123
+ require './lib/mongo'
124
+ con = Mongo::Connection.new(ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost',
125
+ ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::Connection::DEFAULT_PORT)
126
+ con.database_names.each do |name|
127
+ con.drop_database(name) if name =~ /^ruby-test/
128
+ end
129
+ end
130
+ end
131
+
132
+ desc "Generate RDOC documentation"
133
+ task :rdoc do
134
+ version = eval(File.read("mongo.gemspec")).version
135
+ out = File.join('html', version.to_s)
136
+ FileUtils.rm_rf('html')
137
+ system "rdoc --main README.md --op #{out} --inline-source --quiet README.md `find lib -name '*.rb'`"
138
+ end
139
+
140
+ desc "Generate YARD documentation"
141
+ task :ydoc do
142
+ require File.join(File.dirname(__FILE__), 'lib', 'mongo')
143
+ out = File.join('ydoc', Mongo::VERSION)
144
+ FileUtils.rm_rf('ydoc')
145
+ system "yardoc lib/**/*.rb lib/mongo/**/*.rb lib/bson/**/*.rb -e yard/yard_ext.rb -p yard/templates -o #{out} --title MongoRuby-#{Mongo::VERSION} --files docs/TUTORIAL.md,docs/GridFS.md,docs/FAQ.md,docs/REPLICA_SETS.md,docs/WRITE_CONCERN.md,docs/HISTORY.md,docs/CREDITS.md,docs/RELEASES.md"
146
+ end
147
+
148
+ namespace :bamboo do
149
+ task :ci_reporter do
150
+ begin
151
+ require 'ci/reporter/rake/test_unit'
152
+ rescue LoadError
153
+ warn "Warning: Unable to load ci_reporter gem."
154
+ end
155
+ end
156
+
157
+ namespace :test do
158
+ task :ruby => [:ci_reporter, "ci:setup:testunit"] do
159
+ Rake::Task['test:ruby'].invoke
160
+ end
161
+
162
+ task :c => [:ci_reporter, "ci:setup:testunit"] do
163
+ Rake::Task['gem:install_extensions'].invoke
164
+ Rake::Task['test:c'].invoke
165
+ end
166
+ end
167
+ end
168
+
169
+ namespace :gem do
170
+
171
+ desc "Install the gem locally"
172
+ task :install do
173
+ `gem build bson.gemspec`
174
+ `gem install --no-rdoc --no-ri bson-*.gem`
175
+
176
+ `gem build mongo.gemspec`
177
+ `gem install --no-rdoc --no-ri mongo-*.gem`
178
+
179
+ `rm mongo-*.gem`
180
+ `rm bson-*.gem`
181
+ end
182
+
183
+ desc "Install the optional c extensions"
184
+ task :install_extensions do
185
+ `gem uninstall bson_ext`
186
+ `gem build bson_ext.gemspec`
187
+ `gem install --no-rdoc --no-ri bson_ext-*.gem`
188
+ `rm bson_ext-*.gem`
189
+ end
190
+
191
+ desc "Build all gems"
192
+ task :build_all do
193
+ `gem build mongo.gemspec`
194
+ `gem build bson.gemspec`
195
+ `gem build bson.java.gemspec`
196
+ `gem build bson_ext.gemspec`
197
+ end
198
+
199
+ end
200
+
201
+ namespace :ci do
202
+ namespace :test do
203
+ task :c do
204
+ Rake::Task['gem:install'].invoke
205
+ Rake::Task['gem:install_extensions'].invoke
206
+ Rake::Task['test:c'].invoke
207
+ end
208
+ end
209
+ end
210
+
211
+ task :default => :list
212
+
213
+ task :list do
214
+ system 'rake -T'
215
+ end
data/bson_ext.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ require './lib/bson'
2
+ VERSION_HEADER = File.open(File.join(File.dirname(__FILE__), 'ext', 'cbson', 'version.h'), "r")
3
+ VERSION = VERSION_HEADER.read.scan(/VERSION "(\d[^"]+)"/)[0][0]
4
+ Gem::Specification.new do |s|
5
+ s.name = 'bson_ext_ns'
6
+
7
+ s.version = VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.summary = 'C extensions for Ruby BSON.'
10
+ s.description = 'C extensions to accelerate the Ruby BSON serialization. For more information about BSON, see http://bsonspec.org. For information about MongoDB, see http://www.mongodb.org.'
11
+
12
+ s.require_paths = ['ext']
13
+ s.files = ['Rakefile', 'bson_ext.gemspec']
14
+ s.files += Dir['ext/**/*.rb'] + Dir['ext/**/*.c'] + Dir['ext/**/*.h']
15
+ s.test_files = []
16
+
17
+ s.has_rdoc = false
18
+ s.extensions << 'ext/cbson/extconf.rb'
19
+
20
+ s.author = 'Mike Dirolf'
21
+ s.email = 'mongodb-dev@googlegroups.com'
22
+ s.homepage = 'http://www.mongodb.org'
23
+ end
@@ -0,0 +1,135 @@
1
+ /*
2
+ * Copyright 2009-2010 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
+ #include <stdlib.h>
18
+ #include <string.h>
19
+
20
+ #include "buffer.h"
21
+
22
+ #define INITIAL_BUFFER_SIZE 256
23
+
24
+ struct buffer {
25
+ char* buffer;
26
+ int size;
27
+ int position;
28
+ };
29
+
30
+ /* Allocate and return a new buffer.
31
+ * Return NULL on allocation failure. */
32
+ buffer_t buffer_new(void) {
33
+ buffer_t buffer;
34
+ buffer = (buffer_t)malloc(sizeof(struct buffer));
35
+ if (buffer == NULL) {
36
+ return NULL;
37
+ }
38
+
39
+ buffer->size = INITIAL_BUFFER_SIZE;
40
+ buffer->position = 0;
41
+ buffer->buffer = (char*)malloc(sizeof(char) * INITIAL_BUFFER_SIZE);
42
+ if (buffer->buffer == NULL) {
43
+ free(buffer);
44
+ return NULL;
45
+ }
46
+
47
+ return buffer;
48
+ }
49
+
50
+ /* Free the memory allocated for `buffer`.
51
+ * Return non-zero on failure. */
52
+ int bson_buffer_free(buffer_t buffer) {
53
+ if (buffer == NULL) {
54
+ return 1;
55
+ }
56
+ free(buffer->buffer);
57
+ free(buffer);
58
+ return 0;
59
+ }
60
+
61
+ /* Grow `buffer` to at least `min_length`.
62
+ * Return non-zero on allocation failure. */
63
+ static int buffer_grow(buffer_t buffer, int min_length) {
64
+ int size = buffer->size;
65
+ char* old_buffer = buffer->buffer;
66
+ if (size >= min_length) {
67
+ return 0;
68
+ }
69
+ while (size < min_length) {
70
+ size *= 2;
71
+ }
72
+ buffer->buffer = (char*)realloc(buffer->buffer, sizeof(char) * size);
73
+ if (buffer->buffer == NULL) {
74
+ free(old_buffer);
75
+ free(buffer);
76
+ return 1;
77
+ }
78
+ buffer->size = size;
79
+ return 0;
80
+ }
81
+
82
+ /* Assure that `buffer` has at least `size` free bytes (and grow if needed).
83
+ * Return non-zero on allocation failure. */
84
+ static int buffer_assure_space(buffer_t buffer, int size) {
85
+ if (buffer->position + size <= buffer->size) {
86
+ return 0;
87
+ }
88
+ return buffer_grow(buffer, buffer->position + size);
89
+ }
90
+
91
+ /* Save `size` bytes from the current position in `buffer` (and grow if needed).
92
+ * Return offset for writing, or -1 on allocation failure. */
93
+ buffer_position buffer_save_space(buffer_t buffer, int size) {
94
+ int position = buffer->position;
95
+ if (buffer_assure_space(buffer, size) != 0) {
96
+ return -1;
97
+ }
98
+ buffer->position += size;
99
+ return position;
100
+ }
101
+
102
+ /* Write `size` bytes from `data` to `buffer` (and grow if needed).
103
+ * Return non-zero on allocation failure. */
104
+ int buffer_write(buffer_t buffer, const char* data, int size) {
105
+ if (buffer_assure_space(buffer, size) != 0) {
106
+ return 1;
107
+ }
108
+
109
+ memcpy(buffer->buffer + buffer->position, data, size);
110
+ buffer->position += size;
111
+ return 0;
112
+ }
113
+
114
+ /* Write `size` bytes from `data` to `buffer` at position `position`.
115
+ * Does not change the internal position of `buffer`.
116
+ * Return non-zero if buffer isn't large enough for write. */
117
+ int buffer_write_at_position(buffer_t buffer, buffer_position position,
118
+ const char* data, int size) {
119
+ if (position + size > buffer->size) {
120
+ bson_buffer_free(buffer);
121
+ return 1;
122
+ }
123
+
124
+ memcpy(buffer->buffer + position, data, size);
125
+ return 0;
126
+ }
127
+
128
+
129
+ int buffer_get_position(buffer_t buffer) {
130
+ return buffer->position;
131
+ }
132
+
133
+ char* buffer_get_buffer(buffer_t buffer) {
134
+ return buffer->buffer;
135
+ }
@@ -0,0 +1,55 @@
1
+ /*
2
+ * Copyright 2009-2010 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
+ #ifndef BUFFER_H
18
+ #define BUFFER_H
19
+
20
+ /* Note: if any of these functions return a failure condition then the buffer
21
+ * has already been freed. */
22
+
23
+ /* A buffer */
24
+ typedef struct buffer* buffer_t;
25
+ /* A position in the buffer */
26
+ typedef int buffer_position;
27
+
28
+ /* Allocate and return a new buffer.
29
+ * Return NULL on allocation failure. */
30
+ buffer_t buffer_new(void);
31
+
32
+ /* Free the memory allocated for `buffer`.
33
+ * Return non-zero on failure. */
34
+ int bson_buffer_free(buffer_t buffer);
35
+
36
+ /* Save `size` bytes from the current position in `buffer` (and grow if needed).
37
+ * Return offset for writing, or -1 on allocation failure. */
38
+ buffer_position buffer_save_space(buffer_t buffer, int size);
39
+
40
+ /* Write `size` bytes from `data` to `buffer` (and grow if needed).
41
+ * Return non-zero on allocation failure. */
42
+ int buffer_write(buffer_t buffer, const char* data, int size);
43
+
44
+ /* Write `size` bytes from `data` to `buffer` at position `position`.
45
+ * Does not change the internal position of `buffer`.
46
+ * Return non-zero if buffer isn't large enough for write. */
47
+ int buffer_write_at_position(buffer_t buffer, buffer_position position, const char* data, int size);
48
+
49
+ /* Getters for the internals of a buffer_t.
50
+ * Should try to avoid using these as much as possible
51
+ * since they break the abstraction. */
52
+ buffer_position buffer_get_position(buffer_t buffer);
53
+ char* buffer_get_buffer(buffer_t buffer);
54
+
55
+ #endif
data/ext/cbson/cbson.c ADDED
@@ -0,0 +1,996 @@
1
+ /*
2
+ * Copyright 2009-2010 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
+ /* Ensure compatibility with early releases of Ruby 1.8.5 */
26
+ #ifndef RSTRING_PTR
27
+ # define RSTRING_PTR(v) RSTRING(v)->ptr
28
+ #endif
29
+
30
+ #ifndef RSTRING_LEN
31
+ # define RSTRING_LEN(v) RSTRING(v)->len
32
+ #endif
33
+ #ifndef RSTRING_LENINT
34
+ # define RSTRING_LENINT(v) (int)(RSTRING_LEN(v))
35
+ #endif
36
+
37
+ #ifndef RARRAY_LEN
38
+ # define RARRAY_LEN(v) RARRAY(v)->len
39
+ #endif
40
+ #ifndef RARRAY_LENINT
41
+ # define RARRAY_LENINT(v) (int)(RARRAY_LEN(v))
42
+ #endif
43
+
44
+ #if HAVE_RUBY_ST_H
45
+ #include "ruby/st.h"
46
+ #endif
47
+ #if HAVE_ST_H
48
+ #include "st.h"
49
+ #endif
50
+
51
+ #if HAVE_RUBY_REGEX_H
52
+ #include "ruby/regex.h"
53
+ #endif
54
+ #if HAVE_REGEX_H
55
+ #include "regex.h"
56
+ #endif
57
+
58
+ #include <string.h>
59
+ #include <math.h>
60
+ #include <unistd.h>
61
+ #include <time.h>
62
+
63
+ #include "version.h"
64
+ #include "buffer.h"
65
+ #include "encoding_helpers.h"
66
+
67
+ #define SAFE_WRITE(buffer, data, size) \
68
+ if (buffer_write((buffer), (data), (size)) != 0) \
69
+ rb_raise(rb_eNoMemError, "failed to allocate memory in buffer.c")
70
+
71
+ #define SAFE_WRITE_AT_POS(buffer, position, data, size) \
72
+ if (buffer_write_at_position((buffer), (position), (data), (size)) != 0) \
73
+ rb_raise(rb_eRuntimeError, "invalid write at position in buffer.c")
74
+
75
+ #define MAX_HOSTNAME_LENGTH 256
76
+
77
+ static ID element_assignment_method;
78
+ static ID unpack_method;
79
+ static ID utc_method;
80
+ static ID lt_operator;
81
+ static ID gt_operator;
82
+
83
+ static VALUE Binary;
84
+ static VALUE ObjectId;
85
+ static VALUE DBRef;
86
+ static VALUE Code;
87
+ static VALUE MinKey;
88
+ static VALUE MaxKey;
89
+ static VALUE Timestamp;
90
+ static VALUE Regexp;
91
+ static VALUE OrderedHash;
92
+ static VALUE InvalidKeyName;
93
+ static VALUE InvalidStringEncoding;
94
+ static VALUE InvalidDocument;
95
+ static VALUE DigestMD5;
96
+ static VALUE RB_HASH;
97
+
98
+ static int max_bson_size;
99
+
100
+ #if HAVE_RUBY_ENCODING_H
101
+ #include "ruby/encoding.h"
102
+ #define STR_NEW(p,n) \
103
+ ({ \
104
+ VALUE _str = rb_enc_str_new((p), (n), rb_utf8_encoding()); \
105
+ rb_encoding* internal_encoding = rb_default_internal_encoding(); \
106
+ if (internal_encoding) { \
107
+ _str = rb_str_export_to_enc(_str, internal_encoding); \
108
+ } \
109
+ _str; \
110
+ })
111
+ #define TO_UTF8(string) rb_str_export_to_enc((string), rb_utf8_encoding())
112
+ #else
113
+ #define STR_NEW(p,n) rb_str_new((p), (n))
114
+ #define TO_UTF8(string) (string)
115
+ #endif
116
+
117
+ static void write_utf8(buffer_t buffer, VALUE string, char check_null) {
118
+ result_t status = check_string(RSTRING_PTR(string), RSTRING_LEN(string),
119
+ 1, check_null);
120
+ if (status == HAS_NULL) {
121
+ bson_buffer_free(buffer);
122
+ rb_raise(InvalidDocument, "Key names / regex patterns must not contain the NULL byte");
123
+ } else if (status == NOT_UTF_8) {
124
+ bson_buffer_free(buffer);
125
+ rb_raise(InvalidStringEncoding, "String not valid UTF-8");
126
+ }
127
+ string = TO_UTF8(string);
128
+ SAFE_WRITE(buffer, RSTRING_PTR(string), RSTRING_LEN(string));
129
+ }
130
+
131
+ // this sucks. but for some reason these moved around between 1.8 and 1.9
132
+ #ifdef ONIGURUMA_H
133
+ #define IGNORECASE ONIG_OPTION_IGNORECASE
134
+ #define MULTILINE ONIG_OPTION_MULTILINE
135
+ #define EXTENDED ONIG_OPTION_EXTEND
136
+ #else
137
+ #define IGNORECASE RE_OPTION_IGNORECASE
138
+ #define MULTILINE RE_OPTION_MULTILINE
139
+ #define EXTENDED RE_OPTION_EXTENDED
140
+ #endif
141
+
142
+ /* TODO we ought to check that the malloc or asprintf was successful
143
+ * and raise an exception if not. */
144
+ /* TODO maybe we can use something more portable like vsnprintf instead
145
+ * of this hack. And share it with the Python extension ;) */
146
+ /* If we don't have ASPRINTF, there are two possibilities:
147
+ * either use _scprintf and _snprintf on for Windows or
148
+ * use snprintf for solaris. */
149
+ #ifndef HAVE_ASPRINTF
150
+ #ifdef _WIN32 || _MSC_VER
151
+ #define INT2STRING(buffer, i) \
152
+ { \
153
+ int vslength = _scprintf("%d", i) + 1; \
154
+ *buffer = malloc(vslength); \
155
+ _snprintf(*buffer, vslength, "%d", i); \
156
+ }
157
+ #define FREE_INTSTRING(buffer) free(buffer)
158
+ #else
159
+ #define INT2STRING(buffer, i) \
160
+ { \
161
+ int vslength = snprintf(NULL, 0, "%d", i) + 1; \
162
+ *buffer = malloc(vslength); \
163
+ snprintf(*buffer, vslength, "%d", i); \
164
+ }
165
+ #define FREE_INTSTRING(buffer) free(buffer)
166
+ #endif
167
+ #else
168
+ #define INT2STRING(buffer, i) asprintf(buffer, "%d", i);
169
+ #ifdef USING_SYSTEM_ALLOCATOR_LIBRARY /* Ruby Enterprise Edition with tcmalloc */
170
+ #define FREE_INTSTRING(buffer) system_free(buffer)
171
+ #else
172
+ #define FREE_INTSTRING(buffer) free(buffer)
173
+ #endif
174
+ #endif
175
+
176
+ #ifndef RREGEXP_SRC
177
+ #define RREGEXP_SRC(r) rb_str_new(RREGEXP((r))->str, RREGEXP((r))->len)
178
+ #endif
179
+
180
+ // rubinius compatibility
181
+ #ifndef RREGEXP_OPTIONS
182
+ #define RREGEXP_OPTIONS(r) RREGEXP(value)->ptr->options
183
+ #endif
184
+
185
+ static char zero = 0;
186
+ static char one = 1;
187
+
188
+ static char hostname_digest[17];
189
+ static unsigned int object_id_inc = 0;
190
+
191
+ static int cmp_char(const void* a, const void* b) {
192
+ return *(char*)a - *(char*)b;
193
+ }
194
+
195
+ static void write_doc(buffer_t buffer, VALUE hash, VALUE check_keys, VALUE move_id);
196
+ static int write_element_with_id(VALUE key, VALUE value, VALUE extra);
197
+ static int write_element_without_id(VALUE key, VALUE value, VALUE extra);
198
+ static VALUE elements_to_hash(const char* buffer, int max);
199
+
200
+ static VALUE pack_extra(buffer_t buffer, VALUE check_keys) {
201
+ return rb_ary_new3(2, LL2NUM((long long)buffer), check_keys);
202
+ }
203
+
204
+ static void write_name_and_type(buffer_t buffer, VALUE name, char type) {
205
+ SAFE_WRITE(buffer, &type, 1);
206
+ write_utf8(buffer, name, 1);
207
+ SAFE_WRITE(buffer, &zero, 1);
208
+ }
209
+
210
+ static int write_element(VALUE key, VALUE value, VALUE extra, int allow_id) {
211
+ buffer_t buffer = (buffer_t)NUM2LL(rb_ary_entry(extra, 0));
212
+ VALUE check_keys = rb_ary_entry(extra, 1);
213
+
214
+ if (TYPE(key) == T_SYMBOL) {
215
+ // TODO better way to do this... ?
216
+ key = rb_str_new2(rb_id2name(SYM2ID(key)));
217
+ }
218
+
219
+ if (TYPE(key) != T_STRING) {
220
+ bson_buffer_free(buffer);
221
+ rb_raise(rb_eTypeError, "keys must be strings or symbols");
222
+ }
223
+
224
+ if (allow_id == 0 && strcmp("_id", RSTRING_PTR(key)) == 0) {
225
+ return ST_CONTINUE;
226
+ }
227
+
228
+ if (check_keys == Qtrue) {
229
+ int i;
230
+ if (RSTRING_LEN(key) > 0 && RSTRING_PTR(key)[0] == '$') {
231
+ bson_buffer_free(buffer);
232
+ rb_raise(InvalidKeyName, "%s - key must not start with '$'", RSTRING_PTR(key));
233
+ }
234
+ for (i = 0; i < RSTRING_LEN(key); i++) {
235
+ if (RSTRING_PTR(key)[i] == '.') {
236
+ bson_buffer_free(buffer);
237
+ rb_raise(InvalidKeyName, "%s - key must not contain '.'", RSTRING_PTR(key));
238
+ }
239
+ }
240
+ }
241
+
242
+ switch(TYPE(value)) {
243
+ case T_BIGNUM:
244
+ {
245
+ if (rb_funcall(value, gt_operator, 1, LL2NUM(9223372036854775807LL)) == Qtrue ||
246
+ rb_funcall(value, lt_operator, 1, LL2NUM(-9223372036854775808ULL)) == Qtrue) {
247
+ bson_buffer_free(buffer);
248
+ rb_raise(rb_eRangeError, "MongoDB can only handle 8-byte ints");
249
+ }
250
+ }
251
+ // NOTE: falls through to T_FIXNUM code
252
+ case T_FIXNUM:
253
+ {
254
+ long long ll_value;
255
+ ll_value = NUM2LL(value);
256
+
257
+ if (ll_value > 2147483647LL ||
258
+ ll_value < -2147483648LL) {
259
+ write_name_and_type(buffer, key, 0x12);
260
+ SAFE_WRITE(buffer, (char*)&ll_value, 8);
261
+ } else {
262
+ int int_value;
263
+ write_name_and_type(buffer, key, 0x10);
264
+ int_value = (int)ll_value;
265
+ SAFE_WRITE(buffer, (char*)&int_value, 4);
266
+ }
267
+ break;
268
+ }
269
+ case T_TRUE:
270
+ {
271
+ write_name_and_type(buffer, key, 0x08);
272
+ SAFE_WRITE(buffer, &one, 1);
273
+ break;
274
+ }
275
+ case T_FALSE:
276
+ {
277
+ write_name_and_type(buffer, key, 0x08);
278
+ SAFE_WRITE(buffer, &zero, 1);
279
+ break;
280
+ }
281
+ case T_FLOAT:
282
+ {
283
+ double d = NUM2DBL(value);
284
+ write_name_and_type(buffer, key, 0x01);
285
+ SAFE_WRITE(buffer, (char*)&d, 8);
286
+ break;
287
+ }
288
+ case T_NIL:
289
+ {
290
+ write_name_and_type(buffer, key, 0x0A);
291
+ break;
292
+ }
293
+ case T_HASH:
294
+ {
295
+ write_name_and_type(buffer, key, 0x03);
296
+ write_doc(buffer, value, check_keys, Qfalse);
297
+ break;
298
+ }
299
+ case T_ARRAY:
300
+ {
301
+ buffer_position length_location, start_position, obj_length;
302
+ int items, i;
303
+ VALUE* values;
304
+
305
+ write_name_and_type(buffer, key, 0x04);
306
+ start_position = buffer_get_position(buffer);
307
+
308
+ // save space for length
309
+ length_location = buffer_save_space(buffer, 4);
310
+ if (length_location == -1) {
311
+ rb_raise(rb_eNoMemError, "failed to allocate memory in buffer.c");
312
+ }
313
+
314
+ items = RARRAY_LENINT(value);
315
+ for(i = 0; i < items; i++) {
316
+ char* name;
317
+ VALUE key;
318
+ INT2STRING(&name, i);
319
+ key = rb_str_new2(name);
320
+ write_element_with_id(key, rb_ary_entry(value, i), pack_extra(buffer, check_keys));
321
+ FREE_INTSTRING(name);
322
+ }
323
+
324
+ // write null byte and fill in length
325
+ SAFE_WRITE(buffer, &zero, 1);
326
+ obj_length = buffer_get_position(buffer) - start_position;
327
+ SAFE_WRITE_AT_POS(buffer, length_location, (const char*)&obj_length, 4);
328
+ break;
329
+ }
330
+ case T_STRING:
331
+ {
332
+ int length;
333
+ write_name_and_type(buffer, key, 0x02);
334
+ length = RSTRING_LENINT(value) + 1;
335
+ SAFE_WRITE(buffer, (char*)&length, 4);
336
+ write_utf8(buffer, value, 0);
337
+ SAFE_WRITE(buffer, &zero, 1);
338
+ break;
339
+ }
340
+ case T_SYMBOL:
341
+ {
342
+ const char* str_value = rb_id2name(SYM2ID(value));
343
+ int length = (int)strlen(str_value) + 1;
344
+ write_name_and_type(buffer, key, 0x0E);
345
+ SAFE_WRITE(buffer, (char*)&length, 4);
346
+ SAFE_WRITE(buffer, str_value, length);
347
+ break;
348
+ }
349
+ case T_OBJECT:
350
+ {
351
+ // TODO there has to be a better way to do these checks...
352
+ const char* cls = rb_obj_classname(value);
353
+ if (strcmp(cls, "BSON::Binary") == 0 ||
354
+ strcmp(cls, "ByteBuffer") == 0) {
355
+ const char subtype = strcmp(cls, "ByteBuffer") ?
356
+ (const char)FIX2INT(rb_funcall(value, rb_intern("subtype"), 0)) : 2;
357
+ VALUE string_data = rb_funcall(value, rb_intern("to_s"), 0);
358
+ int length = RSTRING_LENINT(string_data);
359
+ write_name_and_type(buffer, key, 0x05);
360
+ if (subtype == 2) {
361
+ const int other_length = length + 4;
362
+ SAFE_WRITE(buffer, (const char*)&other_length, 4);
363
+ SAFE_WRITE(buffer, &subtype, 1);
364
+ }
365
+ SAFE_WRITE(buffer, (const char*)&length, 4);
366
+ if (subtype != 2) {
367
+ SAFE_WRITE(buffer, &subtype, 1);
368
+ }
369
+ SAFE_WRITE(buffer, RSTRING_PTR(string_data), length);
370
+ break;
371
+ }
372
+ if (strcmp(cls, "BSON::ObjectId") == 0) {
373
+ VALUE as_array = rb_funcall(value, rb_intern("to_a"), 0);
374
+ int i;
375
+ write_name_and_type(buffer, key, 0x07);
376
+ for (i = 0; i < 12; i++) {
377
+ char byte = (char)FIX2INT(rb_ary_entry(as_array, i));
378
+ SAFE_WRITE(buffer, &byte, 1);
379
+ }
380
+ break;
381
+ }
382
+ if (strcmp(cls, "BSON::DBRef") == 0) {
383
+ buffer_position length_location, start_position, obj_length;
384
+ VALUE ns, oid;
385
+ write_name_and_type(buffer, key, 0x03);
386
+
387
+ start_position = buffer_get_position(buffer);
388
+
389
+ // save space for length
390
+ length_location = buffer_save_space(buffer, 4);
391
+ if (length_location == -1) {
392
+ rb_raise(rb_eNoMemError, "failed to allocate memory in buffer.c");
393
+ }
394
+
395
+ ns = rb_funcall(value, rb_intern("namespace"), 0);
396
+ write_element_with_id(rb_str_new2("$ref"), ns, pack_extra(buffer, Qfalse));
397
+ oid = rb_funcall(value, rb_intern("object_id"), 0);
398
+ write_element_with_id(rb_str_new2("$id"), oid, pack_extra(buffer, Qfalse));
399
+
400
+ // write null byte and fill in length
401
+ SAFE_WRITE(buffer, &zero, 1);
402
+ obj_length = buffer_get_position(buffer) - start_position;
403
+ SAFE_WRITE_AT_POS(buffer, length_location, (const char*)&obj_length, 4);
404
+ break;
405
+ }
406
+ if (strcmp(cls, "BSON::Code") == 0) {
407
+ buffer_position length_location, start_position, total_length;
408
+ int length;
409
+ VALUE code_str;
410
+ write_name_and_type(buffer, key, 0x0F);
411
+
412
+ start_position = buffer_get_position(buffer);
413
+ length_location = buffer_save_space(buffer, 4);
414
+ if (length_location == -1) {
415
+ rb_raise(rb_eNoMemError, "failed to allocate memory in buffer.c");
416
+ }
417
+
418
+ code_str = rb_funcall(value, rb_intern("code"), 0);
419
+ length = RSTRING_LENINT(code_str) + 1;
420
+ SAFE_WRITE(buffer, (char*)&length, 4);
421
+ SAFE_WRITE(buffer, RSTRING_PTR(code_str), length - 1);
422
+ SAFE_WRITE(buffer, &zero, 1);
423
+ write_doc(buffer, rb_funcall(value, rb_intern("scope"), 0), Qfalse, Qfalse);
424
+
425
+ total_length = buffer_get_position(buffer) - start_position;
426
+ SAFE_WRITE_AT_POS(buffer, length_location, (const char*)&total_length, 4);
427
+ break;
428
+ }
429
+ if (strcmp(cls, "BSON::MaxKey") == 0) {
430
+ write_name_and_type(buffer, key, 0x7f);
431
+ break;
432
+ }
433
+ if (strcmp(cls, "BSON::MinKey") == 0) {
434
+ write_name_and_type(buffer, key, 0xff);
435
+ break;
436
+ }
437
+ if (strcmp(cls, "BSON::Timestamp") == 0) {
438
+ write_name_and_type(buffer, key, 0x11);
439
+ int seconds = FIX2INT(
440
+ rb_funcall(value, rb_intern("seconds"), 0));
441
+ int increment = FIX2INT(
442
+ rb_funcall(value, rb_intern("increment"), 0));
443
+
444
+ SAFE_WRITE(buffer, (const char*)&increment, 4);
445
+ SAFE_WRITE(buffer, (const char*)&seconds, 4);
446
+ break;
447
+ }
448
+ if (strcmp(cls, "DateTime") == 0 || strcmp(cls, "Date") == 0 || strcmp(cls, "ActiveSupport::TimeWithZone") == 0) {
449
+ bson_buffer_free(buffer);
450
+ rb_raise(InvalidDocument, "%s is not currently supported; use a UTC Time instance instead.", cls);
451
+ break;
452
+ }
453
+ if(strcmp(cls, "Complex") == 0 || strcmp(cls, "Rational") == 0 || strcmp(cls, "BigDecimal") == 0) {
454
+ bson_buffer_free(buffer);
455
+ rb_raise(InvalidDocument, "Cannot serialize the Numeric type %s as BSON; only Bignum, Fixnum, and Float are supported.", cls);
456
+ break;
457
+ }
458
+ bson_buffer_free(buffer);
459
+ rb_raise(InvalidDocument, "Cannot serialize an object of class %s into BSON.", cls);
460
+ break;
461
+ }
462
+ case T_DATA:
463
+ {
464
+ const char* cls = rb_obj_classname(value);
465
+ if (strcmp(cls, "Time") == 0) {
466
+ double t = NUM2DBL(rb_funcall(value, rb_intern("to_f"), 0));
467
+ long long time_since_epoch = (long long)round(t * 1000);
468
+ write_name_and_type(buffer, key, 0x09);
469
+ SAFE_WRITE(buffer, (const char*)&time_since_epoch, 8);
470
+ break;
471
+ }
472
+ if(strcmp(cls, "BigDecimal") == 0) {
473
+ bson_buffer_free(buffer);
474
+ rb_raise(InvalidDocument, "Cannot serialize the Numeric type %s as BSON; only Bignum, Fixnum, and Float are supported.", cls);
475
+ break;
476
+ }
477
+ bson_buffer_free(buffer);
478
+ rb_raise(InvalidDocument, "Cannot serialize an object of class %s into BSON.", cls);
479
+ break;
480
+ }
481
+ case T_REGEXP:
482
+ {
483
+ VALUE pattern = RREGEXP_SRC(value);
484
+ long flags = RREGEXP_OPTIONS(value);
485
+ VALUE has_extra;
486
+
487
+ write_name_and_type(buffer, key, 0x0B);
488
+
489
+ write_utf8(buffer, pattern, 1);
490
+ SAFE_WRITE(buffer, &zero, 1);
491
+
492
+ if (flags & IGNORECASE) {
493
+ char ignorecase = 'i';
494
+ SAFE_WRITE(buffer, &ignorecase, 1);
495
+ }
496
+ if (flags & MULTILINE) {
497
+ char multiline = 'm';
498
+ char dotall = 's';
499
+ SAFE_WRITE(buffer, &multiline, 1);
500
+ SAFE_WRITE(buffer, &dotall, 1);
501
+ }
502
+ if (flags & EXTENDED) {
503
+ char extended = 'x';
504
+ SAFE_WRITE(buffer, &extended, 1);
505
+ }
506
+
507
+ has_extra = rb_funcall(value, rb_intern("respond_to?"), 1, rb_str_new2("extra_options_str"));
508
+ if (TYPE(has_extra) == T_TRUE) {
509
+ VALUE extra = rb_funcall(value, rb_intern("extra_options_str"), 0);
510
+ buffer_position old_position = buffer_get_position(buffer);
511
+ SAFE_WRITE(buffer, RSTRING_PTR(extra), RSTRING_LENINT(extra));
512
+ qsort(buffer_get_buffer(buffer) + old_position, RSTRING_LEN(extra), sizeof(char), cmp_char);
513
+ }
514
+ SAFE_WRITE(buffer, &zero, 1);
515
+
516
+ break;
517
+ }
518
+ default:
519
+ {
520
+ const char* cls = rb_obj_classname(value);
521
+ bson_buffer_free(buffer);
522
+ rb_raise(InvalidDocument, "Cannot serialize an object of class %s (type %d) into BSON.", cls, TYPE(value));
523
+ break;
524
+ }
525
+ }
526
+ return ST_CONTINUE;
527
+ }
528
+
529
+ static int write_element_without_id(VALUE key, VALUE value, VALUE extra) {
530
+ return write_element(key, value, extra, 0);
531
+ }
532
+
533
+ static int write_element_with_id(VALUE key, VALUE value, VALUE extra) {
534
+ return write_element(key, value, extra, 1);
535
+ }
536
+
537
+ static void write_doc(buffer_t buffer, VALUE hash, VALUE check_keys, VALUE move_id) {
538
+ buffer_position start_position = buffer_get_position(buffer);
539
+ buffer_position length_location = buffer_save_space(buffer, 4);
540
+ buffer_position length;
541
+ int allow_id;
542
+ int (*write_function)(VALUE, VALUE, VALUE) = NULL;
543
+ VALUE id_str = rb_str_new2("_id");
544
+ VALUE id_sym = ID2SYM(rb_intern("_id"));
545
+
546
+ if (length_location == -1) {
547
+ rb_raise(rb_eNoMemError, "failed to allocate memory in buffer.c");
548
+ }
549
+
550
+ // write '_id' first if move_id is true. then don't allow an id to be written.
551
+ if(move_id == Qtrue) {
552
+ allow_id = 0;
553
+ if (rb_funcall(hash, rb_intern("has_key?"), 1, id_str) == Qtrue) {
554
+ VALUE id = rb_hash_aref(hash, id_str);
555
+ write_element_with_id(id_str, id, pack_extra(buffer, check_keys));
556
+ } else if (rb_funcall(hash, rb_intern("has_key?"), 1, id_sym) == Qtrue) {
557
+ VALUE id = rb_hash_aref(hash, id_sym);
558
+ write_element_with_id(id_sym, id, pack_extra(buffer, check_keys));
559
+ }
560
+ }
561
+ else {
562
+ allow_id = 1;
563
+ // Ensure that hash doesn't contain both '_id' and :_id
564
+ if ((rb_obj_classname(hash), "Hash") == 0) {
565
+ if ((rb_funcall(hash, rb_intern("has_key?"), 1, id_str) == Qtrue) &&
566
+ (rb_funcall(hash, rb_intern("has_key?"), 1, id_sym) == Qtrue)) {
567
+ VALUE oid_sym = rb_hash_delete(hash, id_sym);
568
+ rb_funcall(hash, rb_intern("[]="), 2, id_str, oid_sym);
569
+ }
570
+ }
571
+ }
572
+
573
+ if(allow_id == 1) {
574
+ write_function = write_element_with_id;
575
+ }
576
+ else {
577
+ write_function = write_element_without_id;
578
+ }
579
+
580
+ // we have to check for an OrderedHash and handle that specially
581
+ if (strcmp(rb_obj_classname(hash), "BSON::OrderedHash") == 0) {
582
+ VALUE keys = rb_funcall(hash, rb_intern("keys"), 0);
583
+ int i;
584
+ for(i = 0; i < RARRAY_LEN(keys); i++) {
585
+ VALUE key = rb_ary_entry(keys, i);
586
+ VALUE value = rb_hash_aref(hash, key);
587
+
588
+ write_function(key, value, pack_extra(buffer, check_keys));
589
+ }
590
+ } else if (rb_obj_is_kind_of(hash, RB_HASH) == Qtrue) {
591
+ rb_hash_foreach(hash, write_function, pack_extra(buffer, check_keys));
592
+ } else {
593
+ bson_buffer_free(buffer);
594
+ rb_raise(InvalidDocument, "BSON.serialize takes a Hash but got a %s", rb_obj_classname(hash));
595
+ }
596
+
597
+ // write null byte and fill in length
598
+ SAFE_WRITE(buffer, &zero, 1);
599
+ length = buffer_get_position(buffer) - start_position;
600
+
601
+ // make sure that length doesn't exceed 4MB
602
+ if (length > max_bson_size) {
603
+ bson_buffer_free(buffer);
604
+ rb_raise(InvalidDocument, "Document too large: BSON documents are limited to %d bytes.", max_bson_size);
605
+ return;
606
+ }
607
+ SAFE_WRITE_AT_POS(buffer, length_location, (const char*)&length, 4);
608
+ }
609
+
610
+ static VALUE method_serialize(VALUE self, VALUE doc, VALUE check_keys, VALUE move_id) {
611
+ VALUE result;
612
+ buffer_t buffer = buffer_new();
613
+ if (buffer == NULL) {
614
+ rb_raise(rb_eNoMemError, "failed to allocate memory in buffer.c");
615
+ }
616
+
617
+ write_doc(buffer, doc, check_keys, move_id);
618
+
619
+ result = rb_str_new(buffer_get_buffer(buffer), buffer_get_position(buffer));
620
+ if (bson_buffer_free(buffer) != 0) {
621
+ rb_raise(rb_eRuntimeError, "failed to free buffer");
622
+ }
623
+ return result;
624
+ }
625
+
626
+ static VALUE get_value(const char* buffer, int* position, int type) {
627
+ VALUE value;
628
+ switch (type) {
629
+ case -1:
630
+ {
631
+ value = rb_class_new_instance(0, NULL, MinKey);
632
+ break;
633
+ }
634
+ case 1:
635
+ {
636
+ double d;
637
+ memcpy(&d, buffer + *position, 8);
638
+ value = rb_float_new(d);
639
+ *position += 8;
640
+ break;
641
+ }
642
+ case 2:
643
+ case 13:
644
+ {
645
+ int value_length;
646
+ value_length = *(int*)(buffer + *position) - 1;
647
+ *position += 4;
648
+ value = STR_NEW(buffer + *position, value_length);
649
+ *position += value_length + 1;
650
+ break;
651
+ }
652
+ case 3:
653
+ {
654
+ int size;
655
+ memcpy(&size, buffer + *position, 4);
656
+ if (strcmp(buffer + *position + 5, "$ref") == 0) { // DBRef
657
+ int offset = *position + 10;
658
+ VALUE argv[2];
659
+ int collection_length = *(int*)(buffer + offset) - 1;
660
+ char id_type;
661
+ offset += 4;
662
+
663
+ argv[0] = STR_NEW(buffer + offset, collection_length);
664
+ offset += collection_length + 1;
665
+ id_type = buffer[offset];
666
+ offset += 5;
667
+ argv[1] = get_value(buffer, &offset, (int)id_type);
668
+ value = rb_class_new_instance(2, argv, DBRef);
669
+ } else {
670
+ value = elements_to_hash(buffer + *position + 4, size - 5);
671
+ }
672
+ *position += size;
673
+ break;
674
+ }
675
+ case 4:
676
+ {
677
+ int size, end;
678
+ memcpy(&size, buffer + *position, 4);
679
+ end = *position + size - 1;
680
+ *position += 4;
681
+
682
+ value = rb_ary_new();
683
+ while (*position < end) {
684
+ int type = (int)buffer[(*position)++];
685
+ int key_size = (int)strlen(buffer + *position);
686
+ VALUE to_append;
687
+
688
+ *position += key_size + 1; // just skip the key, they're in order.
689
+ to_append = get_value(buffer, position, type);
690
+ rb_ary_push(value, to_append);
691
+ }
692
+ (*position)++;
693
+ break;
694
+ }
695
+ case 5:
696
+ {
697
+ int length, subtype;
698
+ VALUE data, st;
699
+ VALUE argv[2];
700
+ memcpy(&length, buffer + *position, 4);
701
+ subtype = (unsigned char)buffer[*position + 4];
702
+ if (subtype == 2) {
703
+ data = rb_str_new(buffer + *position + 9, length - 4);
704
+ } else {
705
+ data = rb_str_new(buffer + *position + 5, length);
706
+ }
707
+ st = INT2FIX(subtype);
708
+ argv[0] = data;
709
+ argv[1] = st;
710
+ value = rb_class_new_instance(2, argv, Binary);
711
+ *position += length + 5;
712
+ break;
713
+ }
714
+ case 6:
715
+ {
716
+ value = Qnil;
717
+ break;
718
+ }
719
+ case 7:
720
+ {
721
+ VALUE str = rb_str_new(buffer + *position, 12);
722
+ VALUE oid = rb_funcall(str, unpack_method, 1, rb_str_new2("C*"));
723
+ value = rb_class_new_instance(1, &oid, ObjectId);
724
+ *position += 12;
725
+ break;
726
+ }
727
+ case 8:
728
+ {
729
+ value = buffer[(*position)++] ? Qtrue : Qfalse;
730
+ break;
731
+ }
732
+ case 9:
733
+ {
734
+ long long millis;
735
+ memcpy(&millis, buffer + *position, 8);
736
+
737
+ value = rb_time_new(millis / 1000, (millis % 1000) * 1000);
738
+ value = rb_funcall(value, utc_method, 0);
739
+ *position += 8;
740
+ break;
741
+ }
742
+ case 10:
743
+ {
744
+ value = Qnil;
745
+ break;
746
+ }
747
+ case 11:
748
+ {
749
+ int pattern_length = (int)strlen(buffer + *position);
750
+ VALUE pattern = STR_NEW(buffer + *position, pattern_length);
751
+ int flags_length, flags = 0, i = 0;
752
+ VALUE argv[3];
753
+ *position += pattern_length + 1;
754
+
755
+ flags_length = (int)strlen(buffer + *position);
756
+ for (i = 0; i < flags_length; i++) {
757
+ char flag = buffer[*position + i];
758
+ if (flag == 'i') {
759
+ flags |= IGNORECASE;
760
+ }
761
+ else if (flag == 'm') {
762
+ flags |= MULTILINE;
763
+ }
764
+ else if (flag == 's') {
765
+ flags |= MULTILINE;
766
+ }
767
+ else if (flag == 'x') {
768
+ flags |= EXTENDED;
769
+ }
770
+ }
771
+ argv[0] = pattern;
772
+ argv[1] = INT2FIX(flags);
773
+ value = rb_class_new_instance(2, argv, Regexp);
774
+ *position += flags_length + 1;
775
+ break;
776
+ }
777
+ case 12:
778
+ {
779
+ int collection_length;
780
+ VALUE collection, str, oid, id, argv[2];
781
+ collection_length = *(int*)(buffer + *position) - 1;
782
+ *position += 4;
783
+ collection = STR_NEW(buffer + *position, collection_length);
784
+ *position += collection_length + 1;
785
+
786
+ str = rb_str_new(buffer + *position, 12);
787
+ oid = rb_funcall(str, unpack_method, 1, rb_str_new2("C*"));
788
+ id = rb_class_new_instance(1, &oid, ObjectId);
789
+ *position += 12;
790
+
791
+ argv[0] = collection;
792
+ argv[1] = id;
793
+ value = rb_class_new_instance(2, argv, DBRef);
794
+ break;
795
+ }
796
+ case 14:
797
+ {
798
+ int value_length;
799
+ memcpy(&value_length, buffer + *position, 4);
800
+ value = ID2SYM(rb_intern(buffer + *position + 4));
801
+ *position += value_length + 4;
802
+ break;
803
+ }
804
+ case 15:
805
+ {
806
+ int code_length, scope_size;
807
+ VALUE code, scope, argv[2];
808
+ *position += 4;
809
+ code_length = *(int*)(buffer + *position) - 1;
810
+ *position += 4;
811
+ code = STR_NEW(buffer + *position, code_length);
812
+ *position += code_length + 1;
813
+
814
+ memcpy(&scope_size, buffer + *position, 4);
815
+ scope = elements_to_hash(buffer + *position + 4, scope_size - 5);
816
+ *position += scope_size;
817
+
818
+ argv[0] = code;
819
+ argv[1] = scope;
820
+ value = rb_class_new_instance(2, argv, Code);
821
+ break;
822
+ }
823
+ case 16:
824
+ {
825
+ int i;
826
+ memcpy(&i, buffer + *position, 4);
827
+ value = LL2NUM(i);
828
+ *position += 4;
829
+ break;
830
+ }
831
+ case 17:
832
+ {
833
+ int sec, inc;
834
+ VALUE argv[2];
835
+ memcpy(&inc, buffer + *position, 4);
836
+ memcpy(&sec, buffer + *position + 4, 4);
837
+ argv[0] = INT2FIX(sec);
838
+ argv[1] = INT2FIX(inc);
839
+ value = rb_class_new_instance(2, argv, Timestamp);
840
+ *position += 8;
841
+ break;
842
+ }
843
+ case 18:
844
+ {
845
+ long long ll;
846
+ memcpy(&ll, buffer + *position, 8);
847
+ value = LL2NUM(ll);
848
+ *position += 8;
849
+ break;
850
+ }
851
+ case 127:
852
+ {
853
+ value = rb_class_new_instance(0, NULL, MaxKey);
854
+ break;
855
+ }
856
+ default:
857
+ {
858
+ rb_raise(rb_eTypeError, "no c decoder for this type yet (%d)", type);
859
+ break;
860
+ }
861
+ }
862
+ return value;
863
+ }
864
+
865
+ static VALUE elements_to_hash(const char* buffer, int max) {
866
+ VALUE hash = rb_class_new_instance(0, NULL, OrderedHash);
867
+ int position = 0;
868
+ while (position < max) {
869
+ int type = (int)buffer[position++];
870
+ int name_length = (int)strlen(buffer + position);
871
+ VALUE name = STR_NEW(buffer + position, name_length);
872
+ VALUE value;
873
+ position += name_length + 1;
874
+ value = get_value(buffer, &position, type);
875
+ rb_funcall(hash, element_assignment_method, 2, name, value);
876
+ }
877
+ return hash;
878
+ }
879
+
880
+ static VALUE method_deserialize(VALUE self, VALUE bson) {
881
+ const char* buffer = RSTRING_PTR(bson);
882
+ int remaining = RSTRING_LENINT(bson);
883
+
884
+ // NOTE we just swallow the size and end byte here
885
+ buffer += 4;
886
+ remaining -= 5;
887
+
888
+ return elements_to_hash(buffer, remaining);
889
+ }
890
+
891
+ static VALUE objectid_generate(int argc, VALUE* args, VALUE self)
892
+ {
893
+ VALUE oid;
894
+ unsigned char oid_bytes[12];
895
+ unsigned long t, inc;
896
+ unsigned short pid;
897
+ int i;
898
+
899
+ if(argc == 0 || (argc == 1 && *args == Qnil)) {
900
+ t = htonl((int)time(NULL));
901
+ } else {
902
+ t = htonl(FIX2INT(rb_funcall(*args, rb_intern("to_i"), 0)));
903
+ }
904
+ MEMCPY(&oid_bytes, &t, unsigned char, 4);
905
+
906
+ MEMCPY(&oid_bytes[4], hostname_digest, unsigned char, 3);
907
+
908
+ pid = htons(getpid());
909
+ MEMCPY(&oid_bytes[7], &pid, unsigned char, 2);
910
+
911
+ /* No need to synchronize modification of this counter between threads;
912
+ * MRI global interpreter lock guarantees serializaility.
913
+ *
914
+ * Compiler should optimize out impossible branch.
915
+ */
916
+ if (sizeof(unsigned int) == 4) {
917
+ object_id_inc++;
918
+ } else {
919
+ object_id_inc = (object_id_inc + 1) % 0xFFFFFF;
920
+ }
921
+ inc = htonl(object_id_inc);
922
+ MEMCPY(&oid_bytes[9], ((unsigned char*)&inc + 1), unsigned char, 3);
923
+
924
+ oid = rb_ary_new2(12);
925
+ for(i = 0; i < 12; i++) {
926
+ rb_ary_store(oid, i, INT2FIX((unsigned int)oid_bytes[i]));
927
+ }
928
+ return oid;
929
+ }
930
+
931
+ static VALUE method_update_max_bson_size(VALUE self, VALUE connection) {
932
+ max_bson_size = FIX2INT(rb_funcall(connection, rb_intern("max_bson_size"), 0));
933
+ return INT2FIX(max_bson_size);
934
+ }
935
+
936
+ static VALUE method_max_bson_size(VALUE self) {
937
+ return INT2FIX(max_bson_size);
938
+ }
939
+
940
+ void Init_cbson() {
941
+ VALUE bson, CBson, Digest, ext_version, digest;
942
+ static char hostname[MAX_HOSTNAME_LENGTH];
943
+
944
+ element_assignment_method = rb_intern("[]=");
945
+ unpack_method = rb_intern("unpack");
946
+ utc_method = rb_intern("utc");
947
+ lt_operator = rb_intern("<");
948
+ gt_operator = rb_intern(">");
949
+
950
+ bson = rb_const_get(rb_cObject, rb_intern("BSON"));
951
+ rb_require("bson/types/binary");
952
+ Binary = rb_const_get(bson, rb_intern("Binary"));
953
+ rb_require("bson/types/object_id");
954
+ ObjectId = rb_const_get(bson, rb_intern("ObjectId"));
955
+ rb_require("bson/types/dbref");
956
+ DBRef = rb_const_get(bson, rb_intern("DBRef"));
957
+ rb_require("bson/types/code");
958
+ Code = rb_const_get(bson, rb_intern("Code"));
959
+ rb_require("bson/types/min_max_keys");
960
+ MinKey = rb_const_get(bson, rb_intern("MinKey"));
961
+ MaxKey = rb_const_get(bson, rb_intern("MaxKey"));
962
+ rb_require("bson/types/timestamp");
963
+ Timestamp = rb_const_get(bson, rb_intern("Timestamp"));
964
+ Regexp = rb_const_get(rb_cObject, rb_intern("Regexp"));
965
+ rb_require("bson/exceptions");
966
+ InvalidKeyName = rb_const_get(bson, rb_intern("InvalidKeyName"));
967
+ InvalidStringEncoding = rb_const_get(bson, rb_intern("InvalidStringEncoding"));
968
+ InvalidDocument = rb_const_get(bson, rb_intern("InvalidDocument"));
969
+ rb_require("bson/ordered_hash");
970
+ OrderedHash = rb_const_get(bson, rb_intern("OrderedHash"));
971
+ RB_HASH = rb_const_get(bson, rb_intern("Hash"));
972
+
973
+ CBson = rb_define_module("CBson");
974
+ ext_version = rb_str_new2(VERSION);
975
+ rb_define_const(CBson, "VERSION", ext_version);
976
+ rb_define_module_function(CBson, "serialize", method_serialize, 3);
977
+ rb_define_module_function(CBson, "deserialize", method_deserialize, 1);
978
+ rb_define_module_function(CBson, "max_bson_size", method_max_bson_size, 0);
979
+ rb_define_module_function(CBson, "update_max_bson_size", method_update_max_bson_size, 1);
980
+
981
+ rb_require("digest/md5");
982
+ Digest = rb_const_get(rb_cObject, rb_intern("Digest"));
983
+ DigestMD5 = rb_const_get(Digest, rb_intern("MD5"));
984
+
985
+ rb_define_method(ObjectId, "generate", objectid_generate, -1);
986
+
987
+ if (gethostname(hostname, MAX_HOSTNAME_LENGTH) != 0) {
988
+ rb_raise(rb_eRuntimeError, "failed to get hostname");
989
+ }
990
+ digest = rb_funcall(DigestMD5, rb_intern("digest"), 1,
991
+ rb_str_new2(hostname));
992
+ memcpy(hostname_digest, RSTRING_PTR(digest), 16);
993
+ hostname_digest[16] = '\0';
994
+
995
+ max_bson_size = 4 * 1024 * 1024;
996
+ }