Skip to content

Conversation

RaphDal
Copy link

@RaphDal RaphDal commented Oct 10, 2025

This PR adds decimal support.

This is a tandem pr for:

Rust support

Decimals can be recorded with the column_decimal method in Buffer.

Supported types

  1. &str - Always uses text format
  2. &rust_decimal::Decimal (feature: rust_decimal): text and binary formats.
  3. &bigdecimal::BigDecimal (feature: bigdecimal): text and binary formats.

Usage

C

buffer = line_sender_buffer_new_for_sender(sender); // ... line_sender_column_name price_name = QDB_COLUMN_NAME_LITERAL("price"); line_sender_utf8 price_value = QDB_UTF8_LITERAL("2615.54"); if (!line_sender_buffer_column_decimal_str( buffer, price_name, price_value, &err)) goto on_error;

C++

const auto table_name = "cpp_trades"_tn; const auto symbol_name = "symbol"_cn; const auto side_name = "side"_cn; const auto price_name = "price"_cn; const auto amount_name = "amount"_cn; questdb::ingress::line_sender_buffer buffer = sender.new_buffer(); buffer.table(table_name) .symbol(symbol_name, "ETH-USD"_utf8) .symbol(side_name, "sell"_utf8) .column_decimal(price_name, "2615.54"_utf8) .column(amount_name, 0.00044) .at(questdb::ingress::timestamp_nanos::now());

Rust - Basic Usage

use questdb::ingress::{Buffer, ProtocolVersion}; let mut buffer = Buffer::new(ProtocolVersion::V1); buffer .table("prices")? .symbol("symbol", "AAPL")? .column_decimal("price", "123.45")? .at_now()?;

Rust - With rust_decimal

use rust_decimal::Decimal; use std::str::FromStr; let mut buffer = Buffer::new(ProtocolVersion::V2); let price = Decimal::from_str("123.45")?; buffer .table("prices")? .column_decimal("price", &price)? .at_now()?;

Rust - With bigdecimal

use bigdecimal::BigDecimal; use std::str::FromStr; let mut buffer = Buffer::new(ProtocolVersion::V2); let price = BigDecimal::from_str("0.123456789012345678901234567890")?; // High precision buffer .table("prices")? .column_decimal("price", &price)? .at_now()?;

Progress

  • rust
  • c
  • c++
  • support decimal from interop tests

Summary by CodeRabbit

  • New Features

    • Native Decimal datatype support (text & binary) and Protocol v3 for decimal transport; new invalid-decimal error and public APIs to send decimals.
  • Documentation

    • Added Decimal datatype docs, protocol v3 guidance, and updated examples demonstrating decimal usage.
  • Examples

    • Updated and new C/C++/Rust examples showing decimal usage and Protocol v3; examples free buffers on success.
  • Tests

    • Extensive decimal serialization, buffer integration, interop, and protocol negotiation tests added.
  • Chores

    • Optional decimal libraries exposed via feature flags.
@RaphDal RaphDal added DO NOT MERGE This PR should NOT be merged tandem labels Oct 10, 2025
Copy link

coderabbitai bot commented Oct 10, 2025

Walkthrough

Adds Protocol Version V3 and decimal support across Rust, FFI (C), C++ and tests: new decimal serialization modules (text + optional binary), Buffer::column_dec and FFI/C++ decimal APIs, negotiation updated to include V3, Cargo features for rust_decimal/bigdecimal, and updated examples/tests.

Changes

Cohort / File(s) Summary
FFI & helper
questdb-rs-ffi/src/lib.rs, questdb-rs-ffi/src/decimal.rs
New FFI decimal module and Decimal type; binary ILP decimal serialization; expose line_sender_buffer_column_dec_str and line_sender_buffer_column_dec; add line_sender_error_invalid_decimal and map ErrorCode::InvalidDecimal.
Rust ingress core
questdb-rs/src/ingress/decimal.rs, questdb-rs/src/ingress/buffer.rs, questdb-rs/src/ingress/mod.rs, questdb-rs/src/ingress/mod.md
Add DecimalSerializer trait and &str impl; feature-gated binary impls for rust_decimal/bigdecimal; add Buffer::column_dec; expose DECIMAL_BINARY_FORMAT_TYPE; add ProtocolVersion::V3, SUPPORTED_PROTOCOL_VERSIONS, and update negotiation/docs.
Cargo / features
questdb-rs/Cargo.toml
Add optional rust_decimal and bigdecimal deps and feature flags; include them in almost-all-features; update example feature requirements and json_tests.
Rust examples
questdb-rs/examples/*
Update examples to use protocol_version=3, import decimal types, and replace column_f64 with column_dec/decimal usage.
Tests & test infra
questdb-rs/src/tests/decimal.rs, questdb-rs/src/tests/mod.rs, questdb-rs/src/tests/interop/ilp-client-interop-test.json, questdb-rs/build.rs
Add extensive decimal unit tests and interop case; test-gen updated to emit decimal columns and respect minimum protocol version; tests gated by features.
HTTP / protocol parsing
questdb-rs/src/ingress/sender/http.rs, questdb-rs/src/tests/http.rs
Recognize numeric 3 in server-supported protocol versions; adjust test mocks/expectations.
FFI headers / public C API
include/questdb/ingress/line_sender.h
Add line_sender_error_invalid_decimal, line_sender_protocol_version_3, and C APIs line_sender_buffer_column_dec_str and line_sender_buffer_column_dec; update docs.
C++ API & headers
include/questdb/ingress/line_sender.hpp, include/questdb/ingress/line_sender_core.hpp, include/questdb/ingress/line_sender_decimal.hpp, include/questdb/ingress/line_sender_array.hpp
New decimal views (decimal_str_view, decimal_view), _decimal literal, customization point for decimal views; protocol_version::v3 and invalid_decimal error added; header reorg and new array view APIs.
C / C++ examples
examples/line_sender_c_example*.c, examples/line_sender_cpp_example*.cpp, examples/line_sender_c_example_decimal_binary.c, examples/line_sender_cpp_example_decimal_*.cpp
Add decimal text/binary examples; many examples switch to protocol_version=3; replace f64 writes with decimal APIs; add buffer free on success in C examples.
Python system tests / bindings
system_test/questdb_line_sender.py, system_test/test.py
Register line_sender_buffer_column_dec_str DLL binding; accept Decimal in Buffer/Sender.column unions; add DECIMAL_RELEASE and test_decimal_column; protocol negotiation may select V3.
Build / CI & C++ tests
CMakeLists.txt, cpp_test/test_line_sender.cpp, various examples/*array* files
Add build targets for new C++ decimal examples; bump protocol_version literals to 3 in tests/examples; minor formatting and cleanup.

Sequence Diagram(s)

sequenceDiagram autonumber participant App as Client/App participant Sender participant Buffer participant Serializer participant Network App->>Sender: create(from_conf protocol_version=3) Sender->>Network: negotiate protocol Network-->>Sender: supports [V3, V2, V1] Sender->>Sender: select V3 App->>Buffer: column_dec("price", value) Buffer->>Serializer: DecimalSerializer::serialize(value, out) alt text path (UTF-8) Serializer-->>Buffer: append digits + 'd' else binary path (feature-gated) Serializer-->>Buffer: append '=' + type(23) + scale + len + mantissa end Buffer->>Sender: flush() Sender->>Network: send ILP payload (includes decimal bytes) 
Loading
sequenceDiagram autonumber participant CApp as C/C++ App participant LineBuffer participant FFI as C ABI participant FfiDecimal as FFI Decimal Serializer CApp->>LineBuffer: prepare decimal (text or binary) CApp->>FFI: line_sender_buffer_column_dec_str(...) or line_sender_buffer_column_dec(...) FFI->>FfiDecimal: validate & encode alt ok FfiDecimal-->>LineBuffer: append bytes LineBuffer-->>CApp: success else invalid FfiDecimal-->>FFI: set error (line_sender_error_invalid_decimal) FFI-->>CApp: false / error end 
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

🐇

I hopped through bytes with careful feet,
Text and binary made my treat,
V3 arrived and snugged the code,
Decimals prance along the road,
Tests and examples nibble the feat.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.88% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "feat: support decimal" directly and specifically relates to the main objective of the changeset, which is to add comprehensive decimal support across C, C++, and Rust clients. The title clearly communicates the primary change—introducing decimal functionality—through the specific term "decimal" rather than using vague language. A teammate reviewing the Git history would immediately understand that this PR adds decimal support, which aligns with the substantial implementation that includes new Buffer.column_dec APIs, ProtocolVersion::V3, error handling for decimals, and supporting examples and tests across all language bindings.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch rd_decimal

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
questdb-rs/src/ingress/buffer.rs (1)

978-1063: column_decimal: enable binary for future versions and update docs

  • Gate binary like other binary-capable types: treat any non‑V1 as binary (future-proof, consistent with column_f64/column_arr).
  • Add column_decimal to the method table in the docs.

Apply this minimal change for gating:

- value.serialize( - &mut self.output, - self.protocol_version == ProtocolVersion::V2, - )?; + value.serialize( + &mut self.output, + self.protocol_version != ProtocolVersion::V1, + )?;

Doc table addition (outside this hunk): add a row

  • | column_decimal | DECIMAL |
    to the “Buffer method calls, Serialized ILP types and QuestDB types” table.
questdb-rs/src/ingress/decimal.rs (1)

180-234: bigdecimal binary path looks correct; consider aligning length strategy

Implementation uses minimal two’s complement via to_signed_bytes_be with scale checks. For consistency, consider emitting minimal-length mantissa for rust_decimal too (not a blocker).

questdb-rs/src/tests/decimal.rs (1)

602-611: Avoid hard-coded offset when slicing the binary decimal payload

Using a fixed 17-byte offset assumes specific table/symbol names. Compute the start dynamically to make tests robust.

Example change:

- // Skip the prefix and \n suffix - let dec_binary = &bytes[17..bytes.len() - 1]; + // Find " dec=" prefix and slice from there (exclude trailing '\n') + let prefix = b" dec="; + let start = bytes + .windows(prefix.len()) + .position(|w| w == prefix) + .expect("missing ' dec=' prefix") + + prefix.len(); + let dec_binary = &bytes[start..bytes.len() - 1];

Apply similarly in the bigdecimal v2 test.

Also applies to: 685-691

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b3f08fa and 2883ea2.

📒 Files selected for processing (11)
  • questdb-rs-ffi/src/lib.rs (2 hunks)
  • questdb-rs/Cargo.toml (5 hunks)
  • questdb-rs/examples/basic.rs (1 hunks)
  • questdb-rs/examples/http.rs (1 hunks)
  • questdb-rs/examples/protocol_version.rs (1 hunks)
  • questdb-rs/src/error.rs (1 hunks)
  • questdb-rs/src/ingress/buffer.rs (3 hunks)
  • questdb-rs/src/ingress/decimal.rs (1 hunks)
  • questdb-rs/src/ingress/mod.rs (2 hunks)
  • questdb-rs/src/tests/decimal.rs (1 hunks)
  • questdb-rs/src/tests/mod.rs (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
questdb-rs/examples/http.rs (3)
questdb-rs/examples/basic.rs (1)
  • main (8-34)
questdb-rs/examples/protocol_version.rs (1)
  • main (10-41)
questdb-rs/src/ingress/mod.rs (1)
  • from_conf (390-544)
questdb-rs/examples/protocol_version.rs (3)
questdb-rs/examples/basic.rs (1)
  • main (8-34)
questdb-rs/examples/http.rs (1)
  • main (10-27)
questdb-rs/src/ingress/mod.rs (1)
  • from_conf (390-544)
questdb-rs/src/tests/decimal.rs (2)
questdb-rs/src/ingress/buffer.rs (7)
  • new (97-102)
  • new (181-188)
  • new (204-263)
  • new (303-350)
  • new (486-488)
  • len (523-525)
  • with_max_name_len (500-508)
questdb-rs/src/error.rs (3)
  • new (95-100)
  • code (128-130)
  • msg (133-135)
questdb-rs/src/ingress/buffer.rs (1)
system_test/questdb_line_sender.py (1)
  • ProtocolVersion (101-120)
questdb-rs/examples/basic.rs (2)
questdb-rs/src/ingress/mod.rs (1)
  • from_conf (390-544)
questdb-rs/src/ingress/timestamp.rs (2)
  • from_datetime (130-132)
  • from_datetime (211-220)
questdb-rs/src/ingress/decimal.rs (1)
questdb-rs/src/ingress/buffer.rs (1)
  • must_escape_unquoted (75-77)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: questdb.c-questdb-client (Building and testing on windows-msvc-2019)
  • GitHub Check: questdb.c-questdb-client (Building and testing on windows-msvc-2022)
  • GitHub Check: questdb.c-questdb-client (Building and testing on mac)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux-nightly)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux-beta)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux-stable)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux)
  • GitHub Check: questdb.c-questdb-client (Building and testing cargo fmt and clippy)
  • GitHub Check: questdb.c-questdb-client (Building and testing Vs QuestDB 'master')
🔇 Additional comments (14)
questdb-rs/Cargo.toml (2)

49-50: LGTM: Decimal dependency versions are current and properly configured.

The versions align with the latest stable releases documented in the learnings. Both crates are correctly marked as optional dependencies to support feature-gated decimal serialization.

Based on learnings.


108-112: LGTM: Feature declarations follow best practices.

The feature flags are properly structured to expose the optional decimal dependencies, allowing users to opt-in based on their precision requirements (rust_decimal for fixed ~28-digit precision, bigdecimal for arbitrary precision).

Based on learnings.

questdb-rs/src/tests/mod.rs (1)

33-33: LGTM: Test module integration is correct.

The decimal test module declaration follows the existing pattern and properly extends the test suite to cover the new decimal serialization functionality.

questdb-rs-ffi/src/lib.rs (1)

228-229: LGTM: FFI error code integration follows established patterns.

The new line_sender_error_invalid_decimal variant and its mapping at lines 258-260 are consistent with the existing error handling architecture, ensuring proper error propagation across the FFI boundary.

questdb-rs/examples/basic.rs (1)

11-11: LGTM: Example correctly demonstrates decimal serialization.

The protocol version 2 configuration enables the decimal serialization pathway, and the string literal approach ("2615.54") demonstrates the simplest usage pattern requiring no additional dependencies. This complements the other examples that showcase the rust_decimal and bigdecimal integrations.

Also applies to: 19-19

questdb-rs/examples/protocol_version.rs (2)

1-3: LGTM: BigDecimal integration demonstrates arbitrary-precision decimal support.

The example properly showcases bigdecimal usage for high-precision decimal values, parsing from string and using the same value across both protocol versions. This complements the rust_decimal example in http.rs by demonstrating the alternative arbitrary-precision approach.

Based on learnings.

Also applies to: 11-11, 21-21, 35-35


30-30: LGTM: Corrected buffer creation.

The change from sender.new_buffer() to sender2.new_buffer() ensures the buffer uses the correct sender's protocol settings, preventing potential configuration mismatches between the two senders.

questdb-rs/src/error.rs (1)

82-83: LGTM: Error code addition is well-integrated.

The InvalidDecimal variant follows the established pattern and is properly positioned in the error code enumeration. The clear documentation ensures users understand when this error occurs during decimal serialization.

questdb-rs/examples/http.rs (1)

1-2: LGTM: Example properly demonstrates rust_decimal integration.

The example correctly showcases rust_decimal::Decimal usage with protocol version 2, demonstrating the fixed-precision decimal approach (~28 significant digits). This is appropriate for financial use cases as noted in the learnings.

Based on learnings.

Also applies to: 8-8, 15-15, 20-20

questdb-rs/src/ingress/mod.rs (1)

65-66: LGTM: Decimal module integration is clean and minimal.

The decimal module is properly integrated into the ingress API surface, exposing DecimalSerializer as a public trait. The DECIMAL_BINARY_FORMAT_TYPE constant (value 23) follows the pattern of existing format type constants and coordinates with the server-side implementation for protocol version 2 binary serialization.

Also applies to: 77-77

questdb-rs/src/ingress/buffer.rs (1)

75-77: Visibility change looks good

Making must_escape_unquoted pub(crate) enables reuse from decimal.rs without widening the public API.

questdb-rs/src/ingress/decimal.rs (2)

103-126: &str serializer is correct and lean

Validation against ILP-reserved bytes and appending the 'd' suffix is appropriate; text path ignores the binary flag as intended.


25-26: Import path is correct must_escape_unquoted is re-exported via pub use buffer::*; in src/ingress/mod.rs, so the existing import in decimal.rs compiles as-is.

Likely an incorrect or invalid review comment.

questdb-rs/src/tests/decimal.rs (1)

43-101: Test coverage is thorough

Covers text/binary paths, error cases, and buffer integration across features. Nice.

Also applies to: 112-137, 139-181, 187-326, 332-534, 540-567, 615-630, 632-647, 648-665, 667-692

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
questdb-rs/src/ingress/decimal.rs (1)

167-170: Fix the misleading comment (duplicate of past review).

The comment on line 167 says "We skip the upper 3 bytes" but line 170 says "Skip upper 4 bytes, write lower 12". As discussed in the previous review, the code correctly writes 13 bytes (via [3..]), which includes one sign-extension byte needed for sign detection plus the 12-byte mantissa.

Apply this diff to correct the comment:

- // We skip the upper 3 bytes (which are sign-extended) and write the lower 13 bytes + // Skip the upper 3 sign-extended bytes and write the lower 13 bytes (1 sign byte + 12 mantissa) let mantissa = self.mantissa(); out.push(13); - out.extend_from_slice(&mantissa.to_be_bytes()[3..]); // Skip upper 4 bytes, write lower 12 + out.extend_from_slice(&mantissa.to_be_bytes()[3..]);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 457b77a and 3d8645b.

📒 Files selected for processing (3)
  • questdb-rs/src/ingress/decimal.rs (1 hunks)
  • questdb-rs/src/ingress/mod.rs (3 hunks)
  • questdb-rs/src/tests/decimal.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • questdb-rs/src/ingress/mod.rs
🧰 Additional context used
🧬 Code graph analysis (2)
questdb-rs/src/ingress/decimal.rs (1)
questdb-rs/src/ingress/buffer.rs (1)
  • must_escape_unquoted (75-77)
questdb-rs/src/tests/decimal.rs (2)
questdb-rs/src/error.rs (3)
  • new (95-100)
  • code (128-130)
  • msg (133-135)
questdb-rs/src/ingress/buffer.rs (7)
  • new (97-102)
  • new (181-188)
  • new (204-263)
  • new (303-350)
  • new (486-488)
  • len (523-525)
  • with_max_name_len (500-508)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: questdb.c-questdb-client (Building and testing on windows-msvc-2019)
  • GitHub Check: questdb.c-questdb-client (Building and testing on windows-msvc-2022)
  • GitHub Check: questdb.c-questdb-client (Building and testing on mac)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux-nightly)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux-beta)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux-stable)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux)
  • GitHub Check: questdb.c-questdb-client (Building and testing Vs QuestDB 'master')
  • GitHub Check: questdb.c-questdb-client (Building and testing cargo fmt and clippy)
🔇 Additional comments (7)
questdb-rs/src/ingress/decimal.rs (3)

27-77: Excellent trait design and documentation.

The DecimalSerializer trait is well-defined with comprehensive documentation covering both text and binary formats, including concrete examples. The documented constraints (scale ≤ 76, two's complement big-endian encoding) align with the implementations below.


103-126: Clean &str implementation with appropriate validation scope.

The implementation correctly validates ILP reserved characters while explicitly avoiding full decimal syntax validation, which is a reasonable trade-off documented in lines 88-93. The server-side validation will catch malformed decimals.


176-230: Robust BigDecimal handling with proper constraint enforcement.

The implementation correctly:

  • Validates scale ≤ 76 (QuestDB limit)
  • Handles negative scales by upscaling to scale 0 (lines 203-211)
  • Enforces mantissa size limit (i8::MAX bytes, line 213)

The negative scale handling is particularly well-done: converting 1.23e12 (negative scale) to 1230000000000 with scale 0 ensures protocol compliance.

questdb-rs/src/tests/decimal.rs (4)

29-37: Clean helper function for test readability.

The serialize_decimal wrapper simplifies test code by encapsulating the common pattern of creating a buffer and calling the trait method.


139-183: Well-designed binary format validator with correct sign extension.

The parse_binary_decimal helper properly validates the binary format structure (marker, type, scale, length) and correctly handles sign extension for negative values (lines 174-178). This is essential for verifying two's complement big-endian encoding.


478-522: Excellent edge case coverage for BigDecimal limits.

These tests thoroughly validate:

  • Maximum scale of 76 (QuestDB limit)
  • Rejection of scale > 76 with clear error message
  • Negative scale normalization to scale 0
  • Large value rejection beyond i8::MAX bytes

This ensures the implementation respects all documented constraints.


542-694: Comprehensive integration tests across protocol versions.

The buffer integration tests validate the column_decimal method across:

  • Both V1 (text) and V2 (binary) protocols
  • All decimal types (&str, rust_decimal, bigdecimal)
  • Multiple decimals per buffer
  • Name length enforcement

The V2 tests correctly verify binary encoding by extracting and parsing the decimal bytes (e.g., lines 609-614).

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
include/questdb/ingress/line_sender.h (1)

46-86: Add missing line_sender_error_invalid_decimal to the C API enum
Include a line_sender_error_invalid_decimal entry in line_sender_error_code (in include/questdb/ingress/line_sender.h) to match the FFI mapping for decimal validation errors.

system_test/questdb_line_sender.py (2)

687-689: Update type hint to include Decimal.

The type hint for the value parameter should include Decimal to match the implementation below that handles Decimal values.

Apply this diff to fix the type hint:

 def column( self, name: str, - value: Union[bool, int, float, str, TimestampMicros, datetime]): + value: Union[bool, int, float, str, Decimal, TimestampMicros, datetime]):

733-737: Update error message to include Decimal.

The error message should list Decimal as one of the expected types since it's now supported.

Apply this diff:

 fqn = _fully_qual_name(value) raise ValueError( f'Bad field value of type {fqn}: Expected one of ' - '`bool`, `int`, `float` or `str`.') + '`bool`, `int`, `float`, `str`, `Decimal`, `TimestampMicros`, or `datetime`.')
🧹 Nitpick comments (2)
system_test/test.py (1)

546-547: Document the default decimal scale.

The comment mentions "By default, the decimal created as a scale of 3", but it's unclear where this default is configured or if it's server-side behavior. Consider adding a comment referencing the configuration or documenting this behavior.

doc/C.md (1)

93-96: Consider adding guidance on decimal vs float usage.

The documentation now shows the decimal API but doesn't explain when developers should use column_decimal_str versus column_f64. Consider adding a brief note about use cases (e.g., financial data, precise decimal arithmetic) and the differences in server-side storage/precision.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a583839 and c7f19af.

📒 Files selected for processing (21)
  • doc/C.md (1 hunks)
  • doc/CPP.md (1 hunks)
  • examples/line_sender_c_example.c (1 hunks)
  • examples/line_sender_c_example_auth.c (1 hunks)
  • examples/line_sender_c_example_auth_tls.c (1 hunks)
  • examples/line_sender_c_example_from_conf.c (1 hunks)
  • examples/line_sender_c_example_from_env.c (1 hunks)
  • examples/line_sender_c_example_http.c (1 hunks)
  • examples/line_sender_c_example_tls_ca.c (1 hunks)
  • examples/line_sender_cpp_example.cpp (1 hunks)
  • examples/line_sender_cpp_example_auth.cpp (1 hunks)
  • examples/line_sender_cpp_example_auth_tls.cpp (1 hunks)
  • examples/line_sender_cpp_example_from_conf.cpp (1 hunks)
  • examples/line_sender_cpp_example_from_env.cpp (1 hunks)
  • examples/line_sender_cpp_example_http.cpp (1 hunks)
  • examples/line_sender_cpp_example_tls_ca.cpp (1 hunks)
  • include/questdb/ingress/line_sender.h (1 hunks)
  • include/questdb/ingress/line_sender.hpp (1 hunks)
  • questdb-rs-ffi/src/lib.rs (3 hunks)
  • system_test/questdb_line_sender.py (3 hunks)
  • system_test/test.py (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (12)
include/questdb/ingress/line_sender.h (2)
questdb-rs-ffi/src/lib.rs (1)
  • line_sender_buffer_column_decimal_str (948-959)
include/questdb/ingress/line_sender.hpp (22)
  • buffer (1685-1691)
  • buffer (1685-1685)
  • buffer (1702-1717)
  • buffer (1702-1702)
  • buffer (1740-1762)
  • buffer (1740-1741)
  • name (810-816)
  • name (810-810)
  • name (824-830)
  • name (824-824)
  • name (858-864)
  • name (858-858)
  • name (871-877)
  • name (871-871)
  • name (884-890)
  • name (884-884)
  • name (1005-1011)
  • name (1005-1005)
  • name (1019-1022)
  • name (1019-1019)
  • name (1024-1027)
  • name (1024-1024)
examples/line_sender_c_example_from_conf.c (1)
questdb-rs-ffi/src/lib.rs (1)
  • line_sender_buffer_column_decimal_str (948-959)
examples/line_sender_c_example.c (1)
questdb-rs-ffi/src/lib.rs (1)
  • line_sender_buffer_column_decimal_str (948-959)
include/questdb/ingress/line_sender.hpp (2)
questdb-rs-ffi/src/lib.rs (1)
  • line_sender_buffer_column_decimal_str (948-959)
questdb-rs/src/ingress/buffer.rs (1)
  • column_decimal (1050-1062)
examples/line_sender_c_example_from_env.c (1)
questdb-rs-ffi/src/lib.rs (1)
  • line_sender_buffer_column_decimal_str (948-959)
questdb-rs-ffi/src/lib.rs (2)
include/questdb/ingress/line_sender.hpp (30)
  • buffer (1685-1691)
  • buffer (1685-1685)
  • buffer (1702-1717)
  • buffer (1702-1702)
  • buffer (1740-1762)
  • buffer (1740-1741)
  • line_sender_buffer (634-643)
  • line_sender_buffer (634-637)
  • line_sender_buffer (645-652)
  • line_sender_buffer (654-662)
  • line_sender_buffer (1180-1184)
  • name (810-816)
  • name (810-810)
  • name (824-830)
  • name (824-824)
  • name (858-864)
  • name (858-858)
  • name (871-877)
  • name (871-871)
  • name (884-890)
  • name (884-884)
  • name (1005-1011)
  • name (1005-1005)
  • name (1019-1022)
  • name (1019-1019)
  • name (1024-1027)
  • name (1024-1024)
  • line_sender_error (142-146)
  • column_decimal (1046-1050)
  • column_decimal (1046-1047)
questdb-rs/src/ingress/buffer.rs (1)
  • column_decimal (1050-1062)
examples/line_sender_c_example_http.c (1)
questdb-rs-ffi/src/lib.rs (1)
  • line_sender_buffer_column_decimal_str (948-959)
examples/line_sender_c_example_tls_ca.c (1)
questdb-rs-ffi/src/lib.rs (1)
  • line_sender_buffer_column_decimal_str (948-959)
examples/line_sender_c_example_auth.c (1)
questdb-rs-ffi/src/lib.rs (1)
  • line_sender_buffer_column_decimal_str (948-959)
examples/line_sender_c_example_auth_tls.c (1)
questdb-rs-ffi/src/lib.rs (1)
  • line_sender_buffer_column_decimal_str (948-959)
system_test/questdb_line_sender.py (2)
questdb-rs-ffi/src/lib.rs (1)
  • line_sender_buffer_column_decimal_str (948-959)
include/questdb/ingress/line_sender.hpp (34)
  • _impl (222-225)
  • _impl (227-230)
  • _impl (232-235)
  • _impl (708-714)
  • _impl (717-723)
  • _impl (726-732)
  • _impl (739-745)
  • _impl (755-763)
  • _impl (790-794)
  • _impl (800-804)
  • _impl (1167-1178)
  • _impl (1187-1197)
  • _impl (1546-1553)
  • _impl (1772-1775)
  • _impl (1780-1787)
  • _impl (1795-1800)
  • name (810-816)
  • name (810-810)
  • name (824-830)
  • name (824-824)
  • name (858-864)
  • name (858-858)
  • name (871-877)
  • name (871-871)
  • name (884-890)
  • name (884-884)
  • name (1005-1011)
  • name (1005-1005)
  • name (1019-1022)
  • name (1019-1019)
  • name (1024-1027)
  • name (1024-1024)
  • _utf8 (261-264)
  • _utf8 (261-261)
system_test/test.py (2)
system_test/questdb_line_sender.py (8)
  • table (671-677)
  • table (909-911)
  • column (687-738)
  • column (917-921)
  • at_now (793-796)
  • at_now (934-935)
  • buffer (862-863)
  • peek (648-659)
system_test/fixture.py (1)
  • retry_check_table (277-315)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: questdb.c-questdb-client (Building and testing on windows-msvc-2019)
  • GitHub Check: questdb.c-questdb-client (Building and testing on windows-msvc-2022)
  • GitHub Check: questdb.c-questdb-client (Building and testing on mac)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux-nightly)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux-beta)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux-stable)
  • GitHub Check: questdb.c-questdb-client (Building and testing cargo fmt and clippy)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux)
  • GitHub Check: questdb.c-questdb-client (Building and testing Vs QuestDB 'master')
🔇 Additional comments (24)
examples/line_sender_cpp_example_from_env.cpp (1)

26-26: LGTM! Consistent with the new decimal API.

The change correctly demonstrates the new column_decimal API for inserting decimal values as strings.

examples/line_sender_cpp_example.cpp (1)

28-28: LGTM! Correct decimal API usage.

The change properly uses column_decimal with a UTF-8 string literal for decimal values.

examples/line_sender_cpp_example_auth_tls.cpp (1)

32-32: LGTM! Proper decimal API usage.

Correctly demonstrates the decimal string API with authentication and TLS.

examples/line_sender_c_example_from_env.c (1)

40-42: LGTM! Correct C API usage for decimals.

The change properly uses the C API line_sender_buffer_column_decimal_str with a UTF-8 literal and maintains the existing error handling pattern.

examples/line_sender_c_example.c (1)

54-56: LGTM! Consistent C API usage.

Correctly demonstrates the decimal string API in C with proper error handling.

doc/CPP.md (1)

79-79: LGTM! Documentation correctly updated.

The code example in the documentation properly demonstrates the new column_decimal API.

examples/line_sender_cpp_example_auth.cpp (1)

32-32: LGTM! Proper usage with authentication.

Correctly uses the decimal API in an authenticated context.

examples/line_sender_c_example_auth_tls.c (1)

64-66: LGTM! C API usage with authentication and TLS is correct.

The decimal string API is properly used with authentication and TLS configuration.

Note on PR status: The PR description indicates "c: not done, c++: not done" in the progress checklist, yet these C/C++ examples are updated. Based on the FFI bindings and these examples, the C/C++ API surface appears functional. Consider updating the checklist or clarifying what remains incomplete for C/C++ support (e.g., comprehensive testing, additional API surface, documentation).

examples/line_sender_c_example_from_conf.c (1)

41-44: LGTM! Decimal string API usage is correct.

The transition from line_sender_buffer_column_f64 to line_sender_buffer_column_decimal_str is implemented correctly, using QDB_UTF8_LITERAL for the decimal value and maintaining consistent error handling.

examples/line_sender_c_example_auth.c (1)

63-66: LGTM! Consistent decimal API usage.

The change correctly replaces the floating-point column with the decimal string API, maintaining proper error handling and UTF-8 encoding.

examples/line_sender_c_example_tls_ca.c (1)

67-70: LGTM! Decimal API correctly applied.

The implementation follows the same correct pattern as the other C examples, properly using the decimal string API.

examples/line_sender_cpp_example_from_conf.cpp (1)

27-27: LGTM! C++ decimal API usage is correct.

The change from .column() to .column_decimal() with the _utf8 literal is properly implemented, maintaining the fluent interface pattern.

examples/line_sender_c_example_http.c (1)

52-55: LGTM! HTTP example correctly updated.

The decimal string API is properly applied, consistent with the other C examples.

examples/line_sender_cpp_example_http.cpp (1)

27-27: LGTM! C++ decimal API correctly applied.

The change is consistent with the other C++ examples, properly using .column_decimal() with the UTF-8 literal.

examples/line_sender_cpp_example_tls_ca.cpp (1)

35-35: LGTM! TLS example correctly updated.

The decimal API is properly applied, maintaining consistency with the other C++ examples.

include/questdb/ingress/line_sender.h (1)

499-513: LGTM! API function declaration is correct and well-documented.

The new line_sender_buffer_column_decimal_str function is properly declared with clear documentation, matching the signature used in the C examples and the FFI implementation.

system_test/questdb_line_sender.py (2)

294-300: LGTM!

The DLL binding registration for the new decimal string API is correct and follows the established pattern.


714-719: LGTM!

The Decimal handling correctly converts the value to string and calls the appropriate C function.

system_test/test.py (2)

787-794: LGTM!

The test expectations correctly reflect the change from DOUBLE to DECIMAL(18,3) type with string-formatted values.


520-549: Verify NaN/Infinity mapping to None
The test asserts Decimal("NaN") and Decimal("Infinity") are represented as null; confirm this is the intended, consistently implemented server behavior and documented in the API/specs.

questdb-rs-ffi/src/lib.rs (3)

228-229: LGTM!

The new error code line_sender_error_invalid_decimal is properly added to the enum.


258-258: LGTM!

The error code mapping from ErrorCode::InvalidDecimal to line_sender_error_invalid_decimal is correctly implemented.


941-959: LGTM!

The C API function implementation correctly:

  • Unwraps the buffer, column name, and UTF-8 value
  • Delegates to the Rust buffer.column_decimal method
  • Handles errors using the established bubble_err_to_c! macro
  • Returns bool for success/failure
include/questdb/ingress/line_sender.hpp (1)

1029-1063: LGTM!

The four column_decimal overloads provide convenient C++ API access:

  1. Base implementation with utf8_view
  2. Template for character array literals
  3. std::string_view overload
  4. const std::string& overload

All properly delegate to the underlying C API via line_sender_buffer_column_decimal_str and follow the established pattern used by other column methods.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
system_test/questdb_line_sender.py (1)

687-689: Update the type hint to include Decimal.

The type hint for the value parameter doesn't include Decimal, but the implementation (lines 714-719) handles this type. This inconsistency can cause type checkers to incorrectly flag valid code.

Apply this diff to fix the type hint:

 def column( self, name: str, - value: Union[bool, int, float, str, TimestampMicros, datetime]): + value: Union[bool, int, float, str, Decimal, TimestampMicros, datetime]):
🧹 Nitpick comments (1)
system_test/questdb_line_sender.py (1)

733-737: Consider using TypeError for invalid types.

The error message correctly lists all supported types including Decimal. However, for invalid type errors, TypeError is more semantically appropriate than ValueError.

Based on learnings
Apply this diff for a more appropriate exception type:

 else: fqn = _fully_qual_name(value) - raise ValueError( + raise TypeError( f'Bad field value of type {fqn}: Expected one of ' '`bool`, `int`, `float`, `str`, `Decimal`, `TimestampMicros`, or `datetime`.')
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c7f19af and 4aa3b14.

📒 Files selected for processing (3)
  • include/questdb/ingress/line_sender.h (2 hunks)
  • include/questdb/ingress/line_sender.hpp (2 hunks)
  • system_test/questdb_line_sender.py (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • include/questdb/ingress/line_sender.h
🧰 Additional context used
🧬 Code graph analysis (2)
system_test/questdb_line_sender.py (2)
questdb-rs-ffi/src/lib.rs (1)
  • line_sender_buffer_column_decimal_str (948-959)
include/questdb/ingress/line_sender.hpp (34)
  • _impl (231-234)
  • _impl (236-239)
  • _impl (241-244)
  • _impl (717-723)
  • _impl (726-732)
  • _impl (735-741)
  • _impl (748-754)
  • _impl (764-772)
  • _impl (799-803)
  • _impl (809-813)
  • _impl (1176-1187)
  • _impl (1196-1206)
  • _impl (1555-1562)
  • _impl (1781-1784)
  • _impl (1789-1796)
  • _impl (1804-1809)
  • name (819-825)
  • name (819-819)
  • name (833-839)
  • name (833-833)
  • name (867-873)
  • name (867-867)
  • name (880-886)
  • name (880-880)
  • name (893-899)
  • name (893-893)
  • name (1014-1020)
  • name (1014-1014)
  • name (1028-1031)
  • name (1028-1028)
  • name (1033-1036)
  • name (1033-1033)
  • _utf8 (270-273)
  • _utf8 (270-270)
include/questdb/ingress/line_sender.hpp (2)
questdb-rs-ffi/src/lib.rs (1)
  • line_sender_buffer_column_decimal_str (948-959)
questdb-rs/src/ingress/buffer.rs (1)
  • column_decimal (1050-1062)
🪛 Ruff (0.13.3)
system_test/questdb_line_sender.py

735-737: Prefer TypeError exception for invalid type

(TRY004)


735-737: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: questdb.c-questdb-client (Building and testing on windows-msvc-2019)
  • GitHub Check: questdb.c-questdb-client (Building and testing on windows-msvc-2022)
  • GitHub Check: questdb.c-questdb-client (Building and testing on mac)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux-nightly)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux-beta)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux-stable)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux)
  • GitHub Check: questdb.c-questdb-client (Building and testing Vs QuestDB 'master')
  • GitHub Check: questdb.c-questdb-client (Building and testing cargo fmt and clippy)
🔇 Additional comments (5)
system_test/questdb_line_sender.py (3)

294-300: LGTM!

The C binding setup for line_sender_buffer_column_decimal_str is correct and matches the signature from the Rust FFI layer.


714-719: LGTM!

The Decimal handling implementation is correct. Converting the decimal to a string using str(value) is appropriate for serialization.


917-919: LGTM!

The type hint for Sender.column correctly includes Decimal and matches the implementation.

include/questdb/ingress/line_sender.hpp (2)

85-93: LGTM!

The new error codes are well-documented and follow the existing pattern. The invalid_decimal error code appropriately supports the new decimal functionality.


1038-1071: LGTM!

The column_decimal methods are well-implemented and follow the same pattern as existing string column methods. The multiple overloads provide good ergonomics for different string types, and all correctly delegate to the underlying C function.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
questdb-rs/src/tests/interop/ilp-client-interop-test.json (1)

1638-1680: LGTM! Test case covers essential decimal scenarios.

The test case validates decimal serialization with a good range of values including zero, negative zero, and various magnitudes. The expected line and binary formats are properly specified for both V1 and V2 protocol validation.

Consider adding edge case tests in future iterations:

  • Maximum precision values (if there's a documented limit)
  • Very large exponents (e.g., scientific notation)
  • Boundary values for the decimal type
  • Potentially invalid or out-of-range decimals (in separate error test cases)

However, these may already be covered in dedicated unit tests elsewhere in the codebase.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4aa3b14 and 2095903.

📒 Files selected for processing (3)
  • questdb-rs/Cargo.toml (5 hunks)
  • questdb-rs/build.rs (3 hunks)
  • questdb-rs/src/tests/interop/ilp-client-interop-test.json (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
questdb-rs/build.rs (2)
include/questdb/ingress/line_sender.hpp (29)
  • column (860-860)
  • column (913-946)
  • column (913-914)
  • column (961-977)
  • column (961-962)
  • column (1001-1007)
  • column (1001-1001)
  • column (1023-1026)
  • column (1023-1023)
  • column (1075-1081)
  • column (1075-1077)
  • column (1086-1091)
  • column (1086-1087)
  • name (819-825)
  • name (819-819)
  • name (833-839)
  • name (833-833)
  • name (867-873)
  • name (867-867)
  • name (880-886)
  • name (880-880)
  • name (893-899)
  • name (893-893)
  • name (1014-1020)
  • name (1014-1014)
  • name (1028-1031)
  • name (1028-1028)
  • name (1033-1036)
  • name (1033-1033)
system_test/questdb_line_sender.py (2)
  • column (687-738)
  • column (917-921)
🔇 Additional comments (3)
questdb-rs/build.rs (3)

65-69: LGTM!

The DecimalColumn struct follows the same pattern as other column types in the file, with appropriate derives for serialization.


204-208: LGTM!

The code generation pattern is consistent with other column types. The unwrap() on BigDecimal::from_str is acceptable here since this is test generation code that runs at compile time—if the JSON contains invalid decimal strings, the build will fail with a clear error message.


78-78: bigdecimal is enabled by json_tests, so BigDecimal import is safe.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
examples/line_sender_cpp_example_http.cpp (1)

28-28: Good demonstration of the decimal feature.

The change from a double literal to a decimal literal correctly demonstrates the new decimal column support. Using decimal for the price field is semantically appropriate for financial data.

Optional: Consider adding an explanatory comment.

Since this is an example file demonstrating new functionality, you might add a brief comment explaining why decimal is preferred for financial precision:

+ // Use decimal for price to maintain exact precision for financial data .column(price_name, "2615.54"_decimal)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2095903 and 984a236.

📒 Files selected for processing (16)
  • CMakeLists.txt (1 hunks)
  • doc/CPP.md (1 hunks)
  • examples/line_sender_cpp_example.cpp (2 hunks)
  • examples/line_sender_cpp_example_auth.cpp (2 hunks)
  • examples/line_sender_cpp_example_auth_tls.cpp (2 hunks)
  • examples/line_sender_cpp_example_decimal_binary.cpp (1 hunks)
  • examples/line_sender_cpp_example_decimal_custom.cpp (1 hunks)
  • examples/line_sender_cpp_example_from_conf.cpp (2 hunks)
  • examples/line_sender_cpp_example_from_env.cpp (2 hunks)
  • examples/line_sender_cpp_example_http.cpp (2 hunks)
  • examples/line_sender_cpp_example_tls_ca.cpp (2 hunks)
  • include/questdb/ingress/line_sender.h (2 hunks)
  • include/questdb/ingress/line_sender.hpp (4 hunks)
  • questdb-rs-ffi/src/decimal.rs (1 hunks)
  • questdb-rs-ffi/src/lib.rs (5 hunks)
  • questdb-rs/src/ingress/mod.rs (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • examples/line_sender_cpp_example.cpp
  • examples/line_sender_cpp_example_tls_ca.cpp
  • questdb-rs/src/ingress/mod.rs
  • examples/line_sender_cpp_example_from_env.cpp
  • examples/line_sender_cpp_example_auth.cpp
🧰 Additional context used
🧬 Code graph analysis (5)
include/questdb/ingress/line_sender.hpp (2)
examples/line_sender_cpp_example_decimal_custom.cpp (2)
  • data (29-39)
  • _scale (24-27)
questdb-rs-ffi/src/lib.rs (2)
  • line_sender_buffer_column_decimal_str (950-961)
  • line_sender_buffer_column_decimal (973-986)
examples/line_sender_cpp_example_decimal_binary.cpp (1)
include/questdb/ingress/line_sender.hpp (11)
  • binary_view (751-756)
  • binary_view (765-770)
  • binary_view (779-784)
  • binary_view (792-797)
  • binary_view (806-811)
  • buffer (1921-1927)
  • buffer (1921-1921)
  • buffer (1938-1953)
  • buffer (1938-1938)
  • buffer (1976-1998)
  • buffer (1976-1977)
questdb-rs-ffi/src/lib.rs (2)
questdb-rs/src/ingress/buffer.rs (6)
  • column_decimal (1050-1062)
  • new (97-102)
  • new (181-188)
  • new (204-263)
  • new (303-350)
  • new (486-488)
questdb-rs-ffi/src/decimal.rs (1)
  • new (78-80)
examples/line_sender_cpp_example_decimal_custom.cpp (2)
include/questdb/ingress/line_sender.hpp (7)
  • _scale (815-818)
  • buffer (1921-1927)
  • buffer (1921-1921)
  • buffer (1938-1953)
  • buffer (1938-1938)
  • buffer (1976-1998)
  • buffer (1976-1977)
examples/line_sender_cpp_example_decimal_binary.cpp (2)
  • example (8-51)
  • example (8-8)
include/questdb/ingress/line_sender.h (3)
questdb-rs-ffi/src/lib.rs (2)
  • line_sender_buffer_column_decimal_str (950-961)
  • line_sender_buffer_column_decimal (973-986)
include/questdb/ingress/line_sender.hpp (22)
  • buffer (1921-1927)
  • buffer (1921-1921)
  • buffer (1938-1953)
  • buffer (1938-1938)
  • buffer (1976-1998)
  • buffer (1976-1977)
  • name (1025-1031)
  • name (1025-1025)
  • name (1039-1045)
  • name (1039-1039)
  • name (1073-1079)
  • name (1073-1073)
  • name (1086-1092)
  • name (1086-1086)
  • name (1099-1105)
  • name (1099-1099)
  • name (1220-1226)
  • name (1220-1220)
  • name (1234-1237)
  • name (1234-1234)
  • name (1239-1242)
  • name (1239-1239)
examples/line_sender_cpp_example_decimal_custom.cpp (1)
  • data (29-39)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: questdb.c-questdb-client (Building and testing on windows-msvc-2019)
  • GitHub Check: questdb.c-questdb-client (Building and testing on windows-msvc-2022)
  • GitHub Check: questdb.c-questdb-client (Building and testing on mac)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux-nightly)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux-beta)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux-stable)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux)
  • GitHub Check: questdb.c-questdb-client (Building and testing Vs QuestDB 'master')
  • GitHub Check: questdb.c-questdb-client (Building and testing cargo fmt and clippy)
🔇 Additional comments (20)
examples/line_sender_cpp_example_http.cpp (1)

6-6: LGTM!

The using directive correctly imports the decimal namespace, enabling the _decimal literal suffix used on line 28. This is appropriate for an example file.

doc/CPP.md (1)

79-79: LGTM! Documentation correctly demonstrates decimal literal usage.

The change from a numeric literal to a decimal literal using the _decimal suffix accurately reflects the new decimal support introduced in this PR.

include/questdb/ingress/line_sender.hpp (4)

85-93: LGTM! Error codes properly extended for decimal support.

The new error codes (array_error, protocol_version_error, invalid_decimal) are appropriately added to support the expanded functionality. The invalid_decimal error code specifically addresses decimal parsing and validation errors.


640-712: Well-designed text-based decimal API.

The decimal::text_view wrapper around utf8_view provides type safety while maintaining ergonomic usage through:

  • Multiple constructor overloads for different string types
  • The _decimal literal suffix for convenient string literals
  • Clean separation from regular string values at the type level

The design correctly delegates UTF-8 validation to the underlying utf8_view.


740-843: Comprehensive binary decimal representation.

The binary_view class provides excellent coverage of input types through multiple constructor overloads (raw pointers, fixed arrays, std::array, std::vector, C++20 std::span). The design correctly:

  • Stores scale and unscaled value as big-endian bytes
  • Documents constraints (max scale: 76, max mantissa: 127 bytes)
  • Provides a view() method for customization point compatibility

1244-1298: Column overloads correctly route to C API.

Both decimal overloads properly:

  • Accept column_name_view for the column name
  • Call the appropriate C API function (line_sender_buffer_column_decimal_str for text, line_sender_buffer_column_decimal for binary)
  • Use the wrapped error handling pattern
  • Return *this for method chaining

The text overload accesses the underlying utf8_view via .view()._impl, and the binary overload correctly extracts scale, data pointer, and size.

examples/line_sender_cpp_example_auth_tls.cpp (2)

6-6: LGTM! Namespace import enables decimal literal usage.

The addition of using namespace questdb::ingress::decimal; allows the example to use the _decimal suffix on line 33.


33-33: LGTM! Price field now uses decimal literal.

The change from a floating-point literal to "2615.54"_decimal correctly demonstrates decimal column usage in an authenticated TLS connection context.

CMakeLists.txt (1)

168-173: LGTM! Build targets added for decimal examples.

The two new example targets (line_sender_cpp_example_decimal_custom and line_sender_cpp_example_decimal_binary) are correctly added to demonstrate custom decimal type integration and binary decimal usage.

examples/line_sender_cpp_example_from_conf.cpp (1)

6-6: LGTM! Configuration-based example updated for decimal support.

The changes correctly demonstrate decimal usage when connecting via configuration string. The namespace import (line 6) and decimal literal usage (line 28) are consistent with other examples.

Also applies to: 28-28

examples/line_sender_cpp_example_decimal_binary.cpp (1)

24-32: LGTM! Binary decimal example is clear and correct.

The example properly demonstrates binary decimal usage:

  • Line 24: Raw unscaled value as bytes
  • Line 25-26: Comment explains the decimal representation (123 with scale 1 = 12.3)
  • Line 26: Creates binary_view with scale and data
  • Line 32: Uses the decimal value in a column

The example correctly shows that the unscaled value is stored as bytes in two's complement big-endian format.

examples/line_sender_cpp_example_decimal_custom.cpp (2)

8-45: Excellent demonstration of custom decimal type integration.

The custom_decimal::Decimal32 class effectively shows how users can integrate their own decimal types with the QuestDB client. The implementation correctly:

  • Stores scale and unscaled value as 32-bit integers
  • Provides a view() method that returns binary_view
  • Converts the unscaled value to big-endian byte format
  • Uses std::array for automatic lifetime management

This example serves as a clear template for users with custom decimal types.


64-71: LGTM! Usage demonstrates the customization point.

The example correctly shows:

  • Creating a custom decimal instance (line 65)
  • Calling .view() to convert to binary_view (line 71)
  • Passing the view to the column() method

This validates that the customization point design works as intended.

questdb-rs-ffi/src/lib.rs (4)

47-47: LGTM! Decimal module correctly integrated.

The mod decimal; declaration (line 47) and the use crate::decimal::Decimal; statement (line 1803) properly integrate the decimal module into the FFI layer.

Also applies to: 1803-1803


229-230: LGTM! Error code properly added and mapped.

The new line_sender_error_invalid_decimal variant and its mapping from ErrorCode::InvalidDecimal correctly extend the error handling for decimal parsing and validation failures.

Also applies to: 259-259


942-961: LGTM! Text decimal FFI function is correct.

The line_sender_buffer_column_decimal_str function properly:

  • Takes validated UTF-8 input via line_sender_utf8
  • Converts to Rust string slice with .as_str()
  • Routes to buffer.column_decimal(name, value)
  • Uses the bubble_err_to_c! macro for consistent error handling

The function signature and implementation are safe and correct.


963-986: LGTM! Binary decimal FFI function is correct and safe.

The line_sender_buffer_column_decimal function properly:

  • Takes scale as u32
  • Takes raw byte data as pointer and length
  • Creates a safe Rust slice with slice::from_raw_parts(data, data_len)
  • Constructs Decimal::new(scale, slice)
  • Routes to buffer.column_decimal(name, decimal)
  • Uses consistent error handling

The function correctly handles binary decimal data in two's complement big-endian format. The use of unsafe is appropriate for FFI, and the slice construction is safe as long as callers uphold the safety contract documented in the C header.

include/questdb/ingress/line_sender.h (3)

86-88: LGTM! Clear and well-positioned error code.

The new line_sender_error_invalid_decimal error code is properly documented and positioned at the end of the enum, maintaining backward compatibility. The naming follows the established convention.


502-516: LGTM! Consistent API design for decimal string input.

The function declaration follows the established pattern for column functions. Documentation is clear, parameter types are appropriate, and error handling is consistent with existing APIs.


518-536: Consider documenting scale parameter constraints.

The function declaration is well-designed and consistent with existing patterns. The binary format specification (two's complement, big-endian) is clear.

However, consider documenting any constraints on the scale parameter. For example:

  • Is there a maximum scale value?
  • What happens if scale exceeds supported precision?

Additionally, while unsigned int is typically 32-bit and matches the FFI's u32, consider whether uint32_t (from stdint.h) would be more explicit about the size guarantees. This is a minor point and may not be necessary if the codebase already uses unsigned int consistently, but it could improve portability clarity.

To check if there are existing conventions or constraints:

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
questdb-rs-ffi/src/decimal.rs (1)

140-143: Fix wire-format byte order for decimals.

Scale is emitted before the length/value, so the server treats the scale as the length and desyncs on decode. Emit length first, then value bytes, then append the scale byte.

- out.push(self.scale as u8); - out.push(self.value.len() as u8); - out.extend_from_slice(self.value); + out.push(self.value.len() as u8); + out.extend_from_slice(self.value); + out.push(self.scale as u8);
🧹 Nitpick comments (1)
questdb-rs-ffi/src/decimal.rs (1)

88-94: Align documentation with the actual wire layout.

The bullet list still says “scale byte” precedes “length byte”, contradicting both the protocol spec and the fix above. Please rewrite the doc comment to state length → value → scale.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 984a236 and ce0fe17.

📒 Files selected for processing (2)
  • examples/line_sender_cpp_example_decimal_custom.cpp (1 hunks)
  • questdb-rs-ffi/src/decimal.rs (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
examples/line_sender_cpp_example_decimal_custom.cpp (2)
include/questdb/ingress/line_sender.hpp (7)
  • _scale (815-818)
  • buffer (1921-1927)
  • buffer (1921-1921)
  • buffer (1938-1953)
  • buffer (1938-1938)
  • buffer (1976-1998)
  • buffer (1976-1977)
examples/line_sender_cpp_example_decimal_binary.cpp (2)
  • example (8-51)
  • example (8-8)
🔇 Additional comments (3)
examples/line_sender_cpp_example_decimal_custom.cpp (3)

1-7: LGTM!

The includes and namespace declarations are appropriate for demonstrating custom decimal type integration with QuestDB ILP.


47-90: Example logic is correct (assuming view() fix).

The example demonstrates proper usage of the custom decimal type with QuestDB ILP, including:

  • Configuration with protocol_version=2 required for decimal support
  • Inline view() call within the method chain
  • Appropriate error handling

Once the view() method is fixed to avoid dangling references, this example will correctly demonstrate custom decimal integration.


110-123: LGTM!

The main function correctly handles command-line arguments with sensible defaults (localhost:9009) and returns appropriate exit codes.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
questdb-rs/build.rs (1)

318-321: rerun-if-changed path mismatch with parse() path.

parse() reads src/tests/interop/..., but build script watches src/test/interop/.... Fix the typo or rebuilds won’t trigger on JSON changes.

- println!("cargo:rerun-if-changed=src/test/interop/ilp-client-interop-test.json"); + println!("cargo:rerun-if-changed=src/tests/interop/ilp-client-interop-test.json");
♻️ Duplicate comments (3)
include/questdb/ingress/line_sender.hpp (1)

635-635: Consider narrower type for scale.

If scale is limited to 76, uint8_t would be sufficient and self-documenting. If interop requires u32, keeping u32 is fine.

examples/line_sender_cpp_example_decimal_binary.cpp (1)

60-64: Print the actual program name in Usage.

Use argv[0] to avoid hardcoding and copy/paste mistakes.

- std::cerr << "Usage:\n" - << "line_sender_c_example: [HOST [PORT]]\n" + std::cerr << "Usage:\n" + << argv[0] << ": [HOST [PORT]]\n" << " HOST: ILP host (defaults to \"localhost\").\n" << " PORT: ILP port (defaults to \"9009\")." << std::endl;
examples/line_sender_cpp_example_decimal_custom.cpp (1)

114-118: Print the actual program name in Usage.

Use argv[0] instead of a hardcoded binary name.

- std::cerr << "Usage:\n" - << "line_sender_c_example_decimal_custom: [HOST [PORT]]\n" + std::cerr << "Usage:\n" + << argv[0] << ": [HOST [PORT]]\n" << " HOST: ILP host (defaults to \"localhost\").\n" << " PORT: ILP port (defaults to \"9009\")." << std::endl;
🧹 Nitpick comments (7)
include/questdb/ingress/line_sender.hpp (2)

601-607: Make span const-correct.

Accepting std::span makes the API usable with const buffers and avoids implying mutation.

- decimal_view(uint32_t scale, const std::span<uint8_t>& span) + decimal_view(uint32_t scale, std::span<const uint8_t> span) : _scale{scale} - , _data{span.data()} - , _data_size{span.size()} + , _data{span.data()} + , _data_size{span.size()}

1130-1142: Early-validate scale and mantissa size (nice-to-have).

Constraints are documented (scale ≤ 76, mantissa ≤ 127 bytes) but not checked here. A quick guard yields earlier, clearer errors.

 line_sender_buffer& column( column_name_view name, const decimal::decimal_view& decimal) { may_init(); + if (decimal.scale() > 76u || decimal.data_size() > 127u) { + throw line_sender_error{ + line_sender_error_code::invalid_decimal, + "Invalid decimal: scale must be <= 76 and mantissa <= 127 bytes"}; + } line_sender_error::wrapped_call( ::line_sender_buffer_column_dec, _impl, name._impl, decimal.scale(), decimal.data(), decimal.data_size()); return *this; }
questdb-rs/src/ingress/mod.rs (2)

173-176: Avoid unconditional stderr logging in library code.

Replace eprintln! with gated logging to prevent noisy stderr in production.

- eprintln!( - "Using protocol version {:?} with max_name_len={}", - protocol_version, max_name_len - ); + #[cfg(debug_assertions)] + eprintln!( + "Using protocol version {:?} with max_name_len={}", + protocol_version, max_name_len + ); + // Or, if `log`/`tracing` is available: + // log::debug!("Using protocol version {:?} with max_name_len={}", protocol_version, max_name_len);

94-99: Doc grammar nit.

“required for V3 supported” → “required for V3 support.”

- /// QuestDB server version 9.2.0 or later is required for `V3` supported. + /// QuestDB server version 9.2.0 or later is required for `V3` support.
questdb-rs/src/ingress/buffer.rs (1)

1125-1131: Minor: align error wording with array message.

Tiny phrasing tweak for consistency (“the array datatype”).

- "Protocol version {} does not support array datatype", + "Protocol version {} does not support the array datatype", self.protocol_version
examples/line_sender_cpp_example_decimal_custom.cpp (1)

6-6: Avoid using-directive in examples.

Prefer not to pollute the global namespace; this example already fully qualifies types.

-using namespace questdb::ingress::decimal;
questdb-rs/build.rs (1)

219-224: Better failure message when parsing test Decimal literals.

Use expect(...) to aid debugging malformed JSON specs.

- "{} .column_dec({:?}, &BigDecimal::from_str({:?}).unwrap())?", + "{} .column_dec({:?}, &BigDecimal::from_str({:?}).expect(\"invalid decimal literal in interop spec\"))?",
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e63da89 and d730bad.

📒 Files selected for processing (6)
  • examples/line_sender_cpp_example_decimal_binary.cpp (1 hunks)
  • examples/line_sender_cpp_example_decimal_custom.cpp (1 hunks)
  • include/questdb/ingress/line_sender.hpp (6 hunks)
  • questdb-rs/build.rs (4 hunks)
  • questdb-rs/src/ingress/buffer.rs (3 hunks)
  • questdb-rs/src/ingress/mod.rs (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
questdb-rs/src/ingress/mod.rs (2)
include/questdb/ingress/line_sender.hpp (2)
  • version (1644-1652)
  • version (1644-1644)
system_test/questdb_line_sender.py (2)
  • ProtocolVersion (102-122)
  • protocol_version (913-916)
questdb-rs/build.rs (2)
include/questdb/ingress/line_sender.hpp (30)
  • column (905-905)
  • column (958-991)
  • column (958-959)
  • column (1006-1022)
  • column (1006-1007)
  • column (1050-1056)
  • column (1050-1050)
  • column (1072-1075)
  • column (1072-1072)
  • column (1175-1181)
  • column (1175-1175)
  • column (1185-1191)
  • column (1185-1187)
  • column (1196-1201)
  • name (864-870)
  • name (864-864)
  • name (878-884)
  • name (878-878)
  • name (912-918)
  • name (912-912)
  • name (925-931)
  • name (925-925)
  • name (938-944)
  • name (938-938)
  • name (1063-1069)
  • name (1063-1063)
  • name (1077-1080)
  • name (1077-1077)
  • name (1082-1085)
  • name (1082-1082)
system_test/questdb_line_sender.py (2)
  • column (689-746)
  • column (931-935)
examples/line_sender_cpp_example_decimal_custom.cpp (1)
include/questdb/ingress/line_sender.hpp (3)
  • decimal (657-662)
  • decimal (657-657)
  • _scale (610-613)
questdb-rs/src/ingress/buffer.rs (2)
questdb-rs/src/ingress/mod.rs (4)
  • fmt (109-115)
  • fmt (228-230)
  • fmt (1316-1338)
  • protocol_version (747-751)
questdb-rs/src/ingress/sender/mod.rs (2)
  • fmt (70-72)
  • protocol_version (287-289)
include/questdb/ingress/line_sender.hpp (3)
examples/line_sender_cpp_example_decimal_custom.cpp (4)
  • scale (12-15)
  • _scale (34-37)
  • to_decimal_view_state_impl (47-59)
  • to_decimal_view_state_impl (47-47)
questdb-rs-ffi/src/lib.rs (2)
  • line_sender_buffer_column_dec_str (958-969)
  • line_sender_buffer_column_dec (981-994)
system_test/questdb_line_sender.py (2)
  • column (689-746)
  • column (931-935)
examples/line_sender_cpp_example_decimal_binary.cpp (1)
include/questdb/ingress/line_sender.hpp (11)
  • decimal_view (546-551)
  • decimal_view (560-565)
  • decimal_view (574-579)
  • decimal_view (587-592)
  • decimal_view (601-606)
  • buffer (1804-1810)
  • buffer (1804-1804)
  • buffer (1821-1836)
  • buffer (1821-1821)
  • buffer (1859-1881)
  • buffer (1859-1860)
🪛 Clang (14.0.6)
examples/line_sender_cpp_example_decimal_custom.cpp

[error] 1-1: 'questdb/ingress/line_sender.hpp' file not found

(clang-diagnostic-error)

examples/line_sender_cpp_example_decimal_binary.cpp

[error] 1-1: 'questdb/ingress/line_sender.hpp' file not found

(clang-diagnostic-error)

🔇 Additional comments (7)
questdb-rs/src/ingress/buffer.rs (1)

1051-1068: LGTM: Protocol gate + serializer hook look solid.

Good version check and clean delegation to DecimalSerializer.

Please ensure the public rust docs “Buffer method calls, Serialized ILP types” table includes column_dec and that DecimalSerializer impls exist for:

  • &str (always)
  • &rust_decimal::Decimal (feature rust_decimal)
  • &bigdecimal::BigDecimal (feature bigdecimal)
questdb-rs/build.rs (6)

65-70: Schema addition looks good.

String-based Decimal payload in JSON is appropriate for precision and portability.


78-79: Enum extension is consistent.

DECIMAL variant wiring matches the new struct and serde tag scheme.


98-101: Sane default.

Default minimum protocol version of 1 is fine.


106-111: Good forward-compat hook.

minimumProtocolVersion with a default avoids breaking older specs.


154-156: Clearer diagnostics on mismatch.

Converting to UTF‑8 once and logging the attempted match improves failure output on V1 text assertions. Looks good.


145-147: Code is correct as-is; review comment is inaccurate.

The imports at lines 145-147 are not unconditional. They are nested inside the json_tests module, which is guarded by #[cfg(feature = "json_tests")] at line 25. The json_tests feature is defined as json_tests = ["bigdecimal"], ensuring that whenever json_tests is enabled, the bigdecimal feature is automatically enabled. This means the imports will only be compiled when the feature is active and bigdecimal is available. No refactoring is needed.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0764846 and e2fa211.

📒 Files selected for processing (1)
  • questdb-rs/build.rs (4 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-08T14:57:00.111Z
Learnt from: amunra PR: questdb/c-questdb-client#121 File: cpp_test/test_line_sender.cpp:381-388 Timestamp: 2025-10-08T14:57:00.111Z Learning: In tests that explicitly set `protocol_version` to a specific value (e.g., `opts.protocol_version(questdb::ingress::protocol_version::v2)`), timestamp suffix expectations should match that protocol version without requiring runtime branching, since the protocol version is constant for that test. 

Applied to files:

  • questdb-rs/build.rs
🧬 Code graph analysis (1)
questdb-rs/build.rs (2)
include/questdb/ingress/line_sender.hpp (30)
  • column (907-907)
  • column (960-993)
  • column (960-961)
  • column (1008-1024)
  • column (1008-1009)
  • column (1052-1058)
  • column (1052-1052)
  • column (1074-1077)
  • column (1074-1074)
  • column (1177-1183)
  • column (1177-1177)
  • column (1187-1193)
  • column (1187-1189)
  • column (1198-1203)
  • name (866-872)
  • name (866-866)
  • name (880-886)
  • name (880-880)
  • name (914-920)
  • name (914-914)
  • name (927-933)
  • name (927-927)
  • name (940-946)
  • name (940-940)
  • name (1065-1071)
  • name (1065-1065)
  • name (1079-1082)
  • name (1079-1079)
  • name (1084-1087)
  • name (1084-1084)
system_test/questdb_line_sender.py (2)
  • column (689-746)
  • column (931-935)
🔇 Additional comments (5)
questdb-rs/build.rs (5)

65-78: LGTM: DecimalColumn struct and enum variant follow established patterns.

The DecimalColumn struct and Column::Decimal variant are consistent with other column types. Using String for the decimal value is appropriate and avoids floating-point precision issues during deserialization.


98-110: LGTM: Default function and minimum protocol version field.

The default_minimum_protocol_version function correctly defaults to version 1 for backward compatibility. The serde attributes properly map the JSON field name.


145-146: LGTM: BigDecimal imports align with best practices.

The imports of BigDecimal and FromStr are appropriate for decimal column handling. This approach aligns with best practices for BigDecimal usage, as string-based construction avoids floating-point precision issues.

Based on learnings.


154-154: LGTM: Enhanced error output for debugging.

Converting line bytes to a String for error output improves test diagnostics. Using from_utf8_lossy is appropriate for error messages.


219-223: LGTM: Decimal column handling follows best practices.

The generated code correctly uses BigDecimal::from_str for decimal column values, which is the recommended approach to avoid floating-point precision issues. Using unwrap() is acceptable in test code, as it will produce clear panic messages if the JSON contains malformed decimal values.

Based on learnings.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
include/questdb/ingress/line_sender_decimal.hpp (1)

135-139: Scale width vs. documented max 76.

You keep scale as uint32_t while max is 76. If ABI across C/Rust uses u32, keeping u32 is sensible. Consider a debug-time assert to catch user mistakes early.

- decimal_view(uint32_t scale, const uint8_t* data, size_t data_size) noexcept + decimal_view(uint32_t scale, const uint8_t* data, size_t data_size) noexcept : _scale{scale} @@ { + // Optional guard (compiled out in NDEBUG): + assert(scale <= 76 && "decimal scale must be <= 76"); + assert(data_size <= 127 && "decimal mantissa must be <= 127 bytes"); }

Based on learnings

Also applies to: 151-158, 178-185, 195-201, 228-232

🧹 Nitpick comments (5)
include/questdb/ingress/line_sender.hpp (3)

434-458: Add: text-decimal overload LGTM; minor nit on docs.

API shape is good and matches C/Rust FFI. Tiny doc nit: “validated UTF-8 string” could mislead; validation is only UTF‑8, not numeric. Optional: clarify that numeric format is validated server-side.


460-489: Binary decimal overload: consider local precondition guard (optional).

Server/FFI will reject out-of-bounds, but a cheap guard here would fail fast before crossing FFI:

  • scale <= 76
  • data_size <= 127
    If undesired, ignore; current behavior is acceptable.

945-952: Doc typo.

“adedd” → “added”.

- * calculated from minimum throughput is adedd to the value of + * calculated from minimum throughput is added to the value of
include/questdb/ingress/line_sender_decimal.hpp (2)

188-201: Accept const spans for binary mantissa.

Current ctor takes std::span<uint8_t>. That blocks passing const data. Prefer std::span.

-#if __cplusplus >= 202002L - /** - * Construct a binary decimal view from a std::span (C++20). - * - * @param scale Number of decimal places (must be ≤ 76) - * @param span Span containing the unscaled value - */ - decimal_view(uint32_t scale, const std::span<uint8_t>& span) - : _scale{scale} - , _data{span.data()} - , _data_size{span.size()} - { - } -#endif +#if __cplusplus >= 202002L + /** + * Construct a binary decimal view from a std::span (C++20). + */ + decimal_view(uint32_t scale, std::span<const uint8_t> span) + : _scale{scale} + , _data{span.data()} + , _data_size{span.size()} + {} +#endif

133-145: Optionally mark trivial accessors/ctors noexcept.

Small improvement; not required. Helps optimization and communicates intent.

- decimal_view(uint32_t scale, const uint8_t* data, size_t data_size) + decimal_view(uint32_t scale, const uint8_t* data, size_t data_size) noexcept @@ - decimal_view(uint32_t scale, const uint8_t (&data)[N]) + decimal_view(uint32_t scale, const uint8_t (&data)[N]) noexcept @@ - decimal_view(uint32_t scale, const std::vector<uint8_t>& vec) + decimal_view(uint32_t scale, const std::vector<uint8_t>& vec) noexcept @@ - uint32_t scale() const + uint32_t scale() const noexcept @@ - const uint8_t* data() const + const uint8_t* data() const noexcept @@ - size_t data_size() const + size_t data_size() const noexcept @@ - const decimal_view& view() const + const decimal_view& view() const noexcept

Also applies to: 150-159, 175-186, 203-227

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e2fa211 and 17f2427.

📒 Files selected for processing (4)
  • include/questdb/ingress/line_sender.hpp (3 hunks)
  • include/questdb/ingress/line_sender_core.hpp (1 hunks)
  • include/questdb/ingress/line_sender_decimal.hpp (1 hunks)
  • questdb-rs/src/tests/http.rs (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • questdb-rs/src/tests/http.rs
🧰 Additional context used
🧬 Code graph analysis (3)
include/questdb/ingress/line_sender.hpp (3)
questdb-rs-ffi/src/lib.rs (2)
  • line_sender_buffer_column_dec_str (958-969)
  • line_sender_buffer_column_dec (981-994)
include/questdb/ingress/line_sender_decimal.hpp (2)
  • decimal (251-256)
  • decimal (251-251)
system_test/questdb_line_sender.py (2)
  • column (689-746)
  • column (931-935)
include/questdb/ingress/line_sender_decimal.hpp (1)
examples/line_sender_cpp_example_decimal_custom.cpp (1)
  • scale (12-15)
include/questdb/ingress/line_sender_core.hpp (2)
questdb-rs-ffi/src/lib.rs (3)
  • line_sender_error_get_code (390-394)
  • line_sender_error_msg (400-407)
  • line_sender_error_free (411-415)
include/questdb/ingress/line_sender.hpp (13)
  • _impl (109-115)
  • _impl (118-124)
  • _impl (127-133)
  • _impl (140-146)
  • _impl (156-164)
  • _impl (191-195)
  • _impl (201-205)
  • _impl (633-644)
  • _impl (653-663)
  • _impl (1012-1019)
  • _impl (1238-1241)
  • _impl (1246-1253)
  • _impl (1261-1266)
🔇 Additional comments (2)
include/questdb/ingress/line_sender.hpp (1)

517-528: Customization-point overload: OK; doc matches design via .view() on decimal_view.

Good use of ADL + uniform .view() contract; no issues.

include/questdb/ingress/line_sender_core.hpp (1)

419-424: operator== correctness is fine; relies on include.

Once is added, this is good.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
questdb-rs/build.rs (1)

320-327: Fix incorrect rerun-if-changed path; tests won’t regenerate on JSON changes.

You read from src/tests/interop/… but the rerun path uses src/test/interop/… (missing “s”).

- println!("cargo:rerun-if-changed=src/test/interop/ilp-client-interop-test.json"); + println!("cargo:rerun-if-changed=src/tests/interop/ilp-client-interop-test.json");
system_test/test.py (1)

139-151: Make V3 reachable: check DECIMAL first, then arrays.

Returning V2 before checking DECIMAL prevents V3 from ever being selected on 9.2.0+. Reorder:

 def expected_protocol_version(self) -> qls.ProtocolVersion: """The protocol version that we expect to be handling.""" if QDB_FIXTURE.protocol_version is None: if not QDB_FIXTURE.http: return qls.ProtocolVersion.V1 - - if QDB_FIXTURE.version >= FIRST_ARRAYS_RELEASE: - return qls.ProtocolVersion.V2 - - if QDB_FIXTURE.version >= DECIMAL_RELEASE: - return qls.ProtocolVersion.V3 + if QDB_FIXTURE.version >= DECIMAL_RELEASE: + return qls.ProtocolVersion.V3 + if QDB_FIXTURE.version >= FIRST_ARRAYS_RELEASE: + return qls.ProtocolVersion.V2 return qls.ProtocolVersion.V1
♻️ Duplicate comments (1)
questdb-rs/build.rs (1)

170-174: Fix type mismatch in generated test: compare enum-to-enum or cast to u32.

Current code compares (version as u8) to a u32 literal, which won’t compile. Either compare enums or cast to u32.

Minimal fix:

- if (version as u8) < {} { + if (version as u32) < {} {

Better (enum-to-enum, clearer):

- for (index, spec) in specs.iter().enumerate() { + for (index, spec) in specs.iter().enumerate() { + let min_ver_expr = match spec.minimum_protocol_version { + 1 => "ProtocolVersion::V1", + 2 => "ProtocolVersion::V2", + 3 => "ProtocolVersion::V3", + _ => "ProtocolVersion::V3", + }; write!( output, indoc! {r#" @@ - if (version as u8) < {} { + if version < {} { return Ok(()); } let mut buffer = Buffer::new(version); "#}, spec.test_name, index, slugify!(&spec.test_name, separator = "_"), - spec.minimum_protocol_version + min_ver_expr )?;

Also applies to: 175-179

🧹 Nitpick comments (3)
questdb-rs/src/tests/decimal.rs (1)

521-527: Minor: avoid magic number when slicing decimal binary.

Compute the prefix length instead of hard‑coding 17 for clarity:

- // Skip the prefix and \n suffix - let dec_binary = &bytes[17..bytes.len() - 1]; + // Skip the "test,sym=val dec=" prefix and trailing '\n' + let prefix_len = b"test,sym=val dec=".len(); + let dec_binary = &bytes[prefix_len..bytes.len() - 1];
questdb-rs/examples/http.rs (1)

1-2: Remove optional dependency from example; use &str decimal.

To keep examples buildable without extra features, pass a string to column_dec:

-use std::str::FromStr; @@ -use rust_decimal::Decimal; @@ - let price = Decimal::from_str("2615.54").unwrap(); buffer .table("trades")? .symbol("symbol", "ETH-USD")? .symbol("side", "sell")? - .column_dec("price", &price)? + .column_dec("price", "2615.54")?

If you prefer showcasing binary decimals, I can gate this example behind --features rust_decimal and add a Cargo example feature stanza.

Also applies to: 8-16, 20-21

questdb-rs/src/ingress/mod.rs (1)

1186-1189: Remove debug eprintln! from library code or gate behind logging.

Unconditional stderr printing in SenderBuilder::build is noisy and leaks into users’ apps.

- eprintln!( - "Using protocol version {:?} with max_name_len={}", - protocol_version, max_name_len - ); + // Consider: log::debug!("Using protocol version {:?} with max_name_len={}", protocol_version, max_name_len);

If you want, I can wire up log (debug) behind an optional feature.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 17f2427 and af76951.

📒 Files selected for processing (15)
  • questdb-rs-ffi/src/decimal.rs (1 hunks)
  • questdb-rs-ffi/src/lib.rs (8 hunks)
  • questdb-rs/Cargo.toml (5 hunks)
  • questdb-rs/build.rs (4 hunks)
  • questdb-rs/examples/basic.rs (1 hunks)
  • questdb-rs/examples/http.rs (1 hunks)
  • questdb-rs/examples/protocol_version.rs (3 hunks)
  • questdb-rs/src/ingress/buffer.rs (3 hunks)
  • questdb-rs/src/ingress/decimal.rs (1 hunks)
  • questdb-rs/src/ingress/mod.rs (5 hunks)
  • questdb-rs/src/ingress/sender/http.rs (1 hunks)
  • questdb-rs/src/tests/decimal.rs (1 hunks)
  • questdb-rs/src/tests/http.rs (2 hunks)
  • questdb-rs/src/tests/sender.rs (5 hunks)
  • system_test/test.py (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • questdb-rs/examples/protocol_version.rs
  • questdb-rs/src/tests/http.rs
  • questdb-rs/examples/basic.rs
  • questdb-rs/src/ingress/buffer.rs
  • questdb-rs/src/ingress/sender/http.rs
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-08T14:57:00.111Z
Learnt from: amunra PR: questdb/c-questdb-client#121 File: cpp_test/test_line_sender.cpp:381-388 Timestamp: 2025-10-08T14:57:00.111Z Learning: In tests that explicitly set `protocol_version` to a specific value (e.g., `opts.protocol_version(questdb::ingress::protocol_version::v2)`), timestamp suffix expectations should match that protocol version without requiring runtime branching, since the protocol version is constant for that test. 

Applied to files:

  • questdb-rs/src/tests/sender.rs
  • questdb-rs/build.rs
🧬 Code graph analysis (9)
questdb-rs/examples/http.rs (4)
questdb-rs/examples/basic.rs (1)
  • main (8-34)
questdb-rs/examples/protocol_version.rs (1)
  • main (10-40)
questdb-rs/src/ingress/mod.rs (1)
  • from_conf (405-581)
questdb-rs/src/ingress/sender/mod.rs (1)
  • from_conf (115-117)
questdb-rs/src/tests/sender.rs (2)
system_test/questdb_line_sender.py (1)
  • ProtocolVersion (102-122)
include/questdb/ingress/line_sender.hpp (2)
  • version (991-999)
  • version (991-991)
questdb-rs-ffi/src/decimal.rs (1)
questdb-rs/src/ingress/decimal.rs (4)
  • serialize (73-73)
  • serialize (102-124)
  • serialize (132-149)
  • serialize (154-196)
questdb-rs/src/ingress/decimal.rs (1)
questdb-rs-ffi/src/decimal.rs (1)
  • serialize (105-136)
system_test/test.py (2)
include/questdb/ingress/line_sender.hpp (22)
  • version (991-999)
  • version (991-991)
  • column (252-252)
  • column (305-338)
  • column (305-306)
  • column (353-369)
  • column (353-354)
  • column (397-403)
  • column (397-397)
  • column (419-422)
  • column (419-419)
  • column (522-528)
  • column (522-522)
  • column (532-538)
  • column (532-534)
  • column (543-548)
  • buffer (1151-1157)
  • buffer (1151-1151)
  • buffer (1168-1183)
  • buffer (1168-1168)
  • buffer (1206-1228)
  • buffer (1206-1207)
system_test/questdb_line_sender.py (7)
  • ProtocolVersion (102-122)
  • table (673-679)
  • table (923-925)
  • column (689-746)
  • column (931-935)
  • buffer (876-877)
  • peek (654-661)
questdb-rs-ffi/src/lib.rs (2)
questdb-rs/src/ingress/buffer.rs (6)
  • column_dec (1052-1069)
  • new (99-104)
  • new (183-190)
  • new (206-265)
  • new (305-352)
  • new (488-490)
questdb-rs-ffi/src/decimal.rs (1)
  • new (78-80)
questdb-rs/src/ingress/mod.rs (4)
system_test/questdb_line_sender.py (3)
  • ProtocolVersion (102-122)
  • protocol_version (913-916)
  • max_name_len (919-921)
questdb-rs/src/ingress/buffer.rs (2)
  • fmt (1373-1381)
  • protocol_version (512-514)
questdb-rs/src/ingress/sender/mod.rs (3)
  • fmt (70-72)
  • protocol_version (287-289)
  • max_name_len (297-299)
include/questdb/ingress/line_sender.hpp (4)
  • version (991-999)
  • version (991-991)
  • max_name_len (927-932)
  • max_name_len (927-927)
questdb-rs/src/tests/decimal.rs (2)
questdb-rs/src/tests/mod.rs (1)
  • assert_err_contains (44-69)
questdb-rs/src/ingress/buffer.rs (7)
  • new (99-104)
  • new (183-190)
  • new (206-265)
  • new (305-352)
  • new (488-490)
  • len (525-527)
  • with_max_name_len (502-510)
questdb-rs/build.rs (2)
include/questdb/ingress/line_sender.hpp (30)
  • column (252-252)
  • column (305-338)
  • column (305-306)
  • column (353-369)
  • column (353-354)
  • column (397-403)
  • column (397-397)
  • column (419-422)
  • column (419-419)
  • column (522-528)
  • column (522-522)
  • column (532-538)
  • column (532-534)
  • column (543-548)
  • name (211-217)
  • name (211-211)
  • name (225-231)
  • name (225-225)
  • name (259-265)
  • name (259-259)
  • name (272-278)
  • name (272-272)
  • name (285-291)
  • name (285-285)
  • name (410-416)
  • name (410-410)
  • name (424-427)
  • name (424-424)
  • name (429-432)
  • name (429-429)
system_test/questdb_line_sender.py (2)
  • column (689-746)
  • column (931-935)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: questdb.c-questdb-client (Building and testing on windows-msvc-2019)
  • GitHub Check: questdb.c-questdb-client (Building and testing on windows-msvc-2022)
  • GitHub Check: questdb.c-questdb-client (Building and testing on mac)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux-nightly)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux-beta)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux-stable)
  • GitHub Check: questdb.c-questdb-client (Building and testing cargo fmt and clippy)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux)
  • GitHub Check: questdb.c-questdb-client (Building and testing Vs QuestDB 'master')
🔇 Additional comments (20)
questdb-rs/build.rs (1)

145-147: BigDecimal usage in generated tests: ensure feature is enabled or guard imports and calls.

The generated tests unconditionally import bigdecimal and use BigDecimal::from_str in Column::Decimal arms. If the crate isn’t built with the bigdecimal feature, tests will fail to compile.

Two options:

  • Ensure the json_tests CI profile enables the bigdecimal feature.
  • Or generate feature-gated code and fall back to text decimals when the feature is off.

Example guard:

- use bigdecimal::BigDecimal; - use std::str::FromStr; + #[cfg(feature = "bigdecimal")] + use bigdecimal::BigDecimal; + #[cfg(feature = "bigdecimal")] + use std::str::FromStr;

And for the arm:

- Column::Decimal(column) => writeln!( + #[cfg(feature = "bigdecimal")] + Column::Decimal(column) => writeln!( output, "{} .column_dec({:?}, &BigDecimal::from_str({:?}).unwrap())?", indent, column.name, column.value )?, + #[cfg(not(feature = "bigdecimal"))] + Column::Decimal(column) => writeln!( + output, + "{} .column_dec({:?}, {:?})?", + indent, column.name, column.value + )?,

Would you like me to send a PR update wiring the json_tests profile to build with the bigdecimal feature?

Also applies to: 219-224

questdb-rs/src/tests/sender.rs (1)

244-246: V3 parity for f64 and tests looks good.

  • Param sets include V3 where relevant.
  • f64 binary branch shared for V2|V3 is correct.

Also applies to: 275-279, 869-874

questdb-rs/src/ingress/decimal.rs (1)

130-148: Confirming binary order matches tests.

“=, type, scale, length, mantissa” ordering is consistent with parse_binary_decimal() in tests. The 13‑byte mantissa slice for rust_decimal preserves sign extension as intended. LGTM.

If you want, I can add a debug_assert on the top 3 bytes of mantissa for positive values to document the sign-extension intent.

questdb-rs/Cargo.toml (7)

42-43: LGTM! Decimal dependencies added correctly.

The optional dependencies for rust_decimal (1.38.0) and bigdecimal (0.4.8) are appropriate for feature-gated decimal support. Both versions are current and stable.


72-78: LGTM! Formatting improved for consistency.

The multiline formatting of the sync-sender-http feature enhances readability without altering functionality.


101-105: LGTM! Feature declarations are properly structured.

The rust_decimal and bigdecimal features are correctly declared with clear documentation about their purpose for ILP serialization.


125-127: LGTM! Meta-feature updated to include new dependencies.

The almost-all-features meta-feature correctly includes the new ndarray, rust_decimal, and bigdecimal dependencies, maintaining consistency with the documented pattern.


144-144: LGTM! Example demonstrates rust_decimal integration.

The http example correctly requires the rust_decimal feature, allowing users to see practical usage of decimal support with the rust_decimal crate.


148-148: LGTM! Example demonstrates bigdecimal integration.

The protocol_version example correctly requires the bigdecimal feature, showcasing high-precision decimal usage with the bigdecimal crate.


96-96: No changes needed—the bigdecimal dependency for json_tests is necessary.

The json_tests feature generates test code at build time that directly uses BigDecimal::from_str() when handling decimal columns. At line 217-221 of build.rs, the code generator writes test code that calls BigDecimal::from_str() for each decimal column, making the dependency essential for compilation of the generated tests.

questdb-rs-ffi/src/lib.rs (10)

46-46: LGTM! Decimal module declared for FFI support.

The internal decimal module is properly declared to support the new FFI decimal APIs.


228-229: LGTM! New error code variant added.

The line_sender_error_invalid_decimal error code is properly declared with clear documentation.


258-258: LGTM! Error code mapping implemented.

The mapping from ErrorCode::InvalidDecimal to line_sender_error_invalid_decimal is correctly implemented.


317-321: LGTM! Protocol Version 3 added with clear documentation.

The V3 variant is well-documented, specifying decimal data type support and the minimum required QuestDB server version (9.2.0).


329-329: LGTM! V3 conversion added.

The conversion from FFI ProtocolVersion::V3 to internal ingress::ProtocolVersion::V3 is correctly implemented.


339-339: LGTM! Reverse V3 conversion added.

The conversion from internal ingress::ProtocolVersion::V3 to FFI ProtocolVersion::V3 is correctly implemented.


1005-1026: LGTM! Decimal string API implemented correctly.

The line_sender_buffer_column_dec_str function provides a clean C API for decimal string values. The implementation follows established patterns for column functions, with proper error handling and documentation.


1922-1922: LGTM! Decimal type imported for FFI use.

The Decimal type is correctly imported from the decimal module. Note that this import is placed at the end of the file, which is unconventional but valid Rust.


1028-1053: LGTM! Decimal binary API implementation verified and correct.

The line_sender_buffer_column_dec function correctly implements the C API for decimal values. Verification confirms that Decimal::new(scale: u32, value: &[u8]) matches the usage pattern at line 1049, where scale is passed as u32 and slice::from_raw_parts(data, data_len) correctly provides a &[u8] slice. The serialization logic properly validates scale (≤76) and value size (≤127 bytes) constraints per QuestDB server limitations.


591-591: No issues found; the change is safe and follows Rust best practices.

The implementations of line_sender_column_name::as_name (line 591) and line_sender_table_name::as_name (line 570) are identical—both contain from_utf8_unchecked() and new_unchecked() calls within an internal unsafe block. The signature change from unsafe fn to fn is a correct application of Rust's safe wrapper pattern: the unsafe internals are properly encapsulated, and the method maintains safety invariants. This is confirmed by line 1020 in the codebase, where as_name() is successfully called outside an unsafe block, proving the change is intentional and works correctly.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
questdb-rs-ffi/src/decimal.rs (1)

46-49: Unify wire-format docs with implementation (scale before length).

Docs say "= type len value scale" but code emits "= type scale len value". Align the docs to the implemented order (and to ingress), or change code everywhere to match server if that’s the actual spec. For now, fix the docs to match the current impl.

-/// The binary serialization format is: -/// ```text -/// '=' marker (1 byte) + type ID (1 byte) + length (1 byte) + value bytes + scale (1 byte) -/// ``` +/// The binary serialization format is: +/// ```text +/// '=' (1B) + type (1B) + scale (1B) + length (1B) + value (len B) +/// ```
questdb-rs/src/ingress/decimal.rs (1)

63-67: Doc: mention protocol gating (V3).

Binary decimals are negotiated under Protocol V3. Add note to avoid confusion.

 /// # Binary Format Notes /// - The unscaled value must be encoded in two's complement big-endian format /// - Maximum scale is 76 -/// - Length byte indicates how many bytes follow for the unscaled value +/// - Length byte indicates how many bytes follow for the unscaled value +/// - Available starting with Protocol V3 (text format is always available)
🧹 Nitpick comments (4)
questdb-rs-ffi/src/decimal.rs (2)

38-43: Clarify signed representation (two’s‑complement, big‑endian).

State explicitly that negative values must be two’s‑complement, big‑endian. Helps C/C++ callers construct correct bytes.

-/// - An unscaled integer value (the mantissa), represented as raw bytes in big-endian format +/// - An unscaled integer value (the mantissa), as two's‑complement big‑endian bytes

71-81: Optional: validate at construction.

Early-check scale and length in new() to fail fast for invalid inputs (still keep checks in serialize).

 pub(super) fn new(scale: u32, value: &'a [u8]) -> Self { - Self { scale, value } + // Optional early validation; keep runtime checks in serialize() as source of truth + debug_assert!(scale <= 76, "scale must be <= 76"); + debug_assert!(value.len() <= i8::MAX as usize, "value must be <= 127 bytes"); + Self { scale, value } }
questdb-rs/src/ingress/decimal.rs (2)

136-146: Optional: assert sign-extension invariants for mantissa.

Add a debug assertion to document the 13‑byte choice and catch regressions.

- // We skip the upper 3 bytes (which are sign-extended) and write the lower 13 bytes - let mantissa = self.mantissa(); - out.push(13); - out.extend_from_slice(&mantissa.to_be_bytes()[3..]); + // We skip the upper 3 bytes (sign-extended) and write the lower 13 bytes. + let mantissa = self.mantissa(); + let bytes = mantissa.to_be_bytes(); + debug_assert!( + bytes[0] == bytes[1] && bytes[1] == bytes[2] && (bytes[2] == 0x00 || bytes[2] == 0xFF), + "top 32 bits must be sign extension" + ); + out.push(13); + out.extend_from_slice(&bytes[3..]);

169-176: Safer cast for negative scale upscaling.

Guard already limits |scale| ≤ 76, but using try_from documents intent and avoids future mistakes if the guard changes.

- let unscaled = - unscaled.into_owned() * num_bigint::BigInt::from(10).pow((-scale) as u32); + let exp = u32::try_from(-scale).expect("guard ensures 0 <= -scale <= 76"); + let unscaled = unscaled.into_owned() * num_bigint::BigInt::from(10).pow(exp);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between af76951 and 9489414.

📒 Files selected for processing (2)
  • questdb-rs-ffi/src/decimal.rs (1 hunks)
  • questdb-rs/src/ingress/decimal.rs (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
questdb-rs-ffi/src/decimal.rs (1)
questdb-rs/src/ingress/decimal.rs (4)
  • serialize (73-73)
  • serialize (101-123)
  • serialize (131-148)
  • serialize (153-195)
questdb-rs/src/ingress/decimal.rs (2)
questdb-rs-ffi/src/decimal.rs (1)
  • serialize (104-135)
questdb-rs-ffi/src/lib.rs (7)
  • from (233-260)
  • from (281-288)
  • from (292-299)
  • from (325-331)
  • from (335-341)
  • from (362-371)
  • from (375-384)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: questdb.c-questdb-client (Building and testing on windows-msvc-2019)
  • GitHub Check: questdb.c-questdb-client (Building and testing on windows-msvc-2022)
  • GitHub Check: questdb.c-questdb-client (Building and testing on mac)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux-nightly)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux-beta)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux-stable)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux)
  • GitHub Check: questdb.c-questdb-client (Building and testing Vs QuestDB 'master')
  • GitHub Check: questdb.c-questdb-client (Building and testing cargo fmt and clippy)
🔇 Additional comments (1)
questdb-rs/src/ingress/decimal.rs (1)

132-146: No issues found—all decimal binary format implementations are consistent.

All three implementations (rust_decimal, bigdecimal, and FFI) follow the identical byte order: '='DECIMAL_BINARY_FORMAT_TYPE → scale → length → value. The test parser and documentation confirm this order. There is no discrepancy between emitters or a "different order" in FFI docs to fix.

Likely an incorrect or invalid review comment.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
questdb-rs/src/ingress/mod.rs (1)

81-81: Derive total ordering for ProtocolVersion.

Consider Eq + Ord so ordering is total and usable in ordered collections. This was raised before.

-#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
🧹 Nitpick comments (5)
include/questdb/ingress/line_sender.hpp (3)

392-397: Avoid copies in array customization overload; use perfect forwarding and decay in SFINAE.

Pass user types by forwarding ref and check trait on std::decay_t to prevent unnecessary copies and enable rvalues.

-template < - typename ToArrayViewT, - std::enable_if_t< - questdb::ingress::array::has_array_view_state_v<ToArrayViewT>, - int> = 0> -line_sender_buffer& column(column_name_view name, ToArrayViewT array) +template < + typename ToArrayViewT, + std::enable_if_t< + questdb::ingress::array::has_array_view_state_v<std::decay_t<ToArrayViewT>>, + int> = 0> +line_sender_buffer& column(column_name_view name, ToArrayViewT&& array) { may_init(); - const auto array_view_state = - questdb::ingress::array::to_array_view_state(array); + const auto array_view_state = + questdb::ingress::array::to_array_view_state(std::forward<ToArrayViewT>(array)); return column(name, array_view_state.view()); }

477-489: Binary decimal: consider early client-side validation (optional).

Optionally reject invalid scale (>76) or oversize mantissa (>127 bytes) here to fail fast before crossing FFI. If already enforced in decimal::decimal_view/serializer, ignore.


517-528: Avoid copies in decimal customization overload; prefer perfect forwarding.

Mirror the array overload pattern to avoid copying custom decimal wrappers.

-template < - typename ToDecimalViewT, - std::enable_if_t< - questdb::ingress::decimal::has_decimal_view_state_v<ToDecimalViewT>, - int> = 0> -line_sender_buffer& column(column_name_view name, ToDecimalViewT decimal) +template < + typename ToDecimalViewT, + std::enable_if_t< + questdb::ingress::decimal::has_decimal_view_state_v<std::decay_t<ToDecimalViewT>>, + int> = 0> +line_sender_buffer& column(column_name_view name, ToDecimalViewT&& decimal) { may_init(); - const auto decimal_view_state = - questdb::ingress::decimal::to_decimal_view_state(decimal); + const auto decimal_view_state = + questdb::ingress::decimal::to_decimal_view_state(std::forward<ToDecimalViewT>(decimal)); return column(name, decimal_view_state.view()); }
questdb-rs/src/ingress/mod.rs (1)

77-79: Scope DECIMAL_BINARY_FORMAT_TYPE or justify public export.

If not used outside this crate, make it pub(crate) and drop #[allow(dead_code)]. If other crates (e.g., FFI) consume it, keep it pub and add a brief doc explaining when to use it.

questdb-rs-ffi/src/lib.rs (1)

1005-1026: Doc hint (optional): note V3 requirement.

Consider adding a brief note that decimal types require ProtocolVersion::V3 to help C users diagnose errors sooner.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9489414 and 59cb32e.

📒 Files selected for processing (6)
  • CMakeLists.txt (1 hunks)
  • include/questdb/ingress/line_sender.hpp (3 hunks)
  • questdb-rs-ffi/src/decimal.rs (1 hunks)
  • questdb-rs-ffi/src/lib.rs (8 hunks)
  • questdb-rs/Cargo.toml (5 hunks)
  • questdb-rs/src/ingress/mod.rs (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • questdb-rs-ffi/src/decimal.rs
  • CMakeLists.txt
🧰 Additional context used
🧬 Code graph analysis (3)
questdb-rs-ffi/src/lib.rs (2)
questdb-rs/src/ingress/buffer.rs (6)
  • column_dec (1052-1069)
  • new (99-104)
  • new (183-190)
  • new (206-265)
  • new (305-352)
  • new (488-490)
questdb-rs-ffi/src/decimal.rs (1)
  • new (78-80)
questdb-rs/src/ingress/mod.rs (4)
system_test/questdb_line_sender.py (3)
  • ProtocolVersion (102-122)
  • protocol_version (913-916)
  • max_name_len (919-921)
questdb-rs/src/ingress/buffer.rs (2)
  • fmt (1373-1381)
  • protocol_version (512-514)
questdb-rs/src/ingress/sender/mod.rs (3)
  • fmt (70-72)
  • protocol_version (287-289)
  • max_name_len (297-299)
include/questdb/ingress/line_sender.hpp (4)
  • version (991-999)
  • version (991-991)
  • max_name_len (927-932)
  • max_name_len (927-927)
include/questdb/ingress/line_sender.hpp (4)
include/questdb/ingress/line_sender_core.hpp (5)
  • wrapped_call (183-191)
  • wrapped_call (183-183)
  • _impl (237-240)
  • _impl (242-245)
  • _impl (247-250)
questdb-rs-ffi/src/lib.rs (2)
  • line_sender_buffer_column_dec_str (1013-1026)
  • line_sender_buffer_column_dec (1038-1053)
include/questdb/ingress/line_sender_decimal.hpp (2)
  • decimal (251-256)
  • decimal (251-251)
system_test/questdb_line_sender.py (2)
  • column (689-746)
  • column (931-935)
🔇 Additional comments (13)
questdb-rs/Cargo.toml (3)

42-43: Well‑structured dependency additions for decimal support.

New optional dependencies are cleanly declared with appropriate versions. The optional designation ensures backward compatibility for existing users.


72-78: Feature definitions and gating are properly integrated.

The refactored sync-sender-http feature group, new decimal feature definitions, and the json_tests dependency on bigdecimal all follow established patterns and conventions. Feature comments clearly document the purpose of each new feature.

Also applies to: 96-96, 101-105


125-127: Example and cross-feature coverage appropriately configured.

Adding rust_decimal and bigdecimal to almost-all-features enables comprehensive testing of both decimal serialization pathways. Example required-features declarations are intentional and follow standard cargo conventions—users can skip examples if features are unavailable.

Also applies to: 144-144, 148-148

include/questdb/ingress/line_sender.hpp (2)

27-28: Modular includes look good.

Header surface is clearer by factoring arrays/decimals out.


434-458: Decimal text overload: API and docs read well.

Nice, matches FFI name and clearly documents server requirement.

questdb-rs/src/ingress/mod.rs (4)

65-67: Good separation: decimal module and re-export.

Keeps the public surface tidy while isolating impl details.


101-107: Negotiation preference list LGTM.

Clear highest-to-lowest ordering; simplifies future additions.


439-445: Config parse: allowed values updated correctly.

Accepts "3" and improves error message.


1169-1180: Highest-common-version selection LGTM.

Straightforward and robust against future version additions.

questdb-rs-ffi/src/lib.rs (4)

46-46: Decimal module import looks good.

Keeps FFI surface clean while encapsulating decimal specifics.


228-230: New InvalidDecimal error code mapping: good addition.

Provides a precise category for validation failures.

Also applies to: 258-258


1005-1026: Decimal string FFI: matches Rust Buffer::column_dec path.

Clear error bubbling and consistent naming with other column_* APIs.


591-597: Verification complete: no unsafe wrapping of as_name() calls found.

The search confirms that all 13 callsites to as_name() are using the method correctly as a safe function. Notably, line 1020 demonstrates clean usage outside any outer unsafe block, while others appropriately reside within unsafe blocks for unrelated FFI operations (buffer manipulation). None wrap only the as_name() call itself in an unsafe block, confirming the safety change is properly integrated across the codebase.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
questdb-rs-ffi/src/decimal.rs (1)

104-135: Consider pre-allocating capacity in serialize().

The serialize method writes 4 fixed-size fields (2 for header, 1 for scale, 1 for length) plus the value bytes. Pre-allocating capacity can reduce allocations, especially for repeated calls.

Apply this diff:

 fn serialize(self, out: &mut Vec<u8>) -> Result<()> { // Validate scale constraint (QuestDB server limitation) // The server's decimal implementation supports a maximum scale of 76 if self.scale > 76 { return Err(fmt_error!( InvalidDecimal, "QuestDB ILP does not support decimal scale greater than 76, got {}", self.scale )); } + // Validate value size constraint (protocol limitation) + // The length field is a single byte (i8), limiting value size to 127 bytes + let value_len = self.value.len(); + if value_len > i8::MAX as usize { + return Err(fmt_error!( + InvalidDecimal, + "QuestDB ILP does not support decimal longer than {} bytes, got {}", + i8::MAX, + value_len + )); + } + + // Reserve: header (2) + scale (1) + length (1) + value bytes + out.reserve(4 + value_len); + // Write binary format header out.push(b'='); // Binary encoding marker out.push(DECIMAL_BINARY_FORMAT_TYPE); // Type ID = 23 - // Validate value size constraint (protocol limitation) - // The length field is a single byte (i8), limiting value size to 127 bytes - if self.value.len() > i8::MAX as usize { - return Err(fmt_error!( - InvalidDecimal, - "QuestDB ILP does not support decimal longer than {} bytes, got {}", - i8::MAX, - self.value.len() - )); - } - out.push(self.scale as u8); - out.push(self.value.len() as u8); + out.push(value_len as u8); out.extend_from_slice(self.value); Ok(()) }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9489414 and 59cb32e.

📒 Files selected for processing (6)
  • CMakeLists.txt (1 hunks)
  • include/questdb/ingress/line_sender.hpp (3 hunks)
  • questdb-rs-ffi/src/decimal.rs (1 hunks)
  • questdb-rs-ffi/src/lib.rs (8 hunks)
  • questdb-rs/Cargo.toml (5 hunks)
  • questdb-rs/src/ingress/mod.rs (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • questdb-rs/Cargo.toml
🧰 Additional context used
🧬 Code graph analysis (4)
include/questdb/ingress/line_sender.hpp (4)
include/questdb/ingress/line_sender_core.hpp (5)
  • wrapped_call (183-191)
  • wrapped_call (183-183)
  • _impl (237-240)
  • _impl (242-245)
  • _impl (247-250)
questdb-rs-ffi/src/lib.rs (2)
  • line_sender_buffer_column_dec_str (1013-1026)
  • line_sender_buffer_column_dec (1038-1053)
include/questdb/ingress/line_sender_decimal.hpp (2)
  • decimal (251-256)
  • decimal (251-251)
system_test/questdb_line_sender.py (2)
  • column (689-746)
  • column (931-935)
questdb-rs-ffi/src/lib.rs (2)
questdb-rs/src/ingress/buffer.rs (6)
  • column_dec (1052-1069)
  • new (99-104)
  • new (183-190)
  • new (206-265)
  • new (305-352)
  • new (488-490)
questdb-rs-ffi/src/decimal.rs (1)
  • new (78-80)
questdb-rs/src/ingress/mod.rs (4)
system_test/questdb_line_sender.py (3)
  • ProtocolVersion (102-122)
  • protocol_version (913-916)
  • max_name_len (919-921)
questdb-rs/src/ingress/buffer.rs (2)
  • fmt (1373-1381)
  • protocol_version (512-514)
questdb-rs/src/ingress/sender/mod.rs (3)
  • fmt (70-72)
  • protocol_version (287-289)
  • max_name_len (297-299)
include/questdb/ingress/line_sender.hpp (4)
  • version (991-999)
  • version (991-991)
  • max_name_len (927-932)
  • max_name_len (927-927)
questdb-rs-ffi/src/decimal.rs (2)
examples/line_sender_cpp_example_decimal_custom.cpp (1)
  • scale (12-15)
questdb-rs/src/ingress/decimal.rs (4)
  • serialize (73-73)
  • serialize (101-123)
  • serialize (131-148)
  • serialize (153-195)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: questdb.c-questdb-client (Building and testing on windows-msvc-2019)
  • GitHub Check: questdb.c-questdb-client (Building and testing on windows-msvc-2022)
  • GitHub Check: questdb.c-questdb-client (Building and testing on mac)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux-nightly)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux-beta)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux-stable)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux)
  • GitHub Check: questdb.c-questdb-client (Building and testing Vs QuestDB 'master')
  • GitHub Check: questdb.c-questdb-client (Building and testing cargo fmt and clippy)
🔇 Additional comments (10)
CMakeLists.txt (1)

168-173: Cleanly integrated decimal C++ examples into build system.

The two new compile_example targets follow the established pattern, maintain naming consistency, and are properly placed within the QUESTDB_TESTS_AND_EXAMPLES conditional block alongside other examples. The additions align with the PR objective to provide C++ examples for decimal support (custom types and binary encoding).

questdb-rs-ffi/src/lib.rs (2)

1005-1026: LGTM! Text-based decimal column API is correct.

The function correctly unwraps the buffer, column name, and UTF-8 string value, then delegates to buffer.column_dec(). Error handling via bubble_err_to_c! is appropriate.


1028-1053: LGTM! Binary decimal column API is correct.

The function correctly constructs a Decimal from the raw scale and byte slice, then delegates to buffer.column_dec(). The use of slice::from_raw_parts is safe here as the FFI boundary guarantees the pointer and length are valid for the duration of the call.

questdb-rs/src/ingress/mod.rs (3)

65-66: LGTM! Decimal module and serializer are properly exposed.

The DecimalSerializer trait is correctly re-exported for public consumption, allowing buffer operations and external implementations to serialize decimal values.


101-106: LGTM! Protocol version preference ordering is correct.

The SUPPORTED_PROTOCOL_VERSIONS constant correctly lists protocol versions in descending order (V3, V2, V1), ensuring the client negotiates the highest mutually supported version with the server.


1169-1179: LGTM! Negotiation logic correctly uses the supported versions constant.

The protocol negotiation now uses SUPPORTED_PROTOCOL_VERSIONS.iter().find(...) to select the highest mutually supported version, replacing the previous hardcoded checks. This is more maintainable and automatically handles V3.

include/questdb/ingress/line_sender.hpp (4)

27-28: LGTM! Modular header includes improve organization.

Replacing the previous includes with line_sender_array.hpp and line_sender_decimal.hpp modularizes the codebase and makes dependencies explicit.


434-458: LGTM! Text-based decimal column API is well-documented.

The overload for decimal_str_view correctly delegates to the underlying FFI function. The documentation clearly explains the trade-offs between text and binary formats and notes the server version requirement.


460-489: LGTM! Binary decimal column API is well-documented.

The overload for decimal_view correctly passes the scale and data to the FFI layer. The documentation clearly describes the wire format constraints (scale ≤ 76, mantissa ≤ 127 bytes).


517-528: LGTM! Customization point for decimal types follows established pattern.

The templated overload with has_decimal_view_state_v constraint mirrors the array customization point pattern, allowing users to extend support to custom decimal types. The documentation clearly explains the customization requirements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
questdb-rs-ffi/src/lib.rs (1)

1038-1058: Reject NULL when data_len>0 and enforce protocol bounds.

Current code treats NULL as empty slice regardless of length; also no guards for scale/mantissa. This risks silent data corruption and violates FFI expectations. Apply strict checks and return InvalidDecimal on violations.

 pub unsafe extern "C" fn line_sender_buffer_column_dec( @@ ) -> bool { - unsafe { - let data = if data.is_null() { - &[] - } else { - slice::from_raw_parts(data, data_len) - }; - let buffer = unwrap_buffer_mut(buffer); - let name = name.as_name(); - let decimal = Decimal::new(scale, data); - bubble_err_to_c!(err_out, buffer.column_dec(name, decimal)); - } - true + unsafe { + // Protocol constraints + const MAX_SCALE: u32 = 76; + const MAX_MANTISSA: usize = 127; + if scale > MAX_SCALE { + set_err_out( + err_out, + ErrorCode::InvalidDecimal, + format!("scale {} exceeds maximum {}", scale, MAX_SCALE), + ); + return false; + } + if data_len > MAX_MANTISSA { + set_err_out( + err_out, + ErrorCode::InvalidDecimal, + format!("mantissa length {} exceeds maximum {}", data_len, MAX_MANTISSA), + ); + return false; + } + // Enforce pointer validity when length > 0 + let bytes: &[u8] = if data_len == 0 { + &[] + } else { + if data.is_null() { + set_err_out( + err_out, + ErrorCode::InvalidDecimal, + "null data pointer with non-zero length".to_string(), + ); + return false; + } + slice::from_raw_parts(data, data_len) + }; + let buffer = unwrap_buffer_mut(buffer); + let name = name.as_name(); + let decimal = Decimal::new(scale, bytes); + bubble_err_to_c!(err_out, buffer.column_dec(name, decimal)); + true + } }
🧹 Nitpick comments (5)
questdb-rs/src/ingress/mod.rs (3)

81-81: Consider deriving Eq/Ord for total ordering.

Enum has a natural total order by discriminant. Deriving Eq + Ord makes sorting/Map keys straightforward and future-proofs comparisons.

-#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]

101-107: Prefer a slice to avoid array-length maintenance.

Using a slice removes the need to update the const length when adding V4.

-const SUPPORTED_PROTOCOL_VERSIONS: [ProtocolVersion; 3] = [ +const SUPPORTED_PROTOCOL_VERSIONS: &[ProtocolVersion] = &[ ProtocolVersion::V3, ProtocolVersion::V2, ProtocolVersion::V1, ];

1169-1180: Negotiation: good; improve error for diagnostics (optional).

Include both client and server sets in the error to ease support.

- .ok_or_else(|| { - fmt!( - ProtocolVersionError, - "Server does not support any of the client protocol versions: {:?}", - SUPPORTED_PROTOCOL_VERSIONS - ) - })? + .ok_or_else(|| { + fmt!( + ProtocolVersionError, + "No common protocol version. server={:?}, client={:?}", + protocol_versions, + SUPPORTED_PROTOCOL_VERSIONS + ) + })?
questdb-rs-ffi/src/lib.rs (2)

591-598: Safety contract inconsistency with table_name.as_name.

column_name.as_name is now safe but still uses unchecked UTF-8 and naming invariants; table_name.as_name remains unsafe. Consider aligning both (either make both safe with clear invariants or both unsafe) to avoid confusion.
Would you prefer making table_name.as_name safe too, given both go through validated init?


1927-1928: Move use crate::decimal::Decimal near other imports.

Keeps imports localized and avoids split scopes.

+// near other use statements at the top of the file: +use crate::decimal::Decimal; @@ -use crate::decimal::Decimal;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 59cb32e and 1ede6d7.

📒 Files selected for processing (2)
  • questdb-rs-ffi/src/lib.rs (8 hunks)
  • questdb-rs/src/ingress/mod.rs (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
questdb-rs/src/ingress/mod.rs (2)
system_test/questdb_line_sender.py (1)
  • ProtocolVersion (102-122)
include/questdb/ingress/line_sender.hpp (2)
  • version (991-999)
  • version (991-991)
questdb-rs-ffi/src/lib.rs (2)
questdb-rs/src/ingress/buffer.rs (6)
  • column_dec (1052-1069)
  • new (99-104)
  • new (183-190)
  • new (206-265)
  • new (305-352)
  • new (488-490)
questdb-rs-ffi/src/decimal.rs (1)
  • new (78-80)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: questdb.c-questdb-client (Building and testing on windows-msvc-2019)
  • GitHub Check: questdb.c-questdb-client (Building and testing on windows-msvc-2022)
  • GitHub Check: questdb.c-questdb-client (Building and testing on mac)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux-nightly)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux-beta)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux-stable)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux)
  • GitHub Check: questdb.c-questdb-client (Building and testing cargo fmt and clippy)
  • GitHub Check: questdb.c-questdb-client (Building and testing Vs QuestDB 'master')
🔇 Additional comments (9)
questdb-rs/src/ingress/mod.rs (5)

65-67: Re-export looks good.

Publicly exposing DecimalSerializer here keeps buffer API ergonomics clean.


77-79: OK to expose DECIMAL_BINARY_FORMAT_TYPE.

Public visibility is appropriate for FFI use; allow(dead_code) is fine.


94-99: V3 docs/readiness: LGTM.

Clear statement of decimal support and server ≥ 9.2.0 requirement.


113-114: Display for V3: LGTM.


439-445: Config parser accepts “3”: LGTM.

Value mapping and allowed-values message updated correctly.

questdb-rs-ffi/src/lib.rs (4)

46-46: Module wiring for decimal: LGTM.


228-230: New InvalidDecimal error code and mapping: LGTM.

Also applies to: 258-259


314-322: ProtocolVersion V3 in FFI: LGTM.


1005-1026: Remove this review comment—no naming divergence exists.

The grep results show no references to line_sender_buffer_column_decimal_str anywhere in the codebase. All usages—implementation, headers, examples, Python tests, C++ bindings, and documentation—consistently use line_sender_buffer_column_dec_str. There is no divergence to reconcile and no alias is needed.

Likely an incorrect or invalid review comment.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
examples/line_sender_cpp_example_decimal_binary.cpp (1)

23-26: Add documentation for byte array representation and endianness.

The example shows a single-byte unsigned array {123}, but doesn't clarify:

  • Endianness for multi-byte values (little-endian vs big-endian)
  • How to represent negative decimals (signed interpretation, two's complement?)
  • Why uint8_t is used vs int8_t or other signed types

Users extending this example to larger or negative values may construct the byte array incorrectly.

Consider adding a comment explaining the byte representation:

 const uint8_t price_unscaled_value[] = {123}; - // 123 with a scale of 1 gives a decimal of 12.3 + // 123 with a scale of 1 gives a decimal of 12.3 + // The unscaled value is represented as a little-endian byte array. + // For multi-byte values, use: {low_byte, high_byte, ...} + // For negative values, use two's complement representation. const auto price_value = questdb::ingress::decimal::decimal_view(1, price_unscaled_value);

Alternatively, add a reference to documentation that explains the binary encoding format.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1ede6d7 and e86a18d.

📒 Files selected for processing (2)
  • examples/line_sender_cpp_example_decimal_binary.cpp (1 hunks)
  • examples/line_sender_cpp_example_decimal_custom.cpp (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
examples/line_sender_cpp_example_decimal_binary.cpp (3)
include/questdb/ingress/line_sender.hpp (6)
  • buffer (1151-1157)
  • buffer (1151-1151)
  • buffer (1168-1183)
  • buffer (1168-1168)
  • buffer (1206-1228)
  • buffer (1206-1207)
examples/line_sender_cpp_example_decimal_custom.cpp (2)
  • example (62-105)
  • example (62-62)
examples/line_sender_cpp_example.cpp (2)
  • example (8-48)
  • example (8-8)
examples/line_sender_cpp_example_decimal_custom.cpp (2)
include/questdb/ingress/line_sender.hpp (6)
  • buffer (1151-1157)
  • buffer (1151-1151)
  • buffer (1168-1183)
  • buffer (1168-1168)
  • buffer (1206-1228)
  • buffer (1206-1207)
examples/line_sender_cpp_example_decimal_binary.cpp (2)
  • example (7-51)
  • example (7-7)
🪛 Clang (14.0.6)
examples/line_sender_cpp_example_decimal_binary.cpp

[error] 1-1: 'questdb/ingress/line_sender.hpp' file not found

(clang-diagnostic-error)

examples/line_sender_cpp_example_decimal_custom.cpp

[error] 1-1: 'questdb/ingress/line_sender.hpp' file not found

(clang-diagnostic-error)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: questdb.c-questdb-client (Building and testing on windows-msvc-2019)
  • GitHub Check: questdb.c-questdb-client (Building and testing on windows-msvc-2022)
  • GitHub Check: questdb.c-questdb-client (Building and testing on mac)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux-nightly)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux-beta)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux-stable)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux)
  • GitHub Check: questdb.c-questdb-client (Building and testing cargo fmt and clippy)
  • GitHub Check: questdb.c-questdb-client (Building and testing Vs QuestDB 'master')
🔇 Additional comments (8)
examples/line_sender_cpp_example_decimal_binary.cpp (2)

60-64: Help text correctly uses dynamic program name.

The previous issue with hardcoded program name has been resolved. Using argv[0] ensures the help text displays the actual executable name.


71-84: LGTM!

The argument parsing and program flow are correct. Default values are appropriate, and the return value logic correctly maps the example's boolean result to standard exit codes.

examples/line_sender_cpp_example_decimal_custom.cpp (6)

1-6: LGTM: Clean includes and namespace usage.

The includes and using declarations are appropriate for the example.


8-16: LGTM: ViewHolder pattern is safe for synchronous serialization.

The ViewHolder struct correctly demonstrates the intermediate state holder pattern. As confirmed in previous reviews, the view() method safely returns a decimal_view referencing the data array because:

  1. to_decimal_view_state_impl returns a ViewHolder by value
  2. The templated column method stores it as a local variable decimal_view_state
  3. Serialization via line_sender_buffer_column_dec completes synchronously
  4. The data array remains valid throughout this scope

This pattern effectively shows how to serialize custom decimal types without storing the byte array as a class member.


20-42: LGTM: Clean decimal value class.

The Decimal32 class is well-structured with appropriate types (uint32_t for scale, int32_t for signed unscaled values) and provides clear accessors.


47-59: LGTM: Correct big-endian encoding in customization point.

The to_decimal_view_state_impl function correctly demonstrates the ADL customization point pattern for integrating custom decimal types. The big-endian byte encoding is correct, properly handling signed int32_t values through the cast to uint8_t.


62-105: LGTM: Example demonstrates correct decimal usage with protocol V3.

The example function correctly:

  • Uses protocol version 3 for decimal column support
  • Demonstrates the custom decimal type integration via the customization point
  • Follows established patterns from other examples (pre-validated names, literals usage)
  • Includes appropriate error handling

107-138: LGTM: Help and main functions follow best practices.

The helper and main functions correctly:

  • Use argv[0] for dynamic executable name in usage string (addressing previous review feedback)
  • Provide sensible defaults (localhost:9009)
  • Follow standard patterns from other examples
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
examples/line_sender_c_example_decimal_binary.c (1)

54-58: Consider documenting byte order for multi-byte values.

The decimal binary usage is correct. The single-byte unscaled value (123) with scale 1 correctly represents 12.3.

For educational purposes, consider adding a comment about byte order for users who need larger values:

 // 123 with a scale of 1 gives a decimal of 12.3 +// For multi-byte values, use big-endian byte order (most significant byte first) const uint8_t price_unscaled_value[] = {123};

This would help users understand how to represent larger unscaled values that don't fit in a single byte.

examples/line_sender_cpp_example_decimal_binary.cpp (1)

22-25: Consider moving the explanatory comment before the variable declaration.

The comment explaining that "123 with a scale of 1 gives a decimal of 12.3" appears after the price_unscaled_value array declaration. Moving it before or inline would improve readability.

Apply this diff to improve comment placement:

- const auto amount_name = "amount"_cn; - const uint8_t price_unscaled_value[] = {123}; - // 123 with a scale of 1 gives a decimal of 12.3 + const auto amount_name = "amount"_cn; + // 123 with a scale of 1 gives a decimal of 12.3 + const uint8_t price_unscaled_value[] = {123}; const auto price_value =
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e86a18d and 5b17715.

📒 Files selected for processing (5)
  • CMakeLists.txt (2 hunks)
  • examples/line_sender_c_example_decimal_binary.c (1 hunks)
  • examples/line_sender_cpp_example_decimal_binary.cpp (1 hunks)
  • examples/line_sender_cpp_example_decimal_custom.cpp (1 hunks)
  • examples/line_sender_cpp_example_http.cpp (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • CMakeLists.txt
🧰 Additional context used
🧬 Code graph analysis (3)
examples/line_sender_c_example_decimal_binary.c (2)
examples/line_sender_c_example_http.c (3)
  • example (7-89)
  • displayed_help (91-107)
  • main (109-122)
questdb-rs-ffi/src/lib.rs (14)
  • line_sender_utf8_init (521-537)
  • line_sender_from_conf (1722-1735)
  • line_sender_buffer_new_for_sender (1787-1795)
  • line_sender_buffer_reserve (762-770)
  • line_sender_buffer_table (890-900)
  • line_sender_buffer_symbol (910-921)
  • line_sender_buffer_column_dec (1038-1058)
  • line_sender_buffer_column_f64 (970-981)
  • line_sender_buffer_at_nanos (1254-1265)
  • line_sender_flush (1845-1856)
  • line_sender_buffer_free (730-736)
  • line_sender_close (1810-1816)
  • line_sender_error_msg (399-408)
  • line_sender_error_free (412-418)
examples/line_sender_cpp_example_decimal_custom.cpp (1)
examples/line_sender_cpp_example_decimal_binary.cpp (2)
  • example (7-50)
  • example (7-7)
examples/line_sender_cpp_example_decimal_binary.cpp (4)
include/questdb/ingress/line_sender.hpp (6)
  • buffer (1151-1157)
  • buffer (1151-1151)
  • buffer (1168-1183)
  • buffer (1168-1168)
  • buffer (1206-1228)
  • buffer (1206-1207)
examples/line_sender_c_example_decimal_binary.c (1)
  • example (7-92)
examples/line_sender_cpp_example_decimal_custom.cpp (2)
  • example (62-104)
  • example (62-62)
examples/line_sender_cpp_example_http.cpp (2)
  • example (8-47)
  • example (8-8)
🪛 Clang (14.0.6)
examples/line_sender_c_example_decimal_binary.c

[error] 1-1: 'questdb/ingress/line_sender.h' file not found

(clang-diagnostic-error)

examples/line_sender_cpp_example_decimal_custom.cpp

[error] 1-1: 'questdb/ingress/line_sender.hpp' file not found

(clang-diagnostic-error)

examples/line_sender_cpp_example_decimal_binary.cpp

[error] 1-1: 'questdb/ingress/line_sender.hpp' file not found

(clang-diagnostic-error)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: questdb.c-questdb-client (Building and testing on windows-msvc-2019)
  • GitHub Check: questdb.c-questdb-client (Building and testing on windows-msvc-2022)
  • GitHub Check: questdb.c-questdb-client (Building and testing on mac)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux-nightly)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux-beta)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux-stable)
  • GitHub Check: questdb.c-questdb-client (Building and testing on linux)
  • GitHub Check: questdb.c-questdb-client (Building and testing Vs QuestDB 'master')
  • GitHub Check: questdb.c-questdb-client (Building and testing cargo fmt and clippy)
🔇 Additional comments (12)
examples/line_sender_cpp_example_http.cpp (4)

6-6: LGTM! Decimal namespace import enables the literal syntax.

The namespace import correctly brings the _decimal user-defined literal into scope for use on line 28.


28-28: LGTM! Demonstrates the new decimal feature correctly.

The decimal literal syntax effectively showcases the new decimal API. The C++ design elegantly uses .column() overloading with the decimal type created by the _decimal literal operator.


56-61: LGTM! Help text improvements are clear and correct.

The reformatted help text enhances readability. The port change from 9009 to 9000 is correct—9000 is the standard HTTP port for QuestDB, while 9009 is for TCP/ILP.


76-76: LGTM! Default port is consistent and correct.

The default port change to 9000 is consistent with the help text and appropriate for this HTTP example.

examples/line_sender_cpp_example_decimal_custom.cpp (1)

1-137: LGTM! Clean example demonstrating custom decimal type integration.

The implementation correctly demonstrates the ADL customization point pattern for integrating a custom decimal type with the QuestDB line sender. All previous review concerns have been properly addressed:

  • Big-endian encoding correctly handles signed int32_t values
  • ViewHolder lifetime is safe (lives as a local variable during serialization)
  • Protocol negotiation handles V3 requirement automatically
  • Error handling and resource management follow best practices

The example clearly illustrates the three key components: custom decimal type, ViewHolder for binary representation, and the to_decimal_view_state_impl customization point placed in the type's namespace for ADL.

examples/line_sender_c_example_decimal_binary.c (3)

1-5: LGTM: Includes are appropriate.

All necessary headers are included for the example.

Note: The static analysis warning about the missing header is a false positive—the header exists in the actual build environment.


7-92: Excellent implementation with proper resource cleanup.

The example function correctly demonstrates the decimal binary format API. All previous issues have been properly addressed:

  1. ✅ Memory leak fixed: Buffer is now freed on the success path (line 78) in addition to the error path (line 89).
  2. ✅ Unused variable removed: The example now focuses solely on binary decimal format.

The resource management is correct with proper cleanup on both success and error paths.


94-129: LGTM: Helper and main functions are well-structured.

The help display and main function follow standard patterns with appropriate defaults and clear usage information.

examples/line_sender_cpp_example_decimal_binary.cpp (4)

1-5: LGTM: Includes and literal imports are appropriate.

The file includes the necessary headers and imports literal namespaces, which is acceptable practice for example code.


7-50: LGTM: Example demonstrates decimal binary usage correctly.

The example properly demonstrates:

  • Configuration using HTTP protocol
  • Decimal construction with explicit scale and unscaled byte array
  • Usage of decimal_view API for binary decimal representation
  • Proper error handling and resource management via RAII

The mathematical representation (scale=1, unscaled=123 → 12.3) is correct and well-documented.


52-69: LGTM: Help display logic is correct and self-documenting.

The function correctly uses argv[0] for the program name (avoiding the hardcoded name issue from past reviews) and accurately documents the defaults.


71-84: LGTM: Main function follows standard example pattern.

The argument parsing and exit code handling are correct and consistent with other examples in the repository.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

DO NOT MERGE This PR should NOT be merged tandem

2 participants