Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
9096533
feat: add decimal support to buffer
RaphDal Oct 10, 2025
74f6f62
docs: add decimal in rust examples
RaphDal Oct 10, 2025
3c87c76
docs: improve doc for decimal
RaphDal Oct 10, 2025
2883ea2
fix: restrict visibility of must_escape_unquoted to the crate only
RaphDal Oct 10, 2025
457b77a
fix: improve binary compatibility check
RaphDal Oct 10, 2025
3d8645b
fix: satisfy clippy with feature-gated usage of DECIMAL_BINARY_FORMAT…
RaphDal Oct 10, 2025
139e92d
docs: set diagram comment as text
RaphDal Oct 10, 2025
4bf9bfc
docs: removed unnecessary comment
RaphDal Oct 10, 2025
17f699b
docs: fix comment typo
RaphDal Oct 10, 2025
a583839
docs: fix buffer decimal examples
RaphDal Oct 10, 2025
c7f19af
feat: added support for C and C++
RaphDal Oct 10, 2025
6cc4237
fix: add missing error codes to C and C++ headers
RaphDal Oct 10, 2025
4aa3b14
fix: update type hints to include decimal in python test
RaphDal Oct 10, 2025
fb8ac1e
ci: trigger
RaphDal Oct 10, 2025
2095903
tests: add decimal support to ilp-client-interop-test
RaphDal Oct 13, 2025
984a236
feat: improve cpp decimal integration to cpp
RaphDal Oct 14, 2025
ce0fe17
typo: fix invalid comments
RaphDal Oct 14, 2025
d2ee26c
feat: add protocol version 3
RaphDal Oct 14, 2025
4c4b9af
revert: revert removal of implicit coercion of decimal binary views
RaphDal Oct 14, 2025
4a6a07c
feat: generalize array customization point
RaphDal Oct 14, 2025
cf425b5
typo: remove garbage
RaphDal Oct 14, 2025
2180a9f
docs: remove no longer true comment
RaphDal Oct 14, 2025
14b54eb
feat: improved decimal string validation
RaphDal Oct 15, 2025
fe7cceb
feat: add customization point for decimal
RaphDal Oct 15, 2025
44fe751
fix: accept more characters in decimal serializer for str to allow na…
RaphDal Oct 15, 2025
1b69994
tests: update test to use protocol version 3
RaphDal Oct 15, 2025
36f534e
docs: add decimal to c/cpp docs
RaphDal Oct 15, 2025
b11912a
tests: update rust examples to use protocol version 3
RaphDal Oct 15, 2025
147bab4
feat: add protocol version 3 to supported protocol versions
RaphDal Oct 15, 2025
77520ef
docs: add a simple description about the decimal datatype to ingress
RaphDal Oct 15, 2025
d9ef50d
fix: change port variable type to String for consistency
RaphDal Oct 15, 2025
6d19bd9
fix: change to_array_view_state_impl argument to const reference
RaphDal Oct 15, 2025
f47ee4f
fix: update usage message to line_sender_c_example_decimal_custom
RaphDal Oct 15, 2025
23d51b2
fix: remove unused includes from line_sender_array.hpp
RaphDal Oct 15, 2025
1f889e7
fix: update scale validation in DecimalSerializer to allow negative s…
RaphDal Oct 15, 2025
466b26d
fix: update protocol version to v3 in line_sender tests and header
RaphDal Oct 15, 2025
8dad866
fix: remove unused price_value assignment in line_sender_c_example_de…
RaphDal Oct 15, 2025
de1bba3
fix: update usage message to reflect correct example name in line_sen…
RaphDal Oct 15, 2025
12882be
fix: update price column type and correct table name in protocol_vers…
RaphDal Oct 15, 2025
cf1dd4d
Merge branch 'main' into rd_decimal
RaphDal Oct 15, 2025
f1624df
fix: free line sender buffer after flushing in example files
RaphDal Oct 16, 2025
9d564a8
fix: qualify binary_view with questdb::ingress::decimal namespace
RaphDal Oct 16, 2025
e63da89
fix: update protocol version to 2 in line sender examples
RaphDal Oct 16, 2025
ed83ff2
fix: update protocol version checks to use comparison operators
RaphDal Oct 17, 2025
ed7a984
fix: add PartialOrd to ProtocolVersion
RaphDal Oct 17, 2025
727cf09
fix: rename binary_view to decimal_view in decimal namespace
RaphDal Oct 17, 2025
d730bad
fix: remove supports method from ProtocolVersion implementation
RaphDal Oct 17, 2025
e6140e3
fix: clarify error messages for decimal scale and byte length limits
RaphDal Oct 17, 2025
1ebc6e6
typo: add missing definite article in ingress documentation
RaphDal Oct 17, 2025
0764846
fix: expose decimal_view in line_sender.hpp
RaphDal Oct 17, 2025
e2fa211
fix: cast ProtocolVersion to u8 for comparison in json_tests
RaphDal Oct 17, 2025
3bc2e3a
refactor: split headers
RaphDal Oct 17, 2025
17f2427
fix: improve error message for unsupported client protocol version
RaphDal Oct 17, 2025
af76951
Merge branch 'main' into rd_decimal
RaphDal Oct 17, 2025
9489414
fix: clarify implementation details for DecimalSerializer
RaphDal Oct 17, 2025
ba6744c
fix: correct wording in protocol version documentation for clarity
RaphDal Oct 17, 2025
61f16a7
fix: improve error messages for decimal scale and value length constr…
RaphDal Oct 17, 2025
61ff7ec
fix: correct order of fields in binary serialization format documenta…
RaphDal Oct 17, 2025
59cb32e
Merge branch 'main' into rd_decimal
RaphDal Oct 17, 2025
b2cedfd
fix: handle null data in line_sender_buffer_column_dec function
RaphDal Oct 17, 2025
1ede6d7
fix: remove debug output for protocol version in SenderBuilder
RaphDal Oct 17, 2025
e86a18d
fix: update usage message to include program name in help output
RaphDal Oct 17, 2025
5b17715
fix: use http instead of tcp in decimal examples
RaphDal Oct 17, 2025
76104aa
refactor: replace DecimalSerializer with DecimalView
RaphDal Oct 21, 2025
fec2164
test: add tests for NaN and Infinity decimal representations
RaphDal Oct 21, 2025
dfb9fd4
fix: correct spelling of 'Infinity' in validation comments
RaphDal Oct 21, 2025
23329b0
fix: improve error message for invalid decimal strings
RaphDal Oct 21, 2025
73d9f0d
fix: correct scale type in DecimalView conversion
RaphDal Oct 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions questdb-rs-ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,9 @@ pub enum line_sender_error_code {

/// Line sender protocol version error.
line_sender_error_protocol_version_error,

/// The supplied decimal is invalid.
line_sender_error_invalid_decimal,
}

impl From<ErrorCode> for line_sender_error_code {
Expand Down Expand Up @@ -252,6 +255,9 @@ impl From<ErrorCode> for line_sender_error_code {
ErrorCode::ProtocolVersionError => {
line_sender_error_code::line_sender_error_protocol_version_error
}
ErrorCode::InvalidDecimal => {
line_sender_error_code::line_sender_error_invalid_decimal
}
}
}
}
Expand Down
34 changes: 28 additions & 6 deletions questdb-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,26 @@ itoa = "1.0"
aws-lc-rs = { version = "1.13", optional = true }
ring = { version = "0.17.14", optional = true }
rustls-pki-types = "1.0.1"
rustls = { version = "0.23.25", default-features = false, features = ["logging", "std", "tls12"] }
rustls = { version = "0.23.25", default-features = false, features = [
"logging",
"std",
"tls12",
] }
rustls-native-certs = { version = "0.8.1", optional = true }
webpki-roots = { version = "1.0.1", default-features = false, optional = true }
chrono = { version = "0.4.40", optional = true }

# We need to limit the `ureq` version to 3.0.x since we use
# the `ureq::unversioned` module which does not respect semantic versioning.
ureq = { version = "3.0.10, <3.1.0", default-features = false, features = ["_tls"], optional = true }
ureq = { version = "3.0.10, <3.1.0", default-features = false, features = [
"_tls",
], optional = true }
serde_json = { version = "1", optional = true }
questdb-confstr = "0.1.1"
rand = { version = "0.9.0", optional = true }
ndarray = { version = "0.16", optional = true }
rust_decimal = { version = "1.38.0", optional = true }
bigdecimal = { version = "0.4.8", optional = true }

[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.9", features = ["ws2def"] }
Expand Down Expand Up @@ -68,7 +76,13 @@ sync-sender = ["sync-sender-tcp", "sync-sender-http"]
sync-sender-tcp = ["_sync-sender", "_sender-tcp", "dep:socket2"]

## Sync ILP/HTTP
sync-sender-http = ["_sync-sender", "_sender-http", "dep:ureq", "dep:serde_json", "dep:rand"]
sync-sender-http = [
"_sync-sender",
"_sender-http",
"dep:ureq",
"dep:serde_json",
"dep:rand",
]

## Allow use OS-provided root TLS certificates
tls-native-certs = ["dep:rustls-native-certs"]
Expand All @@ -91,6 +105,12 @@ json_tests = []
## Enable methods to create timestamp objects from chrono::DateTime objects.
chrono_timestamp = ["chrono"]

## Enable serialization of rust_decimal::Decimal in ILP
rust_decimal = ["dep:rust_decimal"]

## Enable serialization of bigdecimal::BigDecimal in ILP
bigdecimal = ["dep:bigdecimal"]

# Hidden derived features, used in code to enable-disable code sections. Don't use directly.
_sender-tcp = []
_sender-http = []
Expand All @@ -109,7 +129,9 @@ almost-all-features = [
"insecure-skip-verify",
"json_tests",
"chrono_timestamp",
"ndarray"
"ndarray",
"rust_decimal",
"bigdecimal",
]

[[example]]
Expand All @@ -126,8 +148,8 @@ required-features = ["chrono_timestamp"]

[[example]]
name = "http"
required-features = ["sync-sender-http", "ndarray"]
required-features = ["sync-sender-http", "ndarray", "rust_decimal"]

[[example]]
name = "protocol_version"
required-features = ["sync-sender-http", "ndarray"]
required-features = ["sync-sender-http", "ndarray", "bigdecimal"]
4 changes: 2 additions & 2 deletions questdb-rs/examples/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ use questdb::{
fn main() -> Result<()> {
let host: String = std::env::args().nth(1).unwrap_or("localhost".to_string());
let port: &str = &std::env::args().nth(2).unwrap_or("9009".to_string());
let mut sender = Sender::from_conf(format!("tcp::addr={host}:{port};"))?;
let mut sender = Sender::from_conf(format!("tcp::addr={host}:{port};protocol_version=2;"))?;
let mut buffer = sender.new_buffer();
let designated_timestamp =
TimestampNanos::from_datetime(Utc.with_ymd_and_hms(1997, 7, 4, 4, 56, 55).unwrap())?;
buffer
.table("trades")?
.symbol("symbol", "ETH-USD")?
.symbol("side", "sell")?
.column_f64("price", 2615.54)?
.column_decimal("price", "2615.54")?
.column_f64("amount", 0.00044)?
// QuestDB server version 9.0.0 or later is required for array support.
.column_arr("location", &arr1(&[100.0, 100.1, 100.2]).view())?
Expand Down
10 changes: 8 additions & 2 deletions questdb-rs/examples/http.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
use std::str::FromStr;

use ndarray::arr1;
use questdb::{
ingress::{Sender, TimestampNanos},
Result,
};
use rust_decimal::Decimal;

fn main() -> Result<()> {
let mut sender = Sender::from_conf("https::addr=localhost:9000;username=foo;password=bar;")?;
let mut sender = Sender::from_conf(
"https::addr=localhost:9000;username=foo;password=bar;protocol_version=2;",
)?;
let mut buffer = sender.new_buffer();
let price = Decimal::from_str("2615.54").unwrap();
buffer
.table("trades")?
.symbol("symbol", "ETH-USD")?
.symbol("side", "sell")?
.column_f64("price", 2615.54)?
.column_decimal("price", &price)?
.column_f64("amount", 0.00044)?
// QuestDB server version 9.0.0 or later is required for array support.
.column_arr("location", &arr1(&[100.0, 100.1, 100.2]).view())?
Expand Down
15 changes: 10 additions & 5 deletions questdb-rs/examples/protocol_version.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,38 @@
use std::str::FromStr;

use bigdecimal::BigDecimal;
use ndarray::arr1;
use questdb::{
ingress::{Sender, TimestampNanos},
Result,
};

fn main() -> Result<()> {
let price = BigDecimal::from_str("2615.54").unwrap();

let mut sender = Sender::from_conf(
"https::addr=localhost:9000;username=foo;password=bar;protocol_version=1;",
"http::addr=localhost:9000;username=foo;password=bar;protocol_version=1;",
)?;
let mut buffer = sender.new_buffer();
buffer
.table("trades_ilp_v1")?
.symbol("symbol", "ETH-USD")?
.symbol("side", "sell")?
.column_f64("price", 2615.54)?
.column_decimal("price", &price)?
.column_f64("amount", 0.00044)?
.at(TimestampNanos::now())?;
sender.flush(&mut buffer)?;

// QuestDB server version 9.0.0 or later is required for `protocol_version=2` support.
let mut sender2 = Sender::from_conf(
"https::addr=localhost:9000;username=foo;password=bar;protocol_version=2;",
"http::addr=localhost:9000;username=foo;password=bar;protocol_version=2;",
)?;
let mut buffer2 = sender.new_buffer();
let mut buffer2 = sender2.new_buffer();
buffer2
.table("trades_ilp_v2")?
.symbol("symbol", "ETH-USD")?
.symbol("side", "sell")?
.column_f64("price", 2615.54)?
.column_decimal("price", &price)?
.column_f64("amount", 0.00044)?
.column_arr("location", &arr1(&[100.0, 100.1, 100.2]).view())?
.at(TimestampNanos::now())?;
Expand Down
3 changes: 3 additions & 0 deletions questdb-rs/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ pub enum ErrorCode {

/// Validate protocol version error.
ProtocolVersionError,

/// The supplied decimal is invalid.
InvalidDecimal,
}

/// An error that occurred when using QuestDB client library.
Expand Down
89 changes: 88 additions & 1 deletion questdb-rs/src/ingress/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
* limitations under the License.
*
******************************************************************************/
use crate::ingress::decimal::DecimalSerializer;
use crate::ingress::ndarr::{check_and_get_array_bytes_size, ArrayElementSealed};
use crate::ingress::{
ndarr, ArrayElement, DebugBytes, NdArrayView, ProtocolVersion, Timestamp, TimestampNanos,
Expand Down Expand Up @@ -71,7 +72,7 @@ where
quoting_fn(output);
}

fn must_escape_unquoted(c: u8) -> bool {
pub(crate) fn must_escape_unquoted(c: u8) -> bool {
matches!(c, b' ' | b',' | b'=' | b'\n' | b'\r' | b'\\')
}

Expand Down Expand Up @@ -974,6 +975,92 @@ impl Buffer {
Ok(self)
}

/// Record a decimal value for the given column.
///
/// ```no_run
/// # use questdb::Result;
/// # use questdb::ingress::{Buffer, SenderBuilder};
/// # fn main() -> Result<()> {
/// # let mut sender = SenderBuilder::from_conf("https::addr=localhost:9000;")?.build()?;
/// # let mut buffer = sender.new_buffer();
/// # buffer.table("x")?;
/// buffer.column_decimal("col_name", "123.45")?;
/// # Ok(())
/// # }
/// ```
///
/// or
///
/// ```no_run
/// # use questdb::Result;
/// # use questdb::ingress::{Buffer, SenderBuilder};
/// use questdb::ingress::ColumnName;
///
/// # fn main() -> Result<()> {
/// # let mut sender = SenderBuilder::from_conf("https::addr=localhost:9000;")?.build()?;
/// # let mut buffer = sender.new_buffer();
/// # buffer.table("x")?;
/// let col_name = ColumnName::new("col_name")?;
/// buffer.column_decimal(col_name, "123.45")?;
/// # Ok(())
/// # }
/// ```
///
/// With `rust_decimal` feature enabled:
///
/// ```no_run
/// # #[cfg(feature = "rust_decimal")]
/// # {
/// # use questdb::Result;
/// # use questdb::ingress::{Buffer, SenderBuilder};
/// use rust_decimal::Decimal;
/// use std::str::FromStr;
///
/// # fn main() -> Result<()> {
/// # let mut sender = SenderBuilder::from_conf("https::addr=localhost:9000;")?.build()?;
/// # let mut buffer = sender.new_buffer();
/// # buffer.table("x")?;
/// let value = Decimal::from_str("123.45")?;
/// buffer.column_decimal("col_name", &value)?;
/// # Ok(())
/// # }
/// # }
/// ```
///
/// With `bigdecimal` feature enabled:
///
/// ```no_run
/// # #[cfg(feature = "bigdecimal")]
/// # {
/// # use questdb::Result;
/// # use questdb::ingress::{Buffer, SenderBuilder};
/// use bigdecimal::BigDecimal;
/// use std::str::FromStr;
///
/// # fn main() -> Result<()> {
/// # let mut sender = SenderBuilder::from_conf("https::addr=localhost:9000;")?.build()?;
/// # let mut buffer = sender.new_buffer();
/// # buffer.table("x")?;
/// let value = BigDecimal::from_str("0.123456789012345678901234567890")?;
/// buffer.column_decimal("col_name", &value)?;
/// # Ok(())
/// # }
/// # }
/// ```
pub fn column_decimal<'a, N, S>(&mut self, name: N, value: S) -> crate::Result<&mut Self>
where
N: TryInto<ColumnName<'a>>,
S: DecimalSerializer,
Error: From<N::Error>,
{
self.write_column_key(name)?;
value.serialize(
&mut self.output,
self.protocol_version.supports_binary_encoding(),
)?;
Ok(self)
}

/// Record a multidimensional array value for the given column.
///
/// Supports arrays with up to [`MAX_ARRAY_DIMS`] dimensions. The array elements must
Expand Down
Loading