Skip to content

Commit 2c18f38

Browse files
authored
VER: Release 0.35.1
2 parents 4f96ab4 + f545f1a commit 2c18f38

File tree

14 files changed

+368
-74
lines changed

14 files changed

+368
-74
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.35.1 - 2025-06-03
4+
5+
### Enhancements
6+
- Documented `AsyncMetadataDecoder::decode`, `AsyncDecoder::new`, and
7+
`AsyncDecoder::with_upgrade_policy()` as now being cancellation safe
8+
- Added `DbnFsm::reset()` method that resets state to facilitate reuse
9+
- Added `DbnFsm::has_decoded_metadata()` method to check the internal
10+
state
11+
12+
### Bug fixes
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.35.0 - 2025-05-28
418
This version marks the release of DBN version 3 (DBNv3), which is the new default.
519
Decoders support decoding all versions of DBN and the DBN encoders default to

Cargo.lock

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ resolver = "2"
1111
[workspace.package]
1212
authors = ["Databento <[email protected]>"]
1313
edition = "2021"
14-
version = "0.35.0"
14+
version = "0.35.1"
1515
documentation = "https://databento.com/docs"
1616
repository = "https://github.com/databento/dbn"
1717
license = "Apache-2.0"

python/pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "databento-dbn"
3-
version = "0.35.0"
3+
version = "0.35.1"
44
description = "Python bindings for encoding and decoding Databento Binary Encoding (DBN)"
55
authors = ["Databento <[email protected]>"]
66
license = "Apache-2.0"
@@ -17,7 +17,7 @@ build-backend = "maturin"
1717

1818
[project]
1919
name = "databento-dbn"
20-
version = "0.35.0"
20+
version = "0.35.1"
2121
authors = [
2222
{ name = "Databento", email = "[email protected]" }
2323
]

rust/dbn-cli/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ name = "dbn"
1616
path = "src/main.rs"
1717

1818
[dependencies]
19-
dbn = { path = "../dbn", version = "=0.35.0", default-features = false }
19+
dbn = { path = "../dbn", version = "=0.35.1", default-features = false }
2020

2121
anyhow = { workspace = true }
2222
clap = { version = "4.5", features = ["derive", "wrap_help"] }

rust/dbn/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ serde = ["dep:serde", "time/parsing", "time/serde"]
2525
trivial_copy = []
2626

2727
[dependencies]
28-
dbn-macros = { version = "=0.35.0", path = "../dbn-macros" }
28+
dbn-macros = { version = "=0.35.1", path = "../dbn-macros" }
2929

3030
async-compression = { version = "0.4.23", features = ["tokio", "zstd"], optional = true }
3131
csv = { workspace = true }

rust/dbn/src/decode/dbn.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ pub mod fsm;
1616
mod r#async;
1717
#[cfg(feature = "async")]
1818
pub use r#async::{
19-
Decoder as AsyncDecoder, MetadataDecoder as AsyncMetadataDecoder,
20-
RecordDecoder as AsyncRecordDecoder,
19+
decode_metadata_with_fsm as async_decode_metadata_with_fsm,
20+
decode_record_ref_with_fsm as async_decode_record_ref_with_fsm, Decoder as AsyncDecoder,
21+
MetadataDecoder as AsyncMetadataDecoder, RecordDecoder as AsyncRecordDecoder,
2122
};

rust/dbn/src/decode/dbn/async.rs

Lines changed: 114 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,8 @@ where
3737
/// `reader` or the input is encoded in a newer version of DBN.
3838
///
3939
/// # Cancel safety
40-
/// This method is not cancellation safe. If this method is used in a
41-
/// `tokio::select!` statement and another branch completes first, the metadata
42-
/// may have been partially read, corrupting the stream.
40+
/// This method is cancel safe. It can be used within a `tokio::select!` statement
41+
/// without the potential for corrupting the input stream.
4342
pub async fn new(reader: R) -> crate::Result<Self> {
4443
let mut metadata_decoder = MetadataDecoder::new(reader);
4544
let mut metadata = metadata_decoder.decode().await?;
@@ -58,9 +57,8 @@ where
5857
/// `reader` or the input is encoded in a newer version of DBN.
5958
///
6059
/// # Cancel safety
61-
/// This method is not cancellation safe. If this method is used in a
62-
/// `tokio::select!` statement and another branch completes first, the metadata
63-
/// may have been partially read, corrupting the stream.
60+
/// This method is cancel safe. It can be used within a `tokio::select!` statement
61+
/// without the potential for corrupting the input stream.
6462
pub async fn with_upgrade_policy(
6563
reader: R,
6664
upgrade_policy: VersionUpgradePolicy,
@@ -102,7 +100,7 @@ where
102100
/// incompatible.
103101
pub fn set_upgrade_policy(&mut self, upgrade_policy: VersionUpgradePolicy) -> Result<()> {
104102
self.decoder.set_upgrade_policy(upgrade_policy)?;
105-
self.metadata.set_version(upgrade_policy);
103+
self.metadata.upgrade(upgrade_policy);
106104
Ok(())
107105
}
108106

@@ -293,6 +291,12 @@ where
293291
Ok(Self { reader, fsm })
294292
}
295293

294+
/// Creates a new `RecordDecoder` that will decode from `reader` and use the state
295+
/// and buffered data already in `fsm`.
296+
pub fn with_fsm(reader: R, fsm: DbnFsm) -> Self {
297+
Self { reader, fsm }
298+
}
299+
296300
/// Sets the DBN version to expect when decoding.
297301
///
298302
/// # Errors
@@ -496,45 +500,23 @@ where
496500
Self { reader, fsm }
497501
}
498502

503+
/// Creates a new `MetadataDecoder` that will decode from `reader` and use the state
504+
/// and buffered data already in `fsm`.
505+
pub fn with_fsm(reader: R, fsm: DbnFsm) -> Self {
506+
Self { reader, fsm }
507+
}
508+
499509
/// Decodes and returns a DBN [`Metadata`].
500510
///
501511
/// # Errors
502512
/// This function will return an error if it is unable to parse the metadata or the
503513
/// input is encoded in a newere version of DBN.
504514
///
505515
/// # Cancel safety
506-
/// This method is not cancellation safe. If this method is used in a
507-
/// `tokio::select!` statement and another branch completes first, the metadata
508-
/// may have been partially read, corrupting the stream.
516+
/// This method is cancel safe. It can be used within a `tokio::select!` statement
517+
/// without the potential for corrupting the input stream.
509518
pub async fn decode(&mut self) -> Result<Metadata> {
510-
let io_err = |err| crate::Error::io(err, "decoding metadata");
511-
let nbytes = self.reader.read(self.fsm.space()).await.map_err(io_err)?;
512-
self.fsm.fill(nbytes);
513-
loop {
514-
match self.fsm.process() {
515-
ProcessResult::ReadMore(n) => {
516-
// asm guarantees there's at least `n` bytes available in `space()`
517-
let mut total_read = 0;
518-
loop {
519-
let read = self.reader.read(self.fsm.space()).await.map_err(io_err)?;
520-
if read == 0 {
521-
return Err(crate::Error::io(
522-
io::Error::from(io::ErrorKind::UnexpectedEof),
523-
"decoding metadata",
524-
));
525-
}
526-
self.fsm.fill(read);
527-
total_read += read;
528-
if total_read >= n {
529-
break;
530-
}
531-
}
532-
}
533-
ProcessResult::Metadata(metadata) => return Ok(metadata),
534-
ProcessResult::Record(_) => unreachable!("metadata precedes records"),
535-
ProcessResult::Err(error) => return Err(error),
536-
}
537-
}
519+
decode_metadata_with_fsm(&mut self.reader, &mut self.fsm).await
538520
}
539521

540522
/// Returns a mutable reference to the inner reader.
@@ -568,6 +550,99 @@ where
568550
}
569551
}
570552

553+
/// A low-level function for decoding DBN [`Metadata`]. Generally, the [`Decoder`] or
554+
/// [`MetadataDecoder`] should be used instead of this function.
555+
///
556+
/// # Errors
557+
/// This function will return an error if it is unable to parse the metadata or the
558+
/// input is encoded in a newere version of DBN.
559+
///
560+
/// # Cancel safety
561+
/// This method is cancel safe. It can be used within a `tokio::select!` statement
562+
/// without the potential for corrupting the input stream.
563+
///
564+
/// # Panics
565+
/// This function will panic if it encounters DBN records. The caller the initial data
566+
/// from `reader` contains DBN metadata.
567+
pub async fn decode_metadata_with_fsm<R>(mut reader: R, fsm: &mut DbnFsm) -> crate::Result<Metadata>
568+
where
569+
R: io::AsyncReadExt + Unpin,
570+
{
571+
let io_err = |err| crate::Error::io(err, "decoding metadata");
572+
let nbytes = reader.read(fsm.space()).await.map_err(io_err)?;
573+
fsm.fill(nbytes);
574+
loop {
575+
match fsm.process() {
576+
ProcessResult::ReadMore(n) => {
577+
// asm guarantees there's at least `n` bytes available in `space()`
578+
let mut total_read = 0;
579+
loop {
580+
let read = reader.read(fsm.space()).await.map_err(io_err)?;
581+
if read == 0 {
582+
return Err(crate::Error::io(
583+
io::Error::from(io::ErrorKind::UnexpectedEof),
584+
"decoding metadata",
585+
));
586+
}
587+
fsm.fill(read);
588+
total_read += read;
589+
if total_read >= n {
590+
break;
591+
}
592+
}
593+
}
594+
ProcessResult::Metadata(metadata) => return Ok(metadata),
595+
ProcessResult::Record(_) => panic!("DBN metadata should precede records"),
596+
ProcessResult::Err(error) => return Err(error),
597+
}
598+
}
599+
}
600+
601+
/// A low-level function for decoding the next [`RecordRef`]. Returns `None` if `reader`
602+
/// has been exhausted.
603+
///
604+
/// Generally [`Decoder`] and [`RecordDecoder`] should be used instead of this function.
605+
///
606+
/// # Errors
607+
/// This function returns an error if the underlying reader returns an
608+
/// error of a kind other than `io::ErrorKind::UnexpectedEof` upon reading.
609+
/// It will also return an error if it encounters an invalid record.
610+
///
611+
/// # Cancel safety
612+
/// This method is cancel safe. It can be used within a `tokio::select!` statement
613+
/// without the potential for corrupting the input stream.
614+
///
615+
/// # Panics
616+
/// This function will panic if it encounters DBN metadata. The caller must ensure
617+
/// the metadata has already been decoded.
618+
pub async fn decode_record_ref_with_fsm<'a, R>(
619+
mut reader: R,
620+
fsm: &'a mut DbnFsm,
621+
) -> Result<Option<RecordRef<'a>>>
622+
where
623+
R: io::AsyncReadExt + Unpin,
624+
{
625+
loop {
626+
match fsm.process() {
627+
ProcessResult::ReadMore(_) => match reader.read(fsm.space()).await {
628+
Ok(0) => return Ok(None),
629+
Ok(nbytes) => {
630+
fsm.fill(nbytes);
631+
}
632+
Err(err) if err.kind() == std::io::ErrorKind::UnexpectedEof => {
633+
return Ok(None);
634+
}
635+
Err(err) => {
636+
return Err(crate::Error::io(err, "decoding record reference"));
637+
}
638+
},
639+
ProcessResult::Record(_) => return Ok(fsm.last_record()),
640+
ProcessResult::Err(error) => return Err(error),
641+
ProcessResult::Metadata(_) => panic!("found DBN metadata when expected records"),
642+
}
643+
}
644+
}
645+
571646
#[cfg(test)]
572647
mod tests {
573648
#![allow(clippy::clone_on_copy)]
@@ -770,7 +845,7 @@ mod tests {
770845
VersionUpgradePolicy::UpgradeToV2,
771846
)
772847
.await?;
773-
assert_eq!(decoder.metadata().version, crate::DBN_VERSION);
848+
assert_eq!(decoder.metadata().version, 2);
774849
assert_eq!(decoder.metadata().symbol_cstr_len, crate::SYMBOL_CSTR_LEN);
775850
let mut has_decoded = false;
776851
while let Some(_rec) = decoder.decode_record::<v2::InstrumentDefMsg>().await? {

0 commit comments

Comments
 (0)