Skip to content

Commit 46cf4f5

Browse files
committed
review, start docs
1 parent de2512a commit 46cf4f5

13 files changed

Lines changed: 734 additions & 1063 deletions

File tree

.clang-format

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ SortIncludes: false
77
TabWidth: 4
88
IndentWidth: 4
99
ColumnLimit: 110
10-
AllowShortFunctionsOnASingleLine: false
10+
AllowShortFunctionsOnASingleLine: true
1111
---
1212
AccessModifierOffset: -4
1313
AlignAfterOpenBracket: Align
@@ -25,7 +25,7 @@ DerivePointerAlignment: false
2525
IncludeBlocks: Regroup
2626
InsertBraces: true
2727
Language: Cpp
28-
NamespaceIndentation: Inner
28+
NamespaceIndentation: None
2929
PointerAlignment: Right
3030
SpaceBeforeCpp11BracedList: true
3131
SpaceBeforeCtorInitializerColon: true
File renamed without changes.

docs/api/cpp/dtypes.rst

Lines changed: 0 additions & 17 deletions
This file was deleted.

docs/api/cpp/expr.rst

Lines changed: 0 additions & 9 deletions
This file was deleted.

docs/api/cpp/file.rst

Lines changed: 0 additions & 17 deletions
This file was deleted.

docs/api/cpp/index.rst

Lines changed: 193 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,224 @@
11
C++ API
22
=======
33

4-
The Vortex C++ API provides an idiomatic C++ wrapper around the Vortex C FFI, built using
5-
`cxx <https://cxx.rs/>`_. It currently supports reading and writing Vortex files and integrates
6-
with the Arrow C Data Interface via `nanoarrow <https://arrow.apache.org/nanoarrow/>`_.
7-
8-
In the future we will expand the C++ API to cover Vortex's plugin and extension points. Please
9-
reach out if you are interested in extending Vortex from C++ so we can prioritize these features.
4+
Vortex C++ API allows you to read and write `.vortex` files directly or via
5+
Arrow compatibility layer `nanoarrow <https://arrow.apache.org/nanoarrow/>`_.
6+
The only dependency apart from nanoarrow are Vortex C bindings.
107

118
.. note::
12-
Both the C++ API and this documentation are a work in progress. The API surface may change
13-
significantly. Please reach out if you are interested in using Vortex from C++ so we can
14-
prioritize stabilization.
15-
9+
C++ API is a work in progress. Please reach out to us if you are interested
10+
in using Vortex from C++ or you want a feature not covered yet e.g.
11+
extension DataType support.
1612

1713
Installation
1814
------------
1915

20-
The C++ bindings are built using CMake. Requirements:
16+
We don't provide prebuilt library files (yet) so you will need to build Vortex
17+
from source, and for that you will need:
18+
19+
- C++20,
20+
- Rust toolchain,
21+
- and CMake 3.10.
22+
23+
```
24+
git clone --depth 1 https://github.com/vortex-data/vortex
25+
cd vortex
26+
cargo build --release -p vortex-ffi
27+
cd vortex-cxx
28+
mkdir build
29+
cmake -Bbuild
30+
cmake --build build -j
31+
```
32+
33+
This will produce a shared and a static library in `vortex/target/release/`
34+
which you can use directly or via
35+
36+
```
37+
# static library
38+
target_link_libraries(target PRIVATE vortex_cxx nanoarrow)
39+
# shared library
40+
target_link_libraries(target PRIVATE vortex_cxx_shared nanoarrow_shared)
41+
```
42+
43+
Have a look at `examples <https://github.com/vortex-data/vortex/tree/develop/vortex-cxx/examples>`_
44+
directory as well.
45+
46+
Reading files
47+
-------------
2148

22-
* CMake 3.22 or higher
23-
* C++20 compatible compiler
24-
* Rust toolchain (for building the underlying Rust library)
49+
TODO provide sample files.
2550

26-
.. code-block:: bash
51+
Let's start with an example. Assuming you have Vortex files `people0`, `people1`,
52+
and `me` in a local folder, each containing two U64 columns "age" and "height",
53+
this is how you print all ages for specific heights:
2754

28-
cd vortex-cxx
29-
mkdir build && cd build
30-
cmake ..
31-
make -j$(nproc)
55+
.. code-block:: cpp
3256
57+
using namespace vortex;
58+
using namespace vortex::expr;
59+
using namespace vortex::expr::ops;
60+
61+
const Session session;
62+
const DataSource ds = DataSource::open(session, {"people*.vortex", "me.vortex"});
63+
Scan scan = ds.scan({
64+
.projection = col("age"),
65+
.filter = col("height") >= lit<uint64_t>(180)
66+
});
67+
68+
for (Partition& partition : scan.partitions()) {
69+
for (Array& array : partition.batches()) {
70+
PrimitiveView<uint64_t> age_view = batch.field("age").values<uint64_t>(session);
71+
std::span<const uint64_t> age_values = age_view.values();
72+
for (uint64_t value: age_values) {
73+
std::cout << value << " ";
74+
}
75+
}
76+
}
3377
34-
Compatibility
35-
-------------
78+
DataSource and Scan
79+
^^^^^^^^^^^^^^^^^^^
80+
81+
First, you need to create a Vortex session which holds stuff like object store
82+
credentials and does extension bookkeeping (we'll get back to it later). Then
83+
you need to create a DataSource, which is a view over multiple files which may
84+
also be remote. You can specify globs for every item as it's shown.
85+
86+
Once you have a DataSource, you can create Scans out of it. A Scan is a single
87+
traversal of a DataSource which projects columns and filters on them. Our Scan
88+
is consumed by following calls so it needs to be non-`const`. ScanOptions which
89+
are passed to Scan are a simple C++ aggregate so you can initialize any fields
90+
you want or avoid them altogether (`auto scan = ds.scan()`). Options have
91+
reasonable defaults: empty projection returns all fields, empty filter doesn't
92+
filter rows, and so on.
93+
94+
Expressions
95+
^^^^^^^^^^^
96+
97+
We want to return only the "age" column so we pass it a `col` Expression - it
98+
gets the root struct of the file (a `DataTypeVariant::Struct`) and then
99+
extracts the column we want by name. A filter works in a similar manner - we
100+
return an Expression which returns false for some "height" values, and the scan
101+
filters them out.
102+
103+
Two additional things to look in `.filter`: first, we allow overloading
104+
Expression operators which produce Expressions themselves a-la Eigen. This is
105+
opt-in via `using namespace vortex::expr::ops`. If you prefer, you can use
106+
`eq(col("height"), lit<uint64_t>(180))` instead. Second thing is that we
107+
explicitly pass the type to the `lit` expression, which creates a literal
108+
constant. We don't do any type coersion in Expression enging, so if you were to
109+
write `lit(180)`, C++ would likely deduce the type to `uint8_t` and fail in
110+
runtime.
111+
112+
Once we're done with the scan, we need to consume the data it provides. If
113+
you want, you can get the data in Arrow format, but now we want our own
114+
Partitions and Arrays.
115+
116+
Partitions and Arrays
117+
^^^^^^^^^^^^^^^^^^^^^
118+
119+
A Partition is an independent unit of work. Assuming your Vortex file
120+
processing will likely be multithreaded, each thread can pull Partitions from
121+
a Scan and handle them in parallel. For simplicity we don't cover it here, but
122+
look for the next sections! Each Partition produces Arrays which are batches of
123+
rows and columns. As we've requested "age", each Array will have one named
124+
column "age".
125+
126+
.. note::
127+
Arrays, DataTypes, and Expressions are reference-counted so copying them is
128+
cheap
36129

37-
The C++ bindings are supported on the following architectures:
130+
Once we have an Array, we can get access to its values. First, we need to
131+
extract "age" field. Then we want to get access to the raw bytes. However,
132+
Array likely references compressed data, so now we need to learn about
133+
canonicalization.
38134

39-
* x86_64 Linux
40-
* ARM64 Linux
41-
* Apple Silicon macOS
135+
Canonicalization
136+
^^^^^^^^^^^^^^^^
42137

43-
They support any Linux distribution with a GLIBC version >= 2.31. This includes
138+
Vortex files hold layers of compressed data. Each layer is a specific encoding
139+
(zstd, FSST, delta) on top of another layer. This is good for performance
140+
because we defer decompression and we can also pass compressed data directly to
141+
other systems. Say, if a reader knows how to deal with bitpacked integers but
142+
not RLE and we have `RLE(Bitpacked(U64))`, we can decompress just RLE and pass
143+
bitpacked array directly to the reader.
44144

45-
* Amazon Linux 2022 or newer
46-
* Ubuntu 20.04 or newer
145+
In this example we want to remove all encodings and uncompress all data fully.
146+
This form is called a canonical Array, and the process is canonicalization.
47147

148+
When we request `.values(session)`, we canonicalize the array and get a
149+
PrimitiveView because we already know the type of the column. As PrimitiveView
150+
holds uncompressed data, we can get raw `uint64_t` numbers by calling
151+
`.values()` on the view.
48152

49-
Usage Example
153+
Writing files
50154
-------------
51155

52-
Here's a basic example of reading a Vortex file into an Arrow array stream:
156+
Now let's write the files to be read by our previous example.
53157

54158
.. code-block:: cpp
159+
// Assume you have imported the namespaces
160+
using dtype::Nullable;
161+
162+
const Session session;
163+
const DataType dtype = dtype::struct({
164+
{"age", dtype::uint8()},
165+
{"height", dtype::uint16(Nullable)},
166+
});
167+
168+
constexpr size_t SAMPLE_ROWS = 100;
169+
std::vector<uint8_t> ages(SAMPLE_ROWS);
170+
std::vector<uint16_t> heights(SAMPLE_ROWS);
171+
for (size_t i = 0; i < SAMPLE_ROWS; ++i) {
172+
ages[i] = static_cast<uint8_t>(i);
173+
heights[i] = static_cast<uint16_t>((i + 1) % 200);
174+
}
55175
56-
#include "vortex/file.hpp"
57-
#include "vortex/scan.hpp"
176+
Array array = make_struct({
177+
{"age", Array::primitive<uint8_t>(ages)},
178+
{"height", Array::primitive<uint16_t>(heights, AllValid)},
179+
});
58180
59-
// Open a Vortex file and scan with a row limit
60-
auto stream = vortex::VortexFile::Open("data.vortex")
61-
.CreateScanBuilder()
62-
.WithLimit(1000)
63-
.IntoStream();
181+
const Validity validity{ValidityType::Array, validity_array};
182+
Array array2 = make_struct({
183+
{"age", Array::primitive<uint8_t>(ages)},
184+
{"height", Array::primitive<uint16_t>(heights, validity)},
185+
});
64186
65-
// Consume the Arrow C Data stream
66-
ArrowArray array;
67-
while (stream.get_next(&stream, &array) == 0 && array.release != nullptr) {
68-
// Process each batch...
69-
}
187+
FileWriter writer = FileWriter::open(session, "people0.vortex", dtype);
188+
writer.push(array);
189+
writer.push(array2);
190+
writer.finish();
70191
192+
DataType
193+
^^^^^^^^
194+
195+
First, you want to create a schema. DataType is a logical representation of
196+
Vortex's type system. "Logical" as opposed to "physical" means DataType doesn't
197+
know what encodings the data uses. As another example, Arrow and Parquet's types
198+
are physical. There's a convenience function `struct_` which creates a
199+
`DataTypeVariant::Struct` with fields passed as a `intializer_list`.
200+
201+
Each DataType has a notion of nullability: whether some items in the row can be
202+
invalid (Vortex uses "null" and "invalid" terms interchangeably). DataTypes are
203+
non-nullable by default, so "age" column is not nullable, but "height" is.
204+
205+
Then we prepare individual columns and use `make_struct` to assemble them into
206+
Array, and here we see Validity.
207+
208+
Validity
209+
^^^^^^^^
210+
211+
DataType's Nullable flag tells us we may theoretically have invalid items in
212+
the Array, but to know this for sure we need Validity. "age" is non-Nullable
213+
so its Validity is NonNullable which means, again, we can't have invalid
214+
elements.
215+
216+
"height" in first Array is AllValid which means in this Array we don't have
217+
invalid items.
71218

72-
API Reference
73-
-------------
74219

75-
.. toctree::
76-
:maxdepth: 2
220+
Multithreaded scans
221+
-------------------
77222

78-
dtypes
79-
scalars
80-
expr
81-
file
82-
scan
223+
Arrow roundtrip
224+
---------------

docs/api/cpp/scalars.rst

Lines changed: 0 additions & 9 deletions
This file was deleted.

docs/api/cpp/scan.rst

Lines changed: 0 additions & 18 deletions
This file was deleted.

vortex-cxx/.clang-format

Lines changed: 0 additions & 6 deletions
This file was deleted.

vortex-cxx/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
# Vortex C++ bindings
2+
3+
For a usage guide, see docs/api/cpp/index.rst.
4+
25
## Requirements
36

47
- CMake 3.10+

0 commit comments

Comments
 (0)