Skip to content

Commit d74c19c

Browse files
authored
VER: Release 0.26.0
2 parents 7ca0f86 + 70e9861 commit d74c19c

File tree

6 files changed

+76
-32
lines changed

6 files changed

+76
-32
lines changed

CHANGELOG.md

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,39 @@
11
# Changelog
22

3-
## 0.25.0 - TBD
3+
4+
## 0.26.0 - 2025-05-28
5+
6+
This version marks the release of DBN version 3 (DBNv3), which is the new default.
7+
API methods and decoders support decoding all versions of DBN, but now default to
8+
upgrading data to version 3.
9+
10+
### Enhancements - Added `From<DatasetRange>` conversion for `DateTimeRange`
11+
- Added `is_last` field to live subscription requests which will be used to improve the
12+
handling of split subscription requests
13+
- Upgraded DBN version to 0.35.0:
14+
- Version 1 and 2 structs can be converted to version 3 structs with the `From` trait
15+
- Implemented conversion from `RecordRef` to `IoSlice` for use with
16+
`Write::write_vectored`
17+
18+
### Breaking changes
19+
- Breaking changes from DBN:
20+
- Definition schema:
21+
- Updated `InstrumentDefMsg` with new `leg_` fields to support multi-leg strategy
22+
definitions.
23+
- Expanded `asset` to 11 bytes and `ASSET_CSTR_LEN` to match
24+
- Expanded `raw_instrument_id` to 64 bits to support more venues. Like other 64-bit
25+
integer fields, its value will now be quoted in JSON
26+
- Removed `trading_reference_date`, `trading_reference_price`, and
27+
`settl_price_type` fields which will be normalized in the statistics schema
28+
- Removed `md_security_trading_status` better served by the status schema
29+
- Statistics schema:
30+
- Updated `StatMsg` has an expanded 64-bit `quantity` field. Like other 64-bit
31+
integer fields, its value will now be quoted in JSON
32+
- The previous `StatMsg` has been moved to `v2::StatMsg` or `StatMsgV2`
33+
- Changed the default `VersionUpgradePolicy` to `UpgradeToV3`
34+
- Updated the minimum supported `tokio` version to 1.38, which was released one year ago
35+
36+
## 0.25.0 - 2025-05-13
437

538
### Enhancements
639
- Increased live subscription symbol chunking size

Cargo.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ historical = ["dep:futures", "dep:reqwest", "dep:serde", "dep:tokio-util", "dep:
2323
live = ["dep:hex", "dep:sha2", "tokio/net"]
2424

2525
[dependencies]
26-
dbn = { version = "0.34.0", features = ["async", "serde"] }
26+
dbn = { version = "0.35.0", features = ["async", "serde"] }
2727
# Async stream trait
2828
futures = { version = "0.3", optional = true }
2929
# Used for Live authentication
@@ -44,8 +44,8 @@ typed-builder = "0.21"
4444
[dev-dependencies]
4545
anyhow = "1.0.98"
4646
async-compression = { version = "0.4.23", features = ["tokio", "zstd"] }
47-
clap = { version = "4.5.37", features = ["derive"] }
48-
tempfile = "3.19.1"
49-
tokio = { version = "1.44", features = ["full"] }
47+
clap = { version = "4.5.39", features = ["derive"] }
48+
tempfile = "3.20.0"
49+
tokio = { version = "1.45", features = ["full"] }
5050
tracing-subscriber = "0.3.19"
5151
wiremock = "0.6"

src/historical/metadata.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ impl MetadataClient<'_> {
159159
}
160160

161161
/// Gets the cost in US dollars for a historical streaming or batch download
162-
/// request.
162+
/// request. This cost respects any discounts provided by flat rate plans.
163163
///
164164
/// # Errors
165165
/// This function returns an error when it fails to communicate with the Databento API
@@ -286,6 +286,12 @@ pub struct DatasetRange {
286286
pub end: time::OffsetDateTime,
287287
}
288288

289+
impl From<DatasetRange> for DateTimeRange {
290+
fn from(DatasetRange { start, end }: DatasetRange) -> Self {
291+
Self { start, end }
292+
}
293+
}
294+
289295
impl<'de> Deserialize<'de> for DatasetRange {
290296
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
291297
where

src/historical/timeseries.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ impl TimeseriesClient<'_> {
5454
)
5555
.await?;
5656
let mut decoder: AsyncDbnDecoder<_> = AsyncDbnDecoder::with_zstd_buffer(reader).await?;
57-
decoder.set_upgrade_policy(params.upgrade_policy);
57+
decoder.set_upgrade_policy(params.upgrade_policy)?;
5858
Ok(decoder)
5959
}
6060

@@ -87,7 +87,7 @@ impl TimeseriesClient<'_> {
8787
)
8888
.await?;
8989
let mut http_decoder = AsyncDbnDecoder::with_zstd_buffer(reader).await?;
90-
http_decoder.set_upgrade_policy(params.upgrade_policy);
90+
http_decoder.set_upgrade_policy(params.upgrade_policy)?;
9191
let file = BufWriter::new(File::create(&params.path).await?);
9292
let mut encoder = AsyncDbnEncoder::with_zstd(file, http_decoder.metadata()).await?;
9393
while let Some(rec_ref) = http_decoder.decode_record_ref().await? {
@@ -132,7 +132,7 @@ impl TimeseriesClient<'_> {
132132
.await?
133133
.error_for_status()?
134134
.bytes_stream()
135-
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e));
135+
.map_err(std::io::Error::other);
136136
Ok(tokio_util::io::StreamReader::new(stream))
137137
}
138138

src/live/client.rs

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ pub struct Client {
3838
}
3939

4040
enum Decoder {
41-
Metadata(AsyncMetadataDecoder<BufReader<ReadHalf<TcpStream>>>),
42-
Record(AsyncRecordDecoder<BufReader<ReadHalf<TcpStream>>>),
41+
Metadata(AsyncMetadataDecoder<ReadHalf<TcpStream>>),
42+
Record(AsyncRecordDecoder<ReadHalf<TcpStream>>),
4343
Empty,
4444
}
4545

@@ -107,7 +107,7 @@ impl Client {
107107
heartbeat_interval,
108108
protocol,
109109
peer_addr,
110-
decoder: Decoder::Metadata(AsyncMetadataDecoder::new(recver)),
110+
decoder: Decoder::Metadata(AsyncMetadataDecoder::new(recver.into_inner())),
111111
session_id,
112112
span,
113113
sub_counter: 0,
@@ -229,12 +229,7 @@ impl Client {
229229
info!("Starting session");
230230
self.protocol.start_session().await?;
231231
let mut metadata = decoder.decode().await?;
232-
self.decoder = Decoder::Record(AsyncRecordDecoder::with_version(
233-
decoder.into_inner(),
234-
metadata.version,
235-
self.upgrade_policy,
236-
metadata.ts_out,
237-
)?);
232+
self.decoder = Decoder::Record(AsyncRecordDecoder::from(decoder));
238233
// Should match `send_ts_out` but set again here for safety
239234
metadata.upgrade(self.upgrade_policy);
240235
Ok(metadata)
@@ -299,7 +294,7 @@ impl Client {
299294
self.heartbeat_interval.map(|i| i.whole_seconds()),
300295
)
301296
.await?;
302-
self.decoder = Decoder::Metadata(AsyncMetadataDecoder::new(recver));
297+
self.decoder = Decoder::Metadata(AsyncMetadataDecoder::new(recver.into_inner()));
303298
self.span = info_span!("LiveClient", dataset = %self.dataset, session_id = self.session_id);
304299
Ok(())
305300
}
@@ -413,7 +408,7 @@ mod tests {
413408
self.send("success=1|session_id=5\n").await;
414409
}
415410

416-
async fn subscribe(&mut self, subscription: Subscription) {
411+
async fn subscribe(&mut self, subscription: Subscription, is_last: bool) {
417412
let sub_line = self.read_line().await;
418413
assert!(sub_line.contains(&format!("symbols={}", subscription.symbols.to_api_string())));
419414
assert!(sub_line.contains(&format!("schema={}", subscription.schema)));
@@ -423,6 +418,7 @@ mod tests {
423418
assert!(sub_line.contains(&format!("start={}", start.unix_timestamp_nanos())))
424419
}
425420
assert!(sub_line.contains(&format!("snapshot={}", subscription.use_snapshot as u8)));
421+
assert!(sub_line.contains(&format!("is_last={}", is_last as u8)));
426422
}
427423

428424
async fn start(&mut self) {
@@ -489,7 +485,7 @@ mod tests {
489485
Accept,
490486
Authenticate(Option<Duration>),
491487
Send(String),
492-
Subscribe(Subscription),
488+
Subscribe(Subscription, bool),
493489
Start,
494490
SendRecord(Box<dyn AsRef<[u8]> + Send>),
495491
Disconnect,
@@ -502,7 +498,7 @@ mod tests {
502498
Event::Accept => write!(f, "Accept"),
503499
Event::Authenticate(hb_int) => write!(f, "Authenticate({hb_int:?})"),
504500
Event::Send(msg) => write!(f, "Send({msg:?})"),
505-
Event::Subscribe(sub) => write!(f, "Subscribe({sub:?})"),
501+
Event::Subscribe(sub, is_last) => write!(f, "Subscribe({sub:?}, {is_last:?})"),
506502
Event::Start => write!(f, "Start"),
507503
Event::SendRecord(_) => write!(f, "SendRecord"),
508504
Event::Disconnect => write!(f, "Disconnect"),
@@ -521,7 +517,7 @@ mod tests {
521517
Some(Event::Authenticate(hb_int)) => mock.authenticate(hb_int).await,
522518
Some(Event::Accept) => mock.accept().await,
523519
Some(Event::Send(msg)) => mock.send(&msg).await,
524-
Some(Event::Subscribe(sub)) => mock.subscribe(sub).await,
520+
Some(Event::Subscribe(sub, is_last)) => mock.subscribe(sub, is_last).await,
525521
Some(Event::Start) => mock.start().await,
526522
Some(Event::SendRecord(rec)) => mock.send_record(rec).await,
527523
Some(Event::Disconnect) => mock.close().await,
@@ -544,8 +540,10 @@ mod tests {
544540
.unwrap();
545541
}
546542

547-
pub fn expect_subscribe(&mut self, subscription: Subscription) {
548-
self.send.send(Event::Subscribe(subscription)).unwrap();
543+
pub fn expect_subscribe(&mut self, subscription: Subscription, is_last: bool) {
544+
self.send
545+
.send(Event::Subscribe(subscription, is_last))
546+
.unwrap();
549547
}
550548

551549
pub fn start(&mut self) {
@@ -613,7 +611,7 @@ mod tests {
613611
.schema(Schema::Ohlcv1M)
614612
.stype_in(SType::RawSymbol)
615613
.build();
616-
fixture.expect_subscribe(subscription.clone());
614+
fixture.expect_subscribe(subscription.clone(), true);
617615
client.subscribe(subscription).await.unwrap();
618616
fixture.stop().await;
619617
}
@@ -628,7 +626,7 @@ mod tests {
628626
.stype_in(SType::RawSymbol)
629627
.use_snapshot()
630628
.build();
631-
fixture.expect_subscribe(subscription.clone());
629+
fixture.expect_subscribe(subscription.clone(), true);
632630
client.subscribe(subscription).await.unwrap();
633631
fixture.stop().await;
634632
}
@@ -670,7 +668,10 @@ mod tests {
670668
let mut i = 0;
671669
while i < SYMBOL_COUNT {
672670
let chunk_size = 500.min(SYMBOL_COUNT - i);
673-
fixture.expect_subscribe(sub_base.clone().symbols(vec![SYMBOL; chunk_size]).build());
671+
fixture.expect_subscribe(
672+
sub_base.clone().symbols(vec![SYMBOL; chunk_size]).build(),
673+
i + chunk_size == SYMBOL_COUNT,
674+
);
674675
i += chunk_size;
675676
}
676677
fixture.stop().await;
@@ -823,7 +824,7 @@ mod tests {
823824
.schema(Schema::Trades)
824825
.start(OffsetDateTime::UNIX_EPOCH)
825826
.build();
826-
fixture.expect_subscribe(sub.clone());
827+
fixture.expect_subscribe(sub.clone(), true);
827828
client.subscribe(sub.clone()).await.unwrap();
828829
fixture.start();
829830
let metadata = client.start().await.unwrap();
@@ -863,7 +864,7 @@ mod tests {
863864

864865
let mut resub = sub.clone();
865866
resub.start = None;
866-
fixture.expect_subscribe(resub);
867+
fixture.expect_subscribe(resub, true);
867868
client.resubscribe().await.unwrap();
868869
fixture.start();
869870
client.start().await.unwrap();

src/live/protocol.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,14 +134,16 @@ where
134134
let start_nanos = sub.start.as_ref().map(|start| start.unix_timestamp_nanos());
135135

136136
let symbol_chunks = sub.symbols.to_chunked_api_string();
137-
for sym_str in symbol_chunks {
137+
let last_chunk_idx = symbol_chunks.len() - 1;
138+
for (i, sym_str) in symbol_chunks.into_iter().enumerate() {
138139
let sub_req = SubRequest::new(
139140
*schema,
140141
*stype_in,
141142
start_nanos,
142143
*use_snapshot,
143144
sub.id,
144145
&sym_str,
146+
i == last_chunk_idx,
145147
);
146148
debug!(?sub_req, "Sending subscription request");
147149
self.sender.write_all(sub_req.as_bytes()).await?;
@@ -309,10 +311,12 @@ impl SubRequest {
309311
use_snapshot: bool,
310312
id: Option<u32>,
311313
symbols: &str,
314+
is_last: bool,
312315
) -> Self {
313316
let use_snapshot = use_snapshot as u8;
317+
let is_last = is_last as u8;
314318
let mut args = format!(
315-
"schema={schema}|stype_in={stype_in}|symbols={symbols}|snapshot={use_snapshot}"
319+
"schema={schema}|stype_in={stype_in}|symbols={symbols}|snapshot={use_snapshot}|is_last={is_last}"
316320
);
317321

318322
if let Some(start) = start_nanos {

0 commit comments

Comments
 (0)