This is an automated email from the ASF dual-hosted git repository.
yuxia pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fluss-rust.git
The following commit(s) were added to refs/heads/main by this push:
new b47ef7e chore: update docs after opaque types CPP rework (#349)
b47ef7e is described below
commit b47ef7e5d6d53c87fb41b63a9969b71c1910eb1f
Author: Anton Borisov <[email protected]>
AuthorDate: Wed Feb 18 02:11:18 2026 +0000
chore: update docs after opaque types CPP rework (#349)
---
bindings/cpp/include/fluss.hpp | 3 +-
website/docs/user-guide/cpp/api-reference.md | 179 ++++++++++++++-------
website/docs/user-guide/cpp/data-types.md | 98 ++++++++---
.../user-guide/cpp/example/partitioned-tables.md | 7 +-
.../user-guide/cpp/example/primary-key-tables.md | 11 +-
5 files changed, 202 insertions(+), 96 deletions(-)
diff --git a/bindings/cpp/include/fluss.hpp b/bindings/cpp/include/fluss.hpp
index 00b5000..a1a6b1f 100644
--- a/bindings/cpp/include/fluss.hpp
+++ b/bindings/cpp/include/fluss.hpp
@@ -716,10 +716,9 @@ class ScanRecords {
Iterator begin() const { return Iterator(this, 0); }
Iterator end() const { return Iterator(this, Size()); }
+ private:
/// Returns the column name-to-index map (lazy-built, cached).
const std::shared_ptr<detail::ColumnMap>& GetColumnMap() const;
-
- private:
friend class LogScanner;
void Destroy() noexcept;
void BuildColumnMap() const;
diff --git a/website/docs/user-guide/cpp/api-reference.md
b/website/docs/user-guide/cpp/api-reference.md
index 3b93916..a20739b 100644
--- a/website/docs/user-guide/cpp/api-reference.md
+++ b/website/docs/user-guide/cpp/api-reference.md
@@ -145,9 +145,9 @@ Complete API reference for the Fluss C++ client.
## `Lookuper`
-| Method |
Description |
-|----------------------------------------------------------------------------|-----------------------------|
-| `Lookup(const GenericRow& pk_row, bool& found, GenericRow& out) -> Result` |
Lookup a row by primary key |
+| Method | Description
|
+|---------------------------------------------------------------|-----------------------------|
+| `Lookup(const GenericRow& pk_row, LookupResult& out) -> Result` | Lookup a
row by primary key |
## `LogScanner`
@@ -164,21 +164,7 @@ Complete API reference for the Fluss C++ client.
## `GenericRow`
-### Index-Based Getters
-
-| Method | Description
|
-|------------------------------------------------|--------------------------------|
-| `GetBool(size_t idx) -> bool` | Get boolean value at index
|
-| `GetInt32(size_t idx) -> int32_t` | Get 32-bit integer at index
|
-| `GetInt64(size_t idx) -> int64_t` | Get 64-bit integer at index
|
-| `GetFloat32(size_t idx) -> float` | Get 32-bit float at index
|
-| `GetFloat64(size_t idx) -> double` | Get 64-bit float at index
|
-| `GetString(size_t idx) -> std::string` | Get string at index
|
-| `GetBytes(size_t idx) -> std::vector<uint8_t>` | Get binary data at index
|
-| `GetDate(size_t idx) -> Date` | Get date at index
|
-| `GetTime(size_t idx) -> Time` | Get time at index
|
-| `GetTimestamp(size_t idx) -> Timestamp` | Get timestamp at index
|
-| `DecimalToString(size_t idx) -> std::string` | Get decimal as string at
index |
+`GenericRow` is a **write-only** row used for append, upsert, delete, and
lookup key construction. For reading field values from scan or lookup results,
see [`RowView`](#rowview) and [`LookupResult`](#lookupresult).
### Index-Based Setters
@@ -215,34 +201,121 @@ When using `table.NewRow()`, the `Set()` method
auto-routes to the correct type
| `Set(const std::string& name, const Time& value)` | Set time by
column name |
| `Set(const std::string& name, const Timestamp& value)` | Set timestamp by
column name |
-### Row Inspection
+## `RowView`
+
+Read-only row view for scan results. Provides zero-copy access to string and
bytes data.
+
+:::warning Lifetime
+`RowView` borrows from `ScanRecords`. It must not outlive the `ScanRecords`
that produced it (similar to `std::string_view` borrowing from `std::string`).
+:::
-| Method | Description |
-|------------------------------------|----------------------------------|
-| `FieldCount() -> size_t` | Get the number of fields |
-| `GetType(size_t idx) -> DatumType` | Get the datum type at index |
-| `IsNull(size_t idx) -> bool` | Check if field is null |
-| `IsDecimal(size_t idx) -> bool` | Check if field is a decimal type |
+### Index-Based Getters
+
+| Method | Description
|
+|------------------------------------------------------------|--------------------------------|
+| `FieldCount() -> size_t` | Get the number
of fields |
+| `GetType(size_t idx) -> TypeId` | Get the type at
index |
+| `IsNull(size_t idx) -> bool` | Check if field
is null |
+| `GetBool(size_t idx) -> bool` | Get boolean
value at index |
+| `GetInt32(size_t idx) -> int32_t` | Get 32-bit
integer at index |
+| `GetInt64(size_t idx) -> int64_t` | Get 64-bit
integer at index |
+| `GetFloat32(size_t idx) -> float` | Get 32-bit
float at index |
+| `GetFloat64(size_t idx) -> double` | Get 64-bit
float at index |
+| `GetString(size_t idx) -> std::string_view` | Get string at
index (zero-copy)|
+| `GetBytes(size_t idx) -> std::pair<const uint8_t*, size_t>`| Get binary data
at index (zero-copy)|
+| `GetDate(size_t idx) -> Date` | Get date at
index |
+| `GetTime(size_t idx) -> Time` | Get time at
index |
+| `GetTimestamp(size_t idx) -> Timestamp` | Get timestamp
at index |
+| `IsDecimal(size_t idx) -> bool` | Check if field
is a decimal type|
+| `GetDecimalString(size_t idx) -> std::string` | Get decimal as
string at index |
+
+### Name-Based Getters
+
+| Method | Description
|
+|---------------------------------------------------------|------------------------------------|
+| `IsNull(const std::string& name) -> bool` | Check if field is
null by name |
+| `GetBool(const std::string& name) -> bool` | Get boolean by
column name |
+| `GetInt32(const std::string& name) -> int32_t` | Get 32-bit integer
by column name |
+| `GetInt64(const std::string& name) -> int64_t` | Get 64-bit integer
by column name |
+| `GetFloat32(const std::string& name) -> float` | Get 32-bit float
by column name |
+| `GetFloat64(const std::string& name) -> double` | Get 64-bit float
by column name |
+| `GetString(const std::string& name) -> std::string_view`| Get string by
column name |
+| `GetBytes(const std::string& name) -> std::pair<const uint8_t*, size_t>` |
Get binary data by column name |
+| `GetDate(const std::string& name) -> Date` | Get date by column
name |
+| `GetTime(const std::string& name) -> Time` | Get time by column
name |
+| `GetTimestamp(const std::string& name) -> Timestamp` | Get timestamp by
column name |
+| `GetDecimalString(const std::string& name) -> std::string` | Get decimal as
string by column name |
## `ScanRecord`
-| Field | Type | Description
|
-|----------------|--------------------------|---------------------------------------------------------------------|
-| `bucket_id` | `int32_t` | Bucket this record belongs to
|
-| `partition_id` | `std::optional<int64_t>` | Partition ID (present only for
partitioned tables) |
-| `offset` | `int64_t` | Record offset in the log
|
-| `timestamp` | `int64_t` | Record timestamp
|
-| `change_type` | `ChangeType` | Change type (AppendOnly, Insert,
UpdateBefore, UpdateAfter, Delete) |
-| `row` | `RowView` | Row data
|
+:::warning Lifetime
+`ScanRecord` contains a `RowView` that borrows from `ScanRecords`. It must not
outlive the `ScanRecords` that produced it.
+:::
+
+| Field | Type | Description |
+|----------------|-------------------------|----------------------------------|
+| `bucket_id` | `int32_t` | Bucket this record belongs to |
+| `partition_id` | `std::optional<int64_t>`| Partition ID (if partitioned) |
+| `offset` | `int64_t` | Record offset in the log |
+| `timestamp` | `int64_t` | Record timestamp |
+| `change_type` | `ChangeType` | Type of change (see `ChangeType`)|
+| `row` | `RowView` | Read-only row view for field
access |
## `ScanRecords`
-| Method | Description
|
-|-----------------------------------------------|--------------------------------------------|
-| `Size() -> size_t` | Number of records
|
-| `Empty() -> bool` | Check if empty
|
-| `operator[](size_t idx) -> const ScanRecord&` | Access record by index
|
-| `begin() / end()` | Iterator support for
range-based for loops |
+| Method | Description
|
+|----------------------------------------|--------------------------------------------|
+| `Size() -> size_t` | Number of records
|
+| `Empty() -> bool` | Check if empty
|
+| `operator[](size_t idx) -> ScanRecord` | Access record by index
|
+| `begin() / end()` | Iterator support for range-based
for loops |
+
+## `LookupResult`
+
+Read-only result for lookup operations. Provides zero-copy access to field
values.
+
+### Metadata
+
+| Method | Description |
+|-----------------------------|--------------------------------|
+| `Found() -> bool` | Whether a matching row was found |
+| `FieldCount() -> size_t` | Get the number of fields |
+
+### Index-Based Getters
+
+| Method | Description
|
+|------------------------------------------------------------|--------------------------------|
+| `GetType(size_t idx) -> TypeId` | Get the type at
index |
+| `IsNull(size_t idx) -> bool` | Check if field
is null |
+| `GetBool(size_t idx) -> bool` | Get boolean
value at index |
+| `GetInt32(size_t idx) -> int32_t` | Get 32-bit
integer at index |
+| `GetInt64(size_t idx) -> int64_t` | Get 64-bit
integer at index |
+| `GetFloat32(size_t idx) -> float` | Get 32-bit
float at index |
+| `GetFloat64(size_t idx) -> double` | Get 64-bit
float at index |
+| `GetString(size_t idx) -> std::string_view` | Get string at
index (zero-copy)|
+| `GetBytes(size_t idx) -> std::pair<const uint8_t*, size_t>`| Get binary data
at index (zero-copy)|
+| `GetDate(size_t idx) -> Date` | Get date at
index |
+| `GetTime(size_t idx) -> Time` | Get time at
index |
+| `GetTimestamp(size_t idx) -> Timestamp` | Get timestamp
at index |
+| `IsDecimal(size_t idx) -> bool` | Check if field
is a decimal type|
+| `GetDecimalString(size_t idx) -> std::string` | Get decimal as
string at index |
+
+### Name-Based Getters
+
+| Method | Description
|
+|---------------------------------------------------------|------------------------------------|
+| `IsNull(const std::string& name) -> bool` | Check if field is
null by name |
+| `GetBool(const std::string& name) -> bool` | Get boolean by
column name |
+| `GetInt32(const std::string& name) -> int32_t` | Get 32-bit integer
by column name |
+| `GetInt64(const std::string& name) -> int64_t` | Get 64-bit integer
by column name |
+| `GetFloat32(const std::string& name) -> float` | Get 32-bit float
by column name |
+| `GetFloat64(const std::string& name) -> double` | Get 64-bit float
by column name |
+| `GetString(const std::string& name) -> std::string_view`| Get string by
column name |
+| `GetBytes(const std::string& name) -> std::pair<const uint8_t*, size_t>` |
Get binary data by column name |
+| `GetDate(const std::string& name) -> Date` | Get date by column
name |
+| `GetTime(const std::string& name) -> Time` | Get time by column
name |
+| `GetTimestamp(const std::string& name) -> Timestamp` | Get timestamp by
column name |
+| `GetDecimalString(const std::string& name) -> std::string` | Get decimal as
string by column name |
## `ArrowRecordBatch`
@@ -496,25 +569,15 @@ inline const char* ChangeTypeShortString(ChangeType ct) {
| `TimestampLtz` | Timestamp with timezone |
| `Decimal` | Decimal |
-### `DatumType`
-
-| Value | C++ Type | Description |
-|-----------------|------------------------|---------------------------------|
-| `Null` | -- | Null value |
-| `Bool` | `bool` | Boolean |
-| `Int32` | `int32_t` | 32-bit integer |
-| `Int64` | `int64_t` | 64-bit integer |
-| `Float32` | `float` | 32-bit float |
-| `Float64` | `double` | 64-bit float |
-| `String` | `std::string` | String |
-| `Bytes` | `std::vector<uint8_t>` | Binary data |
-| `DecimalI64` | `int64_t` | Decimal (64-bit internal) |
-| `DecimalI128` | `__int128` | Decimal (128-bit internal) |
-| `DecimalString` | `std::string` | Decimal (string representation) |
-| `Date` | `Date` | Date |
-| `Time` | `Time` | Time |
-| `TimestampNtz` | `Timestamp` | Timestamp without timezone |
-| `TimestampLtz` | `Timestamp` | Timestamp with timezone |
+### `ChangeType`
+
+| Value | Description |
+|----------------|---------------------------------------------|
+| `AppendOnly` | Append-only record (log tables) |
+| `Insert` | Inserted row (PK tables) |
+| `UpdateBefore` | Row value before an update (PK tables) |
+| `UpdateAfter` | Row value after an update (PK tables) |
+| `Delete` | Deleted row (PK tables) |
### `OffsetSpec`
diff --git a/website/docs/user-guide/cpp/data-types.md
b/website/docs/user-guide/cpp/data-types.md
index fb01ac2..18121d3 100644
--- a/website/docs/user-guide/cpp/data-types.md
+++ b/website/docs/user-guide/cpp/data-types.md
@@ -24,6 +24,8 @@ sidebar_position: 3
## GenericRow Setters
+`SetInt32` is used for `TinyInt`, `SmallInt`, and `Int` columns. For `TinyInt`
and `SmallInt`, the value is validated at write time — an error is returned if
it overflows the column's range (e.g., \[-128, 127\] for `TinyInt`, \[-32768,
32767\] for `SmallInt`).
+
```cpp
fluss::GenericRow row;
row.SetNull(0);
@@ -52,46 +54,90 @@ row.Set("created_at",
fluss::Timestamp::FromMillis(1700000000000));
row.Set("nickname", nullptr); // set to null
```
-## GenericRow Getters
+## Reading Field Values
+
+Field values are read through `RowView` (from scan results) and `LookupResult`
(from lookups), not through `GenericRow`. Both provide the same getter
interface with zero-copy access to string and bytes data.
+
+:::warning Lifetime
+`RowView` borrows from `ScanRecords`. It must not outlive the `ScanRecords`
that produced it (similar to `std::string_view` borrowing from `std::string`).
+:::
```cpp
-std::string name = result_row.GetString(1);
-float score = result_row.GetFloat32(3);
-std::string balance = result_row.DecimalToString(4);
-fluss::Date date = result_row.GetDate(5);
-fluss::Time time = result_row.GetTime(6);
-fluss::Timestamp ts = result_row.GetTimestamp(7);
+// DON'T — string_view dangles after ScanRecords is destroyed:
+std::string_view dangling;
+{
+ fluss::ScanRecords records;
+ scanner.Poll(5000, records);
+ dangling = records[0].row.GetString(0); // points into ScanRecords memory
+}
+// dangling is undefined behavior here — ScanRecords is gone!
+
+// DO — use values within ScanRecords lifetime, or copy when you need
ownership:
+fluss::ScanRecords records;
+scanner.Poll(5000, records);
+for (const auto& rec : records) {
+ auto name = rec.row.GetString(0); // zero-copy string_view
+ auto owned = std::string(rec.row.GetString(0)); // explicit copy when
needed
+ process(owned);
+}
+```
+
+### From Scan Results (RowView)
+
+```cpp
+for (const auto& rec : records) {
+ auto name = rec.row.GetString(1); // zero-copy string_view
+ float score = rec.row.GetFloat32(3);
+ auto balance = rec.row.GetDecimalString(4); // std::string (already owned)
+ fluss::Date date = rec.row.GetDate(5);
+ fluss::Time time = rec.row.GetTime(6);
+ fluss::Timestamp ts = rec.row.GetTimestamp(7);
+}
+```
+
+### From Lookup Results (LookupResult)
+
+```cpp
+fluss::LookupResult result;
+lookuper.Lookup(pk_row, result);
+if (result.Found()) {
+ auto name = result.GetString(1); // zero-copy string_view
+ int64_t age = result.GetInt64(2);
+}
```
-## DatumType Enum
-
-| DatumType | C++ Type | Getter |
-|-----------------|------------------------|------------------------|
-| `Null` | -- | `IsNull(idx)` |
-| `Bool` | `bool` | `GetBool(idx)` |
-| `Int32` | `int32_t` | `GetInt32(idx)` |
-| `Int64` | `int64_t` | `GetInt64(idx)` |
-| `Float32` | `float` | `GetFloat32(idx)` |
-| `Float64` | `double` | `GetFloat64(idx)` |
-| `String` | `std::string` | `GetString(idx)` |
-| `Bytes` | `std::vector<uint8_t>` | `GetBytes(idx)` |
-| `Date` | `Date` | `GetDate(idx)` |
-| `Time` | `Time` | `GetTime(idx)` |
-| `TimestampNtz` | `Timestamp` | `GetTimestamp(idx)` |
-| `TimestampLtz` | `Timestamp` | `GetTimestamp(idx)` |
-| `DecimalString` | `std::string` | `DecimalToString(idx)` |
+## TypeId Enum
+
+`TinyInt` and `SmallInt` values are widened to `int32_t` on read.
+
+| TypeId | C++ Type | Getter
|
+|-----------------|---------------------------------------------|---------------------------|
+| `Boolean` | `bool` |
`GetBool(idx)` |
+| `TinyInt` | `int32_t` |
`GetInt32(idx)` |
+| `SmallInt` | `int32_t` |
`GetInt32(idx)` |
+| `Int` | `int32_t` |
`GetInt32(idx)` |
+| `BigInt` | `int64_t` |
`GetInt64(idx)` |
+| `Float` | `float` |
`GetFloat32(idx)` |
+| `Double` | `double` |
`GetFloat64(idx)` |
+| `String` | `std::string_view` |
`GetString(idx)` |
+| `Bytes` | `std::pair<const uint8_t*, size_t>` |
`GetBytes(idx)` |
+| `Date` | `Date` |
`GetDate(idx)` |
+| `Time` | `Time` |
`GetTime(idx)` |
+| `Timestamp` | `Timestamp` |
`GetTimestamp(idx)` |
+| `TimestampLtz` | `Timestamp` |
`GetTimestamp(idx)` |
+| `Decimal` | `std::string` |
`GetDecimalString(idx)` |
## Type Checking
```cpp
-if (rec.row.GetType(0) == fluss::DatumType::Int32) {
+if (rec.row.GetType(0) == fluss::TypeId::Int) {
int32_t value = rec.row.GetInt32(0);
}
if (rec.row.IsNull(1)) {
// field is null
}
if (rec.row.IsDecimal(2)) {
- std::string decimal_str = rec.row.DecimalToString(2);
+ std::string decimal_str = rec.row.GetDecimalString(2);
}
```
diff --git a/website/docs/user-guide/cpp/example/partitioned-tables.md
b/website/docs/user-guide/cpp/example/partitioned-tables.md
index 371ee3e..17c1c20 100644
--- a/website/docs/user-guide/cpp/example/partitioned-tables.md
+++ b/website/docs/user-guide/cpp/example/partitioned-tables.md
@@ -171,10 +171,9 @@ pk.Set("user_id", 1001);
pk.Set("region", "APAC");
pk.Set("zone", static_cast<int64_t>(1));
-bool found = false;
-fluss::GenericRow result;
-lookuper.Lookup(pk, found, result);
-if (found) {
+fluss::LookupResult result;
+lookuper.Lookup(pk, result);
+if (result.Found()) {
std::cout << "score=" << result.GetInt64(3) << std::endl;
}
```
diff --git a/website/docs/user-guide/cpp/example/primary-key-tables.md
b/website/docs/user-guide/cpp/example/primary-key-tables.md
index 7aa87e3..f26b547 100644
--- a/website/docs/user-guide/cpp/example/primary-key-tables.md
+++ b/website/docs/user-guide/cpp/example/primary-key-tables.md
@@ -120,13 +120,12 @@ table.NewLookup().CreateLookuper(lookuper);
auto pk_row = table.NewRow();
pk_row.Set("id", 1);
-bool found = false;
-fluss::GenericRow result_row;
-lookuper.Lookup(pk_row, found, result_row);
+fluss::LookupResult result;
+lookuper.Lookup(pk_row, result);
-if (found) {
- std::cout << "Found: name=" << result_row.GetString(1)
- << ", age=" << result_row.GetInt64(2) << std::endl;
+if (result.Found()) {
+ std::cout << "Found: name=" << result.GetString(1)
+ << ", age=" << result.GetInt64(2) << std::endl;
} else {
std::cout << "Not found" << std::endl;
}