maxmind-db-rust 0.4.0 → 0.6.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +113 -0
- data/README.md +114 -18
- data/ext/maxmind_db_rust/Cargo.toml +1 -1
- data/ext/maxmind_db_rust/extconf.rb +1 -1
- data/ext/maxmind_db_rust/lib/maxmind/db/rust.rb +1 -1
- data/ext/maxmind_db_rust/src/decoder.rs +324 -0
- data/ext/maxmind_db_rust/src/initialization.rs +75 -0
- data/ext/maxmind_db_rust/src/lib.rs +12 -1084
- data/ext/maxmind_db_rust/src/metadata.rs +123 -0
- data/ext/maxmind_db_rust/src/path.rs +157 -0
- data/ext/maxmind_db_rust/src/reader.rs +940 -0
- data/lib/maxmind/db/rust.rb +2 -2
- metadata +21 -2
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
use crate::{
|
|
2
|
+
rust_module, STRING_CACHE_MAX, STRING_CACHE_MAX_LEN, STRING_CACHE_MIN_LEN,
|
|
3
|
+
STRING_CACHE_ROOTS_CONST,
|
|
4
|
+
};
|
|
5
|
+
use ::maxminddb as maxminddb_crate;
|
|
6
|
+
use magnus::{prelude::*, IntoValue, RArray, RString, Value};
|
|
7
|
+
use rustc_hash::FxHasher;
|
|
8
|
+
use serde::de::{self, Deserialize, DeserializeSeed, Deserializer, MapAccess, SeqAccess, Visitor};
|
|
9
|
+
use std::{
|
|
10
|
+
cell::OnceCell,
|
|
11
|
+
fmt,
|
|
12
|
+
hash::{Hash, Hasher},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
thread_local! {
|
|
16
|
+
static STRING_CACHE_ROOTS: OnceCell<RArray> = const { OnceCell::new() };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
#[inline]
|
|
20
|
+
fn global_string_cache_roots(ruby: &magnus::Ruby) -> RArray {
|
|
21
|
+
let value = rust_module(ruby)
|
|
22
|
+
.const_get::<_, Value>(STRING_CACHE_ROOTS_CONST)
|
|
23
|
+
.expect("string cache roots constant should exist");
|
|
24
|
+
RArray::from_value(value).expect("string cache roots constant should be an array")
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
#[inline]
|
|
28
|
+
fn string_cache_roots(ruby: &magnus::Ruby) -> RArray {
|
|
29
|
+
STRING_CACHE_ROOTS.with(|roots| *roots.get_or_init(|| global_string_cache_roots(ruby)))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
#[inline]
|
|
33
|
+
fn cached_utf8_string(ruby: &magnus::Ruby, value: &[u8]) -> Value {
|
|
34
|
+
if !(STRING_CACHE_MIN_LEN..=STRING_CACHE_MAX_LEN).contains(&value.len()) {
|
|
35
|
+
let string = ruby.enc_str_new(value, ruby.utf8_encoding());
|
|
36
|
+
string.freeze();
|
|
37
|
+
return string.into_value_with(ruby);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let mut hasher = FxHasher::default();
|
|
41
|
+
value.hash(&mut hasher);
|
|
42
|
+
let hash = hasher.finish();
|
|
43
|
+
let slot = (hash as usize) & (STRING_CACHE_MAX - 1);
|
|
44
|
+
|
|
45
|
+
let roots = string_cache_roots(ruby);
|
|
46
|
+
let cached = roots
|
|
47
|
+
.entry::<Value>(slot as isize)
|
|
48
|
+
.expect("string cache roots lookup should succeed");
|
|
49
|
+
if let Some(cached) = RString::from_value(cached) {
|
|
50
|
+
// SAFETY: the bytes are compared immediately while the globally rooted
|
|
51
|
+
// frozen Ruby string remains alive and cannot be mutated.
|
|
52
|
+
if unsafe { cached.as_slice() } == value {
|
|
53
|
+
return cached.into_value_with(ruby);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let string = ruby.enc_str_new(value, ruby.utf8_encoding());
|
|
58
|
+
string.freeze();
|
|
59
|
+
let cached = string.as_value();
|
|
60
|
+
roots
|
|
61
|
+
.store(slot as isize, cached)
|
|
62
|
+
.expect("string cache roots update should succeed");
|
|
63
|
+
cached
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/// Wrapper that owns the Ruby value produced by deserializing a MaxMind record.
|
|
67
|
+
#[derive(Clone)]
|
|
68
|
+
pub(crate) struct RubyDecodedValue {
|
|
69
|
+
value: Value,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
impl RubyDecodedValue {
|
|
73
|
+
#[inline]
|
|
74
|
+
fn new(value: Value) -> Self {
|
|
75
|
+
Self { value }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
#[inline]
|
|
79
|
+
pub(crate) fn into_value(self) -> Value {
|
|
80
|
+
self.value
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
impl<'de> Deserialize<'de> for RubyDecodedValue {
|
|
85
|
+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
86
|
+
where
|
|
87
|
+
D: Deserializer<'de>,
|
|
88
|
+
{
|
|
89
|
+
let ruby = magnus::Ruby::get().expect("Ruby VM should be available in deserializer");
|
|
90
|
+
RubyValueSeed { ruby: &ruby }.deserialize(deserializer)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
struct RubyValueSeed<'ruby> {
|
|
95
|
+
ruby: &'ruby magnus::Ruby,
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
impl<'ruby, 'de> DeserializeSeed<'de> for RubyValueSeed<'ruby> {
|
|
99
|
+
type Value = RubyDecodedValue;
|
|
100
|
+
|
|
101
|
+
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
|
|
102
|
+
where
|
|
103
|
+
D: Deserializer<'de>,
|
|
104
|
+
{
|
|
105
|
+
maxminddb_crate::deserialize_any_with_raw_strings(
|
|
106
|
+
deserializer,
|
|
107
|
+
RubyValueVisitor { ruby: self.ruby },
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
struct RubyValueVisitor<'ruby> {
|
|
113
|
+
ruby: &'ruby magnus::Ruby,
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
impl<'de, 'ruby> Visitor<'de> for RubyValueVisitor<'ruby> {
|
|
117
|
+
type Value = RubyDecodedValue;
|
|
118
|
+
|
|
119
|
+
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
|
120
|
+
formatter.write_str("any valid MaxMind DB value")
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E>
|
|
124
|
+
where
|
|
125
|
+
E: de::Error,
|
|
126
|
+
{
|
|
127
|
+
Ok(RubyDecodedValue::new(value.into_value_with(self.ruby)))
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
fn visit_i32<E>(self, value: i32) -> Result<Self::Value, E>
|
|
131
|
+
where
|
|
132
|
+
E: de::Error,
|
|
133
|
+
{
|
|
134
|
+
Ok(RubyDecodedValue::new(value.into_value_with(self.ruby)))
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
|
|
138
|
+
where
|
|
139
|
+
E: de::Error,
|
|
140
|
+
{
|
|
141
|
+
if value >= i32::MIN as i64 && value <= i32::MAX as i64 {
|
|
142
|
+
Ok(RubyDecodedValue::new(
|
|
143
|
+
(value as i32).into_value_with(self.ruby),
|
|
144
|
+
))
|
|
145
|
+
} else {
|
|
146
|
+
Ok(RubyDecodedValue::new(value.into_value_with(self.ruby)))
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
fn visit_u16<E>(self, value: u16) -> Result<Self::Value, E>
|
|
151
|
+
where
|
|
152
|
+
E: de::Error,
|
|
153
|
+
{
|
|
154
|
+
Ok(RubyDecodedValue::new(value.into_value_with(self.ruby)))
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
fn visit_u32<E>(self, value: u32) -> Result<Self::Value, E>
|
|
158
|
+
where
|
|
159
|
+
E: de::Error,
|
|
160
|
+
{
|
|
161
|
+
Ok(RubyDecodedValue::new(value.into_value_with(self.ruby)))
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
|
|
165
|
+
where
|
|
166
|
+
E: de::Error,
|
|
167
|
+
{
|
|
168
|
+
Ok(RubyDecodedValue::new(value.into_value_with(self.ruby)))
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
fn visit_u128<E>(self, value: u128) -> Result<Self::Value, E>
|
|
172
|
+
where
|
|
173
|
+
E: de::Error,
|
|
174
|
+
{
|
|
175
|
+
Ok(RubyDecodedValue::new(value.into_value_with(self.ruby)))
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
fn visit_f32<E>(self, value: f32) -> Result<Self::Value, E>
|
|
179
|
+
where
|
|
180
|
+
E: de::Error,
|
|
181
|
+
{
|
|
182
|
+
Ok(RubyDecodedValue::new(
|
|
183
|
+
(value as f64).into_value_with(self.ruby),
|
|
184
|
+
))
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
fn visit_f64<E>(self, value: f64) -> Result<Self::Value, E>
|
|
188
|
+
where
|
|
189
|
+
E: de::Error,
|
|
190
|
+
{
|
|
191
|
+
Ok(RubyDecodedValue::new(value.into_value_with(self.ruby)))
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
fn visit_newtype_struct<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
|
|
195
|
+
where
|
|
196
|
+
D: Deserializer<'de>,
|
|
197
|
+
{
|
|
198
|
+
deserializer
|
|
199
|
+
.deserialize_bytes(RubyUtf8StringVisitor { ruby: self.ruby })
|
|
200
|
+
.map(RubyDecodedValue::new)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
|
204
|
+
where
|
|
205
|
+
E: de::Error,
|
|
206
|
+
{
|
|
207
|
+
Ok(RubyDecodedValue::new(cached_utf8_string(
|
|
208
|
+
self.ruby,
|
|
209
|
+
value.as_bytes(),
|
|
210
|
+
)))
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
|
|
214
|
+
where
|
|
215
|
+
E: de::Error,
|
|
216
|
+
{
|
|
217
|
+
Ok(RubyDecodedValue::new(cached_utf8_string(
|
|
218
|
+
self.ruby,
|
|
219
|
+
value.as_bytes(),
|
|
220
|
+
)))
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
fn visit_bytes<E>(self, value: &[u8]) -> Result<Self::Value, E>
|
|
224
|
+
where
|
|
225
|
+
E: de::Error,
|
|
226
|
+
{
|
|
227
|
+
Ok(RubyDecodedValue::new(
|
|
228
|
+
self.ruby.str_from_slice(value).into_value_with(self.ruby),
|
|
229
|
+
))
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
fn visit_byte_buf<E>(self, value: Vec<u8>) -> Result<Self::Value, E>
|
|
233
|
+
where
|
|
234
|
+
E: de::Error,
|
|
235
|
+
{
|
|
236
|
+
Ok(RubyDecodedValue::new(
|
|
237
|
+
self.ruby.str_from_slice(&value).into_value_with(self.ruby),
|
|
238
|
+
))
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
|
242
|
+
where
|
|
243
|
+
A: SeqAccess<'de>,
|
|
244
|
+
{
|
|
245
|
+
let arr = match seq.size_hint() {
|
|
246
|
+
Some(cap) => self.ruby.ary_new_capa(cap),
|
|
247
|
+
None => self.ruby.ary_new(),
|
|
248
|
+
};
|
|
249
|
+
let mut buffer = [self.ruby.qnil().as_value(); 128];
|
|
250
|
+
let mut buffer_len = 0;
|
|
251
|
+
while let Some(elem) = seq.next_element_seed(RubyValueSeed { ruby: self.ruby })? {
|
|
252
|
+
buffer[buffer_len] = elem.into_value();
|
|
253
|
+
buffer_len += 1;
|
|
254
|
+
if buffer_len == buffer.len() {
|
|
255
|
+
arr.cat(&buffer)
|
|
256
|
+
.map_err(|e| de::Error::custom(e.to_string()))?;
|
|
257
|
+
buffer_len = 0;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
arr.cat(&buffer[..buffer_len])
|
|
261
|
+
.map_err(|e| de::Error::custom(e.to_string()))?;
|
|
262
|
+
Ok(RubyDecodedValue::new(arr.into_value_with(self.ruby)))
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
|
|
266
|
+
where
|
|
267
|
+
A: MapAccess<'de>,
|
|
268
|
+
{
|
|
269
|
+
let hash = match map.size_hint() {
|
|
270
|
+
Some(cap) => self.ruby.hash_new_capa(cap),
|
|
271
|
+
None => self.ruby.hash_new(),
|
|
272
|
+
};
|
|
273
|
+
let mut buffer = [self.ruby.qnil().as_value(); 128];
|
|
274
|
+
let mut buffer_len = 0;
|
|
275
|
+
while let Some(key_val) = map.next_key_seed(RubyMapKeySeed { ruby: self.ruby })? {
|
|
276
|
+
let value = map.next_value_seed(RubyValueSeed { ruby: self.ruby })?;
|
|
277
|
+
buffer[buffer_len] = key_val;
|
|
278
|
+
buffer[buffer_len + 1] = value.into_value();
|
|
279
|
+
buffer_len += 2;
|
|
280
|
+
if buffer_len == buffer.len() {
|
|
281
|
+
hash.bulk_insert(&buffer)
|
|
282
|
+
.map_err(|e| de::Error::custom(e.to_string()))?;
|
|
283
|
+
buffer_len = 0;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
hash.bulk_insert(&buffer[..buffer_len])
|
|
287
|
+
.map_err(|e| de::Error::custom(e.to_string()))?;
|
|
288
|
+
Ok(RubyDecodedValue::new(hash.into_value_with(self.ruby)))
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
struct RubyUtf8StringVisitor<'ruby> {
|
|
293
|
+
ruby: &'ruby magnus::Ruby,
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
impl<'de, 'ruby> Visitor<'de> for RubyUtf8StringVisitor<'ruby> {
|
|
297
|
+
type Value = Value;
|
|
298
|
+
|
|
299
|
+
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
|
300
|
+
formatter.write_str("MMDB UTF-8 string bytes")
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
fn visit_borrowed_bytes<E>(self, value: &'de [u8]) -> Result<Self::Value, E>
|
|
304
|
+
where
|
|
305
|
+
E: de::Error,
|
|
306
|
+
{
|
|
307
|
+
Ok(cached_utf8_string(self.ruby, value))
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
struct RubyMapKeySeed<'ruby> {
|
|
312
|
+
ruby: &'ruby magnus::Ruby,
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
impl<'ruby, 'de> DeserializeSeed<'de> for RubyMapKeySeed<'ruby> {
|
|
316
|
+
type Value = Value;
|
|
317
|
+
|
|
318
|
+
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
|
|
319
|
+
where
|
|
320
|
+
D: Deserializer<'de>,
|
|
321
|
+
{
|
|
322
|
+
deserializer.deserialize_identifier(RubyUtf8StringVisitor { ruby: self.ruby })
|
|
323
|
+
}
|
|
324
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
use crate::{metadata, reader, STRING_CACHE_MAX, STRING_CACHE_ROOTS_CONST};
|
|
2
|
+
use magnus::{error::Error, prelude::*, RModule, Value};
|
|
3
|
+
|
|
4
|
+
const MAP_KEY_ROOTS_CONST: &str = "__MAP_KEY_ROOTS__";
|
|
5
|
+
|
|
6
|
+
pub(crate) fn initialize(ruby: &magnus::Ruby) -> Result<(), Error> {
|
|
7
|
+
// Define module hierarchy: MaxMind::DB::Rust
|
|
8
|
+
// Handle case where official maxmind-db gem may have already defined MaxMind::DB as a Class
|
|
9
|
+
let maxmind = ruby.define_module("MaxMind")?;
|
|
10
|
+
|
|
11
|
+
// Try to get or define DB - it might be a Class (official gem) or Module (ours)
|
|
12
|
+
let db_value = maxmind.const_get::<_, Value>("DB");
|
|
13
|
+
let rust = match db_value {
|
|
14
|
+
Ok(existing) if existing.is_kind_of(ruby.class_class()) => {
|
|
15
|
+
// MaxMind::DB exists as a Class (official gem loaded first)
|
|
16
|
+
// Reuse existing Rust constant if present to avoid replacing classes.
|
|
17
|
+
if let Ok(rust_value) = existing.funcall::<_, _, Value>("const_get", ("Rust", false)) {
|
|
18
|
+
RModule::from_value(rust_value).ok_or_else(|| {
|
|
19
|
+
Error::new(
|
|
20
|
+
ruby.exception_type_error(),
|
|
21
|
+
"MaxMind::DB::Rust exists but is not a module",
|
|
22
|
+
)
|
|
23
|
+
})?
|
|
24
|
+
} else {
|
|
25
|
+
// Define Rust module directly as a constant on the class.
|
|
26
|
+
let rust_value: Value = ruby.module_new().as_value();
|
|
27
|
+
let rust_mod = RModule::from_value(rust_value).ok_or_else(|| {
|
|
28
|
+
Error::new(
|
|
29
|
+
ruby.exception_type_error(),
|
|
30
|
+
"Failed to create anonymous module for MaxMind::DB::Rust",
|
|
31
|
+
)
|
|
32
|
+
})?;
|
|
33
|
+
let _ = existing.funcall::<_, _, Value>("const_set", ("Rust", rust_mod))?;
|
|
34
|
+
rust_mod
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
Ok(existing) => {
|
|
38
|
+
// MaxMind::DB exists as a Module (our gem loaded first)
|
|
39
|
+
let db_mod = RModule::from_value(existing).ok_or_else(|| {
|
|
40
|
+
Error::new(ruby.exception_type_error(), "MaxMind::DB is not a module")
|
|
41
|
+
})?;
|
|
42
|
+
db_mod.define_module("Rust")?
|
|
43
|
+
}
|
|
44
|
+
Err(_) => {
|
|
45
|
+
// MaxMind::DB doesn't exist, define it as a module
|
|
46
|
+
let db = maxmind.define_module("DB")?;
|
|
47
|
+
db.define_module("Rust")?
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
if rust
|
|
52
|
+
.const_get::<_, Value>(STRING_CACHE_ROOTS_CONST)
|
|
53
|
+
.is_err()
|
|
54
|
+
{
|
|
55
|
+
let roots = ruby.ary_new_capa(STRING_CACHE_MAX);
|
|
56
|
+
for _ in 0..STRING_CACHE_MAX {
|
|
57
|
+
roots.push(ruby.qnil().as_value())?;
|
|
58
|
+
}
|
|
59
|
+
rust.const_set(STRING_CACHE_ROOTS_CONST, roots)?;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if rust.const_get::<_, Value>(MAP_KEY_ROOTS_CONST).is_ok() {
|
|
63
|
+
let _ = rust.funcall::<_, _, Value>("send", ("remove_const", MAP_KEY_ROOTS_CONST))?;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// The extension can be loaded more than once from different paths.
|
|
67
|
+
// Reusing previously defined classes avoids typed-data incompatibilities.
|
|
68
|
+
if rust.const_get::<_, Value>("Reader").is_ok() {
|
|
69
|
+
return Ok(());
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
reader::define(ruby, rust)?;
|
|
73
|
+
metadata::define(ruby, rust)?;
|
|
74
|
+
Ok(())
|
|
75
|
+
}
|