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 +4 -4
- data/Cargo.lock +11 -1
- data/README.md +27 -27
- data/ext/osv/Cargo.toml +1 -0
- data/ext/osv/src/csv/header_cache.rs +79 -26
- data/ext/osv/src/csv/mod.rs +1 -0
- data/ext/osv/src/csv/parser.rs +7 -6
- data/ext/osv/src/csv/record.rs +22 -5
- data/ext/osv/src/csv/record_reader.rs +7 -6
- data/ext/osv/src/csv/ruby_reader.rs +1 -3
- data/ext/osv/src/reader.rs +2 -2
- data/lib/osv/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 91401989a8532162a9731fed3cb07661c0676105f77465da23f9a267773e7651
|
4
|
+
data.tar.gz: aeba48f1338a4160044e8c7264f80eb065d950567288bded39acf5d9bc593d7b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
🏃
|
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.
|
146
|
-
FastCSV - StringIO 0.
|
147
|
-
OSV - StringIO 0.
|
148
|
-
CSV - Hash output 0.
|
149
|
-
OSV - Hash output 0.
|
150
|
-
CSV - Array output 0.066 (± 0.0%) i/s (15.
|
151
|
-
OSV - Array output 0.
|
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.
|
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.
|
156
|
-
OSV - Gzipped 0.
|
157
|
-
OSV - Gzipped Direct 0.
|
158
|
-
FastCSV - Gzipped 0.
|
159
|
-
CSV - Gzipped 0.
|
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
|
163
|
-
OSV -
|
164
|
-
|
165
|
-
|
166
|
-
OSV - Gzipped: 0.5 i/s - 1.
|
167
|
-
FastCSV - StringIO: 0.4 i/s - 1.
|
168
|
-
FastCSV - Array output: 0.
|
169
|
-
|
170
|
-
|
171
|
-
CSV - StringIO: 0.1 i/s - 9.
|
172
|
-
CSV - Array output: 0.1 i/s -
|
173
|
-
CSV - Hash output: 0.1 i/s - 12.
|
174
|
-
CSV - Gzipped: 0.1 i/s - 12.
|
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
|
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((
|
32
|
-
|
33
|
-
Ok(
|
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(
|
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
|
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((
|
49
|
-
|
50
|
-
result.push(
|
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(
|
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: &[
|
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
|
-
|
66
|
-
|
67
|
-
|
68
|
-
let
|
69
|
-
if
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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(())
|
data/ext/osv/src/csv/mod.rs
CHANGED
data/ext/osv/src/csv/parser.rs
CHANGED
@@ -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: &[
|
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
|
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: &[
|
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,
|
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: &[
|
59
|
+
headers: &[StringCacheKey],
|
59
60
|
record: &csv::StringRecord,
|
60
61
|
null_string: Option<&str>,
|
61
62
|
flexible_default: Option<Cow<'a, str>>,
|
data/ext/osv/src/csv/record.rs
CHANGED
@@ -1,10 +1,13 @@
|
|
1
|
-
use
|
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
|
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
|
-
|
23
|
-
|
24
|
-
|
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
|
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
|
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
|
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
|
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
|
}
|
data/ext/osv/src/reader.rs
CHANGED
@@ -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
|
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