Skip to content

Commit c778b93

Browse files
authored
VER: Release 0.26.2
2 parents e586307 + 23c6e6d commit c778b93

File tree

4 files changed

+90
-40
lines changed

4 files changed

+90
-40
lines changed

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
# Changelog
22

3+
## 0.26.2 - 2025-06-03
4+
5+
### Enhancements
6+
- Improved performance of live client by removing redundant state
7+
- Upgraded DBN version to 0.35.1
8+
9+
### Bug fixes
10+
- Fixed handling of `VersionUpgradePolicy` in `timeseries().get_range()` and
11+
`get_range_to_file()`
12+
- Bug fixes from DBN:
13+
- Fixed behavior where encoding metadata could lower the `version`
14+
- Changed `DbnFsm::data()` to exclude all processed data
15+
- Fixed `Metadata::upgrade()` behavior with `UpgradeToV2`
16+
317
## 0.26.1 - 2025-05-30
418

519
### Bug fixes

Cargo.toml

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "databento"
33
authors = ["Databento <support@databento.com>"]
4-
version = "0.26.1"
4+
version = "0.26.2"
55
edition = "2021"
66
repository = "https://github.com/databento/databento-rs"
77
description = "Official Databento client library"
@@ -19,11 +19,21 @@ rustdoc-args = ["--cfg", "docsrs"]
1919

2020
[features]
2121
default = ["historical", "live"]
22-
historical = ["dep:futures", "dep:reqwest", "dep:serde", "dep:tokio-util", "dep:serde_json", "tokio/fs"]
22+
historical = [
23+
"dep:async-compression",
24+
"dep:futures",
25+
"dep:reqwest",
26+
"dep:serde",
27+
"dep:tokio-util",
28+
"dep:serde_json",
29+
"tokio/fs"
30+
]
2331
live = ["dep:hex", "dep:sha2", "tokio/net"]
2432

2533
[dependencies]
26-
dbn = { version = "0.35.0", features = ["async", "serde"] }
34+
dbn = { version = "0.35.1", features = ["async", "serde"] }
35+
36+
async-compression = { version = "0.4", features = ["tokio", "zstd"], optional = true }
2737
# Async stream trait
2838
futures = { version = "0.3", optional = true }
2939
# Used for Live authentication
@@ -43,8 +53,9 @@ typed-builder = "0.21"
4353

4454
[dev-dependencies]
4555
anyhow = "1.0.98"
46-
async-compression = { version = "0.4.23", features = ["tokio", "zstd"] }
56+
async-compression = { version = "0.4", features = ["tokio", "zstd"] }
4757
clap = { version = "4.5.37", features = ["derive"] }
58+
rstest = "0.25.0"
4859
tempfile = "3.19.1"
4960
tokio = { version = "1.44", features = ["full"] }
5061
tracing-subscriber = "0.3.19"

src/historical/timeseries.rs

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use futures::{Stream, TryStreamExt};
77
use reqwest::{header::ACCEPT, RequestBuilder};
88
use tokio::{
99
fs::File,
10-
io::{AsyncReadExt, AsyncWriteExt, BufWriter},
10+
io::{AsyncReadExt, AsyncWriteExt, BufReader, BufWriter},
1111
};
1212
use tokio_util::{bytes::Bytes, io::StreamReader};
1313
use typed_builder::TypedBuilder;
@@ -53,9 +53,10 @@ impl TimeseriesClient<'_> {
5353
params.limit,
5454
)
5555
.await?;
56-
let mut decoder: AsyncDbnDecoder<_> = AsyncDbnDecoder::with_zstd_buffer(reader).await?;
57-
decoder.set_upgrade_policy(params.upgrade_policy)?;
58-
Ok(decoder)
56+
Ok(
57+
AsyncDbnDecoder::with_upgrade_policy(zstd_decoder(reader), params.upgrade_policy)
58+
.await?,
59+
)
5960
}
6061

6162
/// Makes a streaming request for timeseries data from Databento.
@@ -86,15 +87,21 @@ impl TimeseriesClient<'_> {
8687
params.limit,
8788
)
8889
.await?;
89-
let mut http_decoder = AsyncDbnDecoder::with_zstd_buffer(reader).await?;
90-
http_decoder.set_upgrade_policy(params.upgrade_policy)?;
90+
let mut http_decoder =
91+
AsyncDbnDecoder::with_upgrade_policy(zstd_decoder(reader), params.upgrade_policy)
92+
.await?;
9193
let file = BufWriter::new(File::create(&params.path).await?);
9294
let mut encoder = AsyncDbnEncoder::with_zstd(file, http_decoder.metadata()).await?;
9395
while let Some(rec_ref) = http_decoder.decode_record_ref().await? {
9496
encoder.encode_record_ref(rec_ref).await?;
9597
}
9698
encoder.get_mut().shutdown().await?;
97-
Ok(AsyncDbnDecoder::from_zstd_file(&params.path).await?)
99+
Ok(AsyncDbnDecoder::with_upgrade_policy(
100+
zstd_decoder(BufReader::new(File::open(&params.path).await?)),
101+
// Applied upgrade policy during initial decoding
102+
VersionUpgradePolicy::AsIs,
103+
)
104+
.await?)
98105
}
99106

100107
#[allow(clippy::too_many_arguments)] // private method
@@ -241,10 +248,21 @@ impl GetRangeParams {
241248
}
242249
}
243250

251+
fn zstd_decoder<R>(reader: R) -> async_compression::tokio::bufread::ZstdDecoder<R>
252+
where
253+
R: tokio::io::AsyncBufReadExt + Unpin,
254+
{
255+
let mut zstd_decoder = async_compression::tokio::bufread::ZstdDecoder::new(reader);
256+
// explicitly enable decoding multiple frames
257+
zstd_decoder.multiple_members(true);
258+
zstd_decoder
259+
}
260+
244261
#[cfg(test)]
245262
mod tests {
246263
use dbn::{record::TradeMsg, Dataset};
247264
use reqwest::StatusCode;
265+
use rstest::*;
248266
use time::macros::datetime;
249267
use wiremock::{
250268
matchers::{basic_auth, method, path},
@@ -260,8 +278,12 @@ mod tests {
260278

261279
const API_KEY: &str = "test-API";
262280

281+
#[rstest]
282+
#[case(VersionUpgradePolicy::AsIs, 1)]
283+
#[case(VersionUpgradePolicy::UpgradeToV2, 2)]
284+
#[case(VersionUpgradePolicy::UpgradeToV3, 3)]
263285
#[tokio::test]
264-
async fn test_get_range() {
286+
async fn test_get_range(#[case] upgrade_policy: VersionUpgradePolicy, #[case] exp_version: u8) {
265287
const START: time::OffsetDateTime = datetime!(2023 - 06 - 14 00:00 UTC);
266288
const END: time::OffsetDateTime = datetime!(2023 - 06 - 17 00:00 UTC);
267289
const SCHEMA: Schema = Schema::Trades;
@@ -299,19 +321,29 @@ mod tests {
299321
.schema(SCHEMA)
300322
.symbols(vec!["SPOT", "AAPL"])
301323
.date_time_range((START, END))
324+
.upgrade_policy(upgrade_policy)
302325
.build(),
303326
)
304327
.await
305328
.unwrap();
306-
assert_eq!(decoder.metadata().schema.unwrap(), SCHEMA);
329+
let metadata = decoder.metadata();
330+
assert_eq!(metadata.schema.unwrap(), SCHEMA);
331+
assert_eq!(metadata.version, exp_version);
307332
// Two records
308333
decoder.decode_record::<TradeMsg>().await.unwrap().unwrap();
309334
decoder.decode_record::<TradeMsg>().await.unwrap().unwrap();
310335
assert!(decoder.decode_record::<TradeMsg>().await.unwrap().is_none());
311336
}
312337

338+
#[rstest]
339+
#[case(VersionUpgradePolicy::AsIs, 1)]
340+
#[case(VersionUpgradePolicy::UpgradeToV2, 2)]
341+
#[case(VersionUpgradePolicy::UpgradeToV3, 3)]
313342
#[tokio::test]
314-
async fn test_get_range_to_file() {
343+
async fn test_get_range_to_file(
344+
#[case] upgrade_policy: VersionUpgradePolicy,
345+
#[case] exp_version: u8,
346+
) {
315347
const START: time::OffsetDateTime = datetime!(2024 - 05 - 17 00:00 UTC);
316348
const END: time::OffsetDateTime = datetime!(2024 - 05 - 18 00:00 UTC);
317349
const SCHEMA: Schema = Schema::Trades;
@@ -354,11 +386,14 @@ mod tests {
354386
.stype_in(SType::Parent)
355387
.date_time_range((START, END))
356388
.path(path.clone())
389+
.upgrade_policy(upgrade_policy)
357390
.build(),
358391
)
359392
.await
360393
.unwrap();
361-
assert_eq!(decoder.metadata().schema.unwrap(), SCHEMA);
394+
let metadata = decoder.metadata();
395+
assert_eq!(metadata.schema.unwrap(), SCHEMA);
396+
assert_eq!(metadata.version, exp_version);
362397
// Two records
363398
decoder.decode_record::<TradeMsg>().await.unwrap().unwrap();
364399
decoder.decode_record::<TradeMsg>().await.unwrap().unwrap();

src/live/client.rs

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::{fmt, net::SocketAddr};
22

33
use dbn::{
4-
decode::dbn::{AsyncMetadataDecoder, AsyncRecordDecoder},
4+
decode::dbn::{async_decode_metadata_with_fsm, async_decode_record_ref_with_fsm, fsm::DbnFsm},
55
Metadata, RecordRef, VersionUpgradePolicy,
66
};
77
use time::Duration;
@@ -32,17 +32,12 @@ pub struct Client {
3232
peer_addr: SocketAddr,
3333
sub_counter: u32,
3434
subscriptions: Vec<Subscription>,
35-
decoder: Decoder,
35+
reader: ReadHalf<TcpStream>,
36+
fsm: DbnFsm,
3637
session_id: String,
3738
span: Span,
3839
}
3940

40-
enum Decoder {
41-
Metadata(AsyncMetadataDecoder<ReadHalf<TcpStream>>),
42-
Record(AsyncRecordDecoder<ReadHalf<TcpStream>>),
43-
Empty,
44-
}
45-
4641
impl Client {
4742
/// Creates a new client connected to a Live gateway.
4843
///
@@ -107,10 +102,12 @@ impl Client {
107102
heartbeat_interval,
108103
protocol,
109104
peer_addr,
110-
decoder: Decoder::Metadata(AsyncMetadataDecoder::with_upgrade_policy(
111-
recver.into_inner(),
112-
upgrade_policy,
113-
)),
105+
reader: recver.into_inner(),
106+
fsm: DbnFsm::builder()
107+
.upgrade_policy(upgrade_policy)
108+
.build()
109+
// Not setting input version so it's infallible
110+
.unwrap(),
114111
session_id,
115112
span,
116113
sub_counter: 0,
@@ -220,20 +217,15 @@ impl Client {
220217
/// closing the connection.
221218
#[instrument(parent = &self.span, skip_all)]
222219
pub async fn start(&mut self) -> crate::Result<Metadata> {
223-
let decoder @ Decoder::Metadata(_) = &mut self.decoder else {
220+
if self.fsm.has_decoded_metadata() {
224221
return Err(crate::Error::BadArgument {
225222
param_name: "self".to_owned(),
226223
desc: "ignored request to start session that has already been started".to_owned(),
227224
});
228225
};
229-
let Decoder::Metadata(mut decoder) = std::mem::replace(decoder, Decoder::Empty) else {
230-
unreachable!("previously checked decoder type");
231-
};
232226
info!("Starting session");
233227
self.protocol.start_session().await?;
234-
let metadata = decoder.decode().await?;
235-
self.decoder = Decoder::Record(AsyncRecordDecoder::from(decoder));
236-
Ok(metadata)
228+
Ok(async_decode_metadata_with_fsm(&mut self.reader, &mut self.fsm).await?)
237229
}
238230

239231
/// Fetches the next record. This method should only be called after the session has
@@ -252,13 +244,13 @@ impl Client {
252244
/// without the potential for corrupting the input stream.
253245
#[instrument(parent = &self.span, level = "debug", skip_all)]
254246
pub async fn next_record(&mut self) -> crate::Result<Option<RecordRef>> {
255-
let Decoder::Record(decoder) = &mut self.decoder else {
247+
if !self.fsm.has_decoded_metadata() {
256248
return Err(crate::Error::BadArgument {
257249
param_name: "self".to_owned(),
258250
desc: "Can't call LiveClient::next_record before starting session".to_owned(),
259251
});
260252
};
261-
Ok(decoder.decode_ref().await?)
253+
Ok(async_decode_record_ref_with_fsm(&mut self.reader, &mut self.fsm).await?)
262254
}
263255

264256
/// Closes the current connection, then reopens the connection and authenticates
@@ -295,10 +287,8 @@ impl Client {
295287
self.heartbeat_interval.map(|i| i.whole_seconds()),
296288
)
297289
.await?;
298-
self.decoder = Decoder::Metadata(AsyncMetadataDecoder::with_upgrade_policy(
299-
recver.into_inner(),
300-
self.upgrade_policy,
301-
));
290+
self.reader = recver.into_inner();
291+
self.fsm.reset();
302292
self.span = info_span!("LiveClient", dataset = %self.dataset, session_id = self.session_id);
303293
Ok(())
304294
}

0 commit comments

Comments
 (0)