osv 0.3.15 → 0.3.16

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 872cf06d1389f45f77b4eefc178cc8462ab165b833ab2c5bf4dc7f92e1c8308e
4
- data.tar.gz: 84e6c5d0e03389966b8882a5a73f1698ddee3ed0edae24f2fd5b7f257935a98e
3
+ metadata.gz: 91401989a8532162a9731fed3cb07661c0676105f77465da23f9a267773e7651
4
+ data.tar.gz: aeba48f1338a4160044e8c7264f80eb065d950567288bded39acf5d9bc593d7b
5
5
  SHA512:
6
- metadata.gz: 445581447e8f5ec336da7843af715a5f5fbc298232a24f303a22eebb844f83f65ecc2e85d877a448119adae9e6a5529e377d87399a36e6f070562fa4ce0a11b7
7
- data.tar.gz: '08f417b19b0549aa4a3db1538e4be413c5ec8faa3bd18e4c101a6fc3ea3e9496d04c30e39ea8eec9cc0cc3a38f8f83f7c2274e09c75259a26f3609620cf07a80'
6
+ metadata.gz: 8d2ea3f724a6f7af317bb1ae865513c15f2ef0e475b070e7f9ae2e1b4155b2d82090387beb0c6a2e5cb8664b1f6dd0cf61e6ad9545957bc3ada1a3e87758b1ee
7
+ data.tar.gz: 0eaa86241092c14f4c2973d74e65877b7f3f87487a2681b9a094054f98db759772bcf012ec2f4fa073bd16f2b02927212b13afec484f84daf764d3b3e0811b6b
data/Cargo.lock CHANGED
@@ -45,7 +45,7 @@ dependencies = [
45
45
  "bitflags",
46
46
  "cexpr",
47
47
  "clang-sys",
48
- "itertools",
48
+ "itertools 0.12.1",
49
49
  "lazy_static",
50
50
  "lazycell",
51
51
  "proc-macro2",
@@ -175,6 +175,15 @@ dependencies = [
175
175
  "either",
176
176
  ]
177
177
 
178
+ [[package]]
179
+ name = "itertools"
180
+ version = "0.14.0"
181
+ source = "registry+https://github.com/rust-lang/crates.io-index"
182
+ checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
183
+ dependencies = [
184
+ "either",
185
+ ]
186
+
178
187
  [[package]]
179
188
  name = "itoa"
180
189
  version = "1.0.14"
@@ -347,6 +356,7 @@ dependencies = [
347
356
  "ahash",
348
357
  "csv",
349
358
  "flate2",
359
+ "itertools 0.14.0",
350
360
  "jemallocator",
351
361
  "kanal",
352
362
  "magnus 0.7.1",
data/README.md CHANGED
@@ -121,7 +121,7 @@ Here's some unscientific benchmarks. You can find the code in the [benchmark/com
121
121
  ### 1,000,000 records
122
122
 
123
123
  ```
124
- 🏃 Running benchmarks...
124
+ 🏃 Running benchmarks...
125
125
  Benchmarking with 3000001 lines of data
126
126
 
127
127
  ruby 3.3.6 (2024-11-05 revision 75015d4c1f) +YJIT [arm64-darwin24]
@@ -142,34 +142,34 @@ OSV - Gzipped Direct 1.000 i/100ms
142
142
  FastCSV - Gzipped 1.000 i/100ms
143
143
  CSV - Gzipped 1.000 i/100ms
144
144
  Calculating -------------------------------------
145
- CSV - StringIO 0.079 (± 0.0%) i/s (12.69 s/i) - 3.000 in 38.139709s
146
- FastCSV - StringIO 0.370 (± 0.0%) i/s (2.71 s/i) - 12.000 in 32.474164s
147
- OSV - StringIO 0.635 (± 0.0%) i/s (1.58 s/i) - 19.000 in 30.772490s
148
- CSV - Hash output 0.058 (± 0.0%) i/s (17.11 s/i) - 2.000 in 34.212335s
149
- OSV - Hash output 0.249 (± 0.0%) i/s (4.01 s/i) - 8.000 in 32.124319s
150
- CSV - Array output 0.066 (± 0.0%) i/s (15.11 s/i) - 2.000 in 30.212137s
151
- OSV - Array output 0.665 (± 0.0%) i/s (1.50 s/i) - 20.000 in 30.813986s
145
+ CSV - StringIO 0.080 (± 0.0%) i/s (12.43 s/i) - 3.000 in 37.301114s
146
+ FastCSV - StringIO 0.368 (± 0.0%) i/s (2.72 s/i) - 12.000 in 32.619020s
147
+ OSV - StringIO 0.699 (± 0.0%) i/s (1.43 s/i) - 21.000 in 30.091225s
148
+ CSV - Hash output 0.059 (± 0.0%) i/s (16.95 s/i) - 2.000 in 33.908533s
149
+ OSV - Hash output 0.329 (± 0.0%) i/s (3.04 s/i) - 10.000 in 30.551275s
150
+ CSV - Array output 0.066 (± 0.0%) i/s (15.18 s/i) - 2.000 in 30.357327s
151
+ OSV - Array output 0.632 (± 0.0%) i/s (1.58 s/i) - 19.000 in 30.150113s
152
152
  FastCSV - Array output
153
- 0.351 (± 0.0%) i/s (2.85 s/i) - 11.000 in 31.418786s
153
+ 0.350 (± 0.0%) i/s (2.86 s/i) - 11.000 in 31.477268s
154
154
  OSV - Direct Open Array output
155
- 0.713 (± 0.0%) i/s (1.40 s/i) - 22.000 in 30.938525s
156
- OSV - Gzipped 0.506 (± 0.0%) i/s (1.98 s/i) - 16.000 in 31.709708s
157
- OSV - Gzipped Direct 0.685 (± 0.0%) i/s (1.46 s/i) - 21.000 in 31.145435s
158
- FastCSV - Gzipped 0.324 (± 0.0%) i/s (3.09 s/i) - 10.000 in 30.983582s
159
- CSV - Gzipped 0.057 (± 0.0%) i/s (17.69 s/i) - 2.000 in 35.379009s
155
+ 0.641 (± 0.0%) i/s (1.56 s/i) - 20.000 in 31.275201s
156
+ OSV - Gzipped 0.530 (± 0.0%) i/s (1.89 s/i) - 16.000 in 30.183753s
157
+ OSV - Gzipped Direct 0.727 (± 0.0%) i/s (1.37 s/i) - 22.000 in 30.283991s
158
+ FastCSV - Gzipped 0.323 (± 0.0%) i/s (3.09 s/i) - 10.000 in 30.949600s
159
+ CSV - Gzipped 0.056 (± 0.0%) i/s (17.72 s/i) - 2.000 in 35.440473s
160
160
 
161
161
  Comparison:
162
- OSV - Direct Open Array output: 0.7 i/s
163
- OSV - Gzipped Direct: 0.7 i/s - 1.04x slower
164
- OSV - Array output: 0.7 i/s - 1.07x slower
165
- OSV - StringIO: 0.6 i/s - 1.12x slower
166
- OSV - Gzipped: 0.5 i/s - 1.41x slower
167
- FastCSV - StringIO: 0.4 i/s - 1.93x slower
168
- FastCSV - Array output: 0.4 i/s - 2.03x slower
169
- FastCSV - Gzipped: 0.3 i/s - 2.20x slower
170
- OSV - Hash output: 0.2 i/s - 2.86x slower
171
- CSV - StringIO: 0.1 i/s - 9.05x slower
172
- CSV - Array output: 0.1 i/s - 10.77x slower
173
- CSV - Hash output: 0.1 i/s - 12.20x slower
174
- CSV - Gzipped: 0.1 i/s - 12.61x slower
162
+ OSV - Gzipped Direct: 0.7 i/s
163
+ OSV - StringIO: 0.7 i/s - 1.04x slower
164
+ OSV - Direct Open Array output: 0.6 i/s - 1.14x slower
165
+ OSV - Array output: 0.6 i/s - 1.15x slower
166
+ OSV - Gzipped: 0.5 i/s - 1.37x slower
167
+ FastCSV - StringIO: 0.4 i/s - 1.98x slower
168
+ FastCSV - Array output: 0.3 i/s - 2.08x slower
169
+ OSV - Hash output: 0.3 i/s - 2.21x slower
170
+ FastCSV - Gzipped: 0.3 i/s - 2.25x slower
171
+ CSV - StringIO: 0.1 i/s - 9.04x slower
172
+ CSV - Array output: 0.1 i/s - 11.04x slower
173
+ CSV - Hash output: 0.1 i/s - 12.33x slower
174
+ CSV - Gzipped: 0.1 i/s - 12.89x slower
175
175
  ```
data/ext/osv/Cargo.toml CHANGED
@@ -16,6 +16,7 @@ rb-sys = "^0.9"
16
16
  serde = { version = "1.0", features = ["derive"] }
17
17
  serde_magnus = "0.8.1"
18
18
  thiserror = "2.0"
19
+ itertools = "^0.14"
19
20
 
20
21
  [target.'cfg(target_os = "linux")'.dependencies]
21
22
  jemallocator = { version = "0.5", features = ["disable_initial_exec_tls"] }
@@ -1,3 +1,4 @@
1
+ use magnus::{r_string::FString, value::Opaque, IntoValue, RString, Ruby, Value};
1
2
  /// This module exists to avoid cloning header keys in returned HashMaps.
2
3
  /// Since the underlying RString creation already involves cloning,
3
4
  /// this caching layer aims to reduce redundant allocations.
@@ -6,7 +7,7 @@
6
7
  /// so this optimization could be removed if any issues arise.
7
8
  use std::{
8
9
  collections::HashMap,
9
- sync::{atomic::AtomicU32, LazyLock, Mutex},
10
+ sync::{atomic::AtomicU32, atomic::Ordering, LazyLock, Mutex},
10
11
  };
11
12
  use thiserror::Error;
12
13
 
@@ -16,64 +17,116 @@ pub enum CacheError {
16
17
  LockError(String),
17
18
  }
18
19
 
19
- static STRING_CACHE: LazyLock<Mutex<HashMap<&'static str, AtomicU32>>> =
20
+ static STRING_CACHE: LazyLock<Mutex<HashMap<&'static str, (StringCacheKey, AtomicU32)>>> =
20
21
  LazyLock::new(|| Mutex::new(HashMap::with_capacity(100)));
21
22
 
22
23
  pub struct StringCache;
23
24
 
25
+ #[derive(Copy, Clone)]
26
+ pub struct StringCacheKey(Opaque<FString>, &'static str);
27
+
28
+ impl StringCacheKey {
29
+ pub fn new(string: &str) -> Self {
30
+ let rstr = RString::new(string);
31
+ let fstr = rstr.to_interned_str();
32
+ Self(Opaque::from(fstr), fstr.as_str().unwrap())
33
+ }
34
+ }
35
+
36
+ impl AsRef<str> for StringCacheKey {
37
+ fn as_ref(&self) -> &'static str {
38
+ self.1
39
+ }
40
+ }
41
+
42
+ impl IntoValue for StringCacheKey {
43
+ fn into_value_with(self, handle: &Ruby) -> Value {
44
+ handle.into_value(self.0)
45
+ }
46
+ }
47
+
48
+ impl std::fmt::Debug for StringCacheKey {
49
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50
+ self.1.fmt(f)
51
+ }
52
+ }
53
+
54
+ impl PartialEq for StringCacheKey {
55
+ fn eq(&self, other: &Self) -> bool {
56
+ self.1 == other.1
57
+ }
58
+ }
59
+
60
+ impl std::cmp::Eq for StringCacheKey {}
61
+
62
+ impl std::hash::Hash for StringCacheKey {
63
+ fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
64
+ self.1.hash(state);
65
+ }
66
+ }
67
+
24
68
  impl StringCache {
25
69
  #[allow(dead_code)]
26
- pub fn intern(string: String) -> Result<&'static str, CacheError> {
70
+ pub fn intern(string: String) -> Result<StringCacheKey, CacheError> {
27
71
  let mut cache = STRING_CACHE
28
72
  .lock()
29
73
  .map_err(|e| CacheError::LockError(e.to_string()))?;
30
74
 
31
- if let Some((&existing, count)) = cache.get_key_value(string.as_str()) {
32
- count.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
33
- Ok(existing)
75
+ if let Some((_, (interned_string, counter))) = cache.get_key_value(string.as_str()) {
76
+ counter.fetch_add(1, Ordering::Relaxed);
77
+ Ok(*interned_string)
34
78
  } else {
79
+ let interned = StringCacheKey::new(string.as_str());
35
80
  let leaked = Box::leak(string.into_boxed_str());
36
- cache.insert(leaked, AtomicU32::new(1));
37
- Ok(leaked)
81
+ cache.insert(leaked, (interned, AtomicU32::new(1)));
82
+ Ok(interned)
38
83
  }
39
84
  }
40
85
 
41
- pub fn intern_many(strings: &[String]) -> Result<Vec<&'static str>, CacheError> {
86
+ pub fn intern_many(strings: &[String]) -> Result<Vec<StringCacheKey>, CacheError> {
42
87
  let mut cache = STRING_CACHE
43
88
  .lock()
44
89
  .map_err(|e| CacheError::LockError(e.to_string()))?;
45
90
 
46
- let mut result = Vec::with_capacity(strings.len());
91
+ let mut result: Vec<StringCacheKey> = Vec::with_capacity(strings.len());
47
92
  for string in strings {
48
- if let Some((&existing, count)) = cache.get_key_value(string.as_str()) {
49
- count.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
50
- result.push(existing);
93
+ if let Some((_, (interned_string, counter))) = cache.get_key_value(string.as_str()) {
94
+ counter.fetch_add(1, Ordering::Relaxed);
95
+ result.push(*interned_string);
51
96
  } else {
97
+ let interned = StringCacheKey::new(&string);
52
98
  let leaked = Box::leak(string.clone().into_boxed_str());
53
- cache.insert(leaked, AtomicU32::new(1));
54
- result.push(leaked);
99
+ cache.insert(leaked, (interned, AtomicU32::new(1)));
100
+ result.push(interned);
55
101
  }
56
102
  }
57
103
  Ok(result)
58
104
  }
59
105
 
60
- pub fn clear(headers: &[&'static str]) -> Result<(), CacheError> {
106
+ pub fn clear(headers: &[StringCacheKey]) -> Result<(), CacheError> {
61
107
  let mut cache = STRING_CACHE
62
108
  .lock()
63
109
  .map_err(|e| CacheError::LockError(e.to_string()))?;
64
110
 
65
- for header in headers {
66
- if let Some(count) = cache.get(header) {
67
- // Returns the previous value of the counter
68
- let was = count.fetch_sub(1, std::sync::atomic::Ordering::Relaxed);
69
- if was == 1 {
70
- cache.remove(header);
71
- let ptr = *header as *const str as *mut str;
72
- unsafe {
73
- let _ = Box::from_raw(ptr);
111
+ let to_remove: Vec<_> = headers
112
+ .iter()
113
+ .filter_map(|header| {
114
+ let key = header.as_ref();
115
+ if let Some((_, (_, counter))) = cache.get_key_value(key) {
116
+ let prev_count = counter.fetch_sub(1, Ordering::Relaxed);
117
+ if prev_count == 1 {
118
+ Some(key)
119
+ } else {
120
+ None
74
121
  }
122
+ } else {
123
+ None
75
124
  }
76
- }
125
+ })
126
+ .collect();
127
+
128
+ for key in to_remove {
129
+ cache.remove(key);
77
130
  }
78
131
 
79
132
  Ok(())
@@ -8,6 +8,7 @@ mod ruby_reader;
8
8
 
9
9
  pub use builder::RecordReaderBuilder;
10
10
  pub(crate) use builder::BUFFER_CHANNEL_SIZE;
11
+ pub use header_cache::StringCacheKey;
11
12
  pub use record::CowValue;
12
13
  pub use record::CsvRecord;
13
14
  pub use ruby_integration::*;
@@ -2,13 +2,14 @@ use std::borrow::Cow;
2
2
  use std::collections::HashMap;
3
3
  use std::hash::BuildHasher;
4
4
 
5
+ use super::header_cache::StringCacheKey;
5
6
  use super::CowValue;
6
7
 
7
8
  pub trait RecordParser<'a> {
8
9
  type Output: 'a;
9
10
 
10
11
  fn parse(
11
- headers: &[&'static str],
12
+ headers: &[StringCacheKey],
12
13
  record: &csv::StringRecord,
13
14
  null_string: Option<&str>,
14
15
  flexible_default: Option<Cow<'a, str>>,
@@ -16,13 +17,13 @@ pub trait RecordParser<'a> {
16
17
  }
17
18
 
18
19
  impl<'a, S: BuildHasher + Default + 'a> RecordParser<'a>
19
- for HashMap<&'static str, Option<CowValue<'a>>, S>
20
+ for HashMap<StringCacheKey, Option<CowValue<'a>>, S>
20
21
  {
21
22
  type Output = Self;
22
23
 
23
24
  #[inline]
24
25
  fn parse(
25
- headers: &[&'static str],
26
+ headers: &[StringCacheKey],
26
27
  record: &csv::StringRecord,
27
28
  null_string: Option<&str>,
28
29
  flexible_default: Option<Cow<'a, str>>,
@@ -31,7 +32,7 @@ impl<'a, S: BuildHasher + Default + 'a> RecordParser<'a>
31
32
 
32
33
  let shared_empty = Cow::Borrowed("");
33
34
  let shared_default = flexible_default.map(CowValue);
34
- headers.iter().enumerate().for_each(|(i, &header)| {
35
+ headers.iter().enumerate().for_each(|(i, ref header)| {
35
36
  let value = record.get(i).map_or_else(
36
37
  || shared_default.clone(),
37
38
  |field| {
@@ -44,7 +45,7 @@ impl<'a, S: BuildHasher + Default + 'a> RecordParser<'a>
44
45
  }
45
46
  },
46
47
  );
47
- map.insert(header, value);
48
+ map.insert((*header).clone(), value);
48
49
  });
49
50
  map
50
51
  }
@@ -55,7 +56,7 @@ impl<'a> RecordParser<'a> for Vec<Option<CowValue<'a>>> {
55
56
 
56
57
  #[inline]
57
58
  fn parse(
58
- headers: &[&'static str],
59
+ headers: &[StringCacheKey],
59
60
  record: &csv::StringRecord,
60
61
  null_string: Option<&str>,
61
62
  flexible_default: Option<Cow<'a, str>>,
@@ -1,10 +1,13 @@
1
- use magnus::{IntoValue, Ruby, Value};
1
+ use itertools::Itertools;
2
+ use magnus::{value::ReprValue, IntoValue, Ruby, Value};
2
3
  use std::{borrow::Cow, collections::HashMap, hash::BuildHasher};
3
4
 
5
+ use super::StringCacheKey;
6
+
4
7
  #[derive(Debug)]
5
8
  pub enum CsvRecord<'a, S: BuildHasher + Default> {
6
9
  Vec(Vec<Option<CowValue<'a>>>),
7
- Map(HashMap<&'static str, Option<CowValue<'a>>, S>),
10
+ Map(HashMap<StringCacheKey, Option<CowValue<'a>>, S>),
8
11
  }
9
12
 
10
13
  impl<S: BuildHasher + Default> IntoValue for CsvRecord<'_, S> {
@@ -19,9 +22,23 @@ impl<S: BuildHasher + Default> IntoValue for CsvRecord<'_, S> {
19
22
  CsvRecord::Map(map) => {
20
23
  // Pre-allocate the hash with the known size
21
24
  let hash = handle.hash_new_capa(map.len());
22
- map.into_iter()
23
- .try_for_each(|(k, v)| hash.aset(k, v))
24
- .unwrap();
25
+
26
+ let mut values: [Value; 128] = [handle.qnil().as_value(); 128];
27
+ let mut i = 0;
28
+
29
+ for chunk in &map.into_iter().chunks(128) {
30
+ for (k, v) in chunk {
31
+ values[i] = handle.into_value(k);
32
+ values[i + 1] = handle.into_value(v);
33
+ i += 2;
34
+ }
35
+ hash.bulk_insert(&values[..i]).unwrap();
36
+
37
+ // Zero out used values
38
+ values[..i].fill(handle.qnil().as_value());
39
+ i = 0;
40
+ }
41
+
25
42
  hash.into_value_with(handle)
26
43
  }
27
44
  }
@@ -1,3 +1,4 @@
1
+ use super::header_cache::StringCacheKey;
1
2
  use super::parser::RecordParser;
2
3
  use super::{header_cache::StringCache, ruby_reader::SeekableRead};
3
4
  use magnus::{Error, Ruby};
@@ -14,13 +15,13 @@ pub struct RecordReader<'a, T: RecordParser<'a>> {
14
15
  enum ReaderImpl<'a, T: RecordParser<'a>> {
15
16
  SingleThreaded {
16
17
  reader: csv::Reader<BufReader<Box<dyn SeekableRead>>>,
17
- headers: Vec<&'static str>,
18
+ headers: Vec<StringCacheKey>,
18
19
  null_string: Option<String>,
19
20
  flexible_default: Option<Cow<'a, str>>,
20
21
  string_record: csv::StringRecord,
21
22
  },
22
23
  MultiThreaded {
23
- headers: Vec<&'static str>,
24
+ headers: Vec<StringCacheKey>,
24
25
  receiver: kanal::Receiver<T::Output>,
25
26
  handle: Option<thread::JoinHandle<()>>,
26
27
  },
@@ -51,7 +52,7 @@ impl<'a, T: RecordParser<'a>> RecordReader<'a, T> {
51
52
 
52
53
  pub(crate) fn new_single_threaded(
53
54
  reader: csv::Reader<BufReader<Box<dyn SeekableRead>>>,
54
- headers: Vec<&'static str>,
55
+ headers: Vec<StringCacheKey>,
55
56
  null_string: Option<String>,
56
57
  flexible_default: Option<&'a str>,
57
58
  ) -> Self {
@@ -71,7 +72,7 @@ impl<'a, T: RecordParser<'a>> RecordReader<'a, T> {
71
72
  impl<T: RecordParser<'static> + Send> RecordReader<'static, T> {
72
73
  pub(crate) fn new_multi_threaded(
73
74
  mut reader: csv::Reader<Box<dyn Read + Send + 'static>>,
74
- headers: Vec<&'static str>,
75
+ headers: Vec<StringCacheKey>,
75
76
  buffer_size: usize,
76
77
  null_string: Option<String>,
77
78
  flexible_default: Option<&'static str>,
@@ -162,10 +163,10 @@ impl<'a, T: RecordParser<'a>> Drop for RecordReader<'a, T> {
162
163
  if let Some(handle) = handle.take() {
163
164
  let _ = handle.join();
164
165
  }
165
- let _ = StringCache::clear(headers);
166
+ let _ = StringCache::clear(&headers);
166
167
  }
167
168
  ReaderImpl::SingleThreaded { headers, .. } => {
168
- let _ = StringCache::clear(headers);
169
+ let _ = StringCache::clear(&headers);
169
170
  }
170
171
  }
171
172
  }
@@ -74,9 +74,7 @@ impl Seek for RubyReader<RString> {
74
74
  match pos {
75
75
  io::SeekFrom::Start(offset) => self.offset = offset as usize,
76
76
  io::SeekFrom::Current(offset) => self.offset = (self.offset as i64 + offset) as usize,
77
- io::SeekFrom::End(offset) => {
78
- self.offset = self.inner.len() - offset as usize
79
- }
77
+ io::SeekFrom::End(offset) => self.offset = self.inner.len() - offset as usize,
80
78
  }
81
79
  Ok(self.offset as u64)
82
80
  }
@@ -1,4 +1,4 @@
1
- use crate::csv::{CowValue, CsvRecord, RecordReaderBuilder};
1
+ use crate::csv::{CowValue, CsvRecord, RecordReaderBuilder, StringCacheKey};
2
2
  use crate::utils::*;
3
3
  use ahash::RandomState;
4
4
  use csv::Trim;
@@ -54,7 +54,7 @@ pub fn parse_csv(
54
54
  let iter: Box<dyn Iterator<Item = CsvRecord<RandomState>>> = match result_type.as_str() {
55
55
  "hash" => {
56
56
  let builder = RecordReaderBuilder::<
57
- HashMap<&'static str, Option<CowValue<'static>>, RandomState>,
57
+ HashMap<StringCacheKey, Option<CowValue<'static>>, RandomState>,
58
58
  >::new(ruby, to_read)
59
59
  .has_headers(has_headers)
60
60
  .flexible(flexible)
data/lib/osv/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module OSV
2
- VERSION = "0.3.15"
2
+ VERSION = "0.3.16"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: osv
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.15
4
+ version: 0.3.16
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan Jaremko