| <!-- |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, |
| software distributed under the License is distributed on an |
| "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| KIND, either express or implied. See the License for the |
| specific language governing permissions and limitations |
| under the License. |
| --> |
| |
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>C++ API - Mosaic</title> |
| <link rel="stylesheet" href="css/style.css"> |
| <script src="js/main.js"></script> |
| </head> |
| <body> |
| <button class="menu-toggle" aria-label="Menu">☰</button> |
| <div class="overlay"></div> |
| |
| <aside class="sidebar"> |
| <div class="sidebar-header"> |
| <h2>Mosaic</h2> |
| <p>Columnar-bucket hybrid format</p> |
| </div> |
| <nav> |
| <ul> |
| <li><a href="index.html">Home</a></li> |
| <li><a href="design.html">Design</a></li> |
| <li><a href="rust-api.html">Rust API</a></li> |
| <li><a href="java-api.html">Java API</a></li> |
| <li><a href="python-api.html">Python API</a></li> |
| <li><a href="cpp-api.html">C++ API</a></li> |
| </ul> |
| </nav> |
| <div class="sidebar-footer"> |
| <button class="theme-toggle">Dark Mode</button> |
| </div> |
| </aside> |
| |
| <main class="main"> |
| <div class="content"> |
| <h1>C++ API</h1> |
| <p class="subtitle">Use Mosaic from C or C++ via the FFI bindings.</p> |
| |
| <h2>Overview</h2> |
| <p> |
| The <code>ffi/</code> crate generates a shared library (<code>libmosaic_ffi</code>) and a |
| C header (<code>mosaic.h</code>) via <a href="https://github.com/mozilla/cbindgen">cbindgen</a>. |
| The C++ header (<code>mosaic.hpp</code>) is a hand-written RAII wrapper on top of the C API. |
| </p> |
| |
| <h2>Building</h2> |
| <pre><code><span class="cmt"># Build the FFI shared library</span> |
| cargo build --release -p mosaic-ffi |
| |
| <span class="cmt"># C header generated at include/mosaic.h</span> |
| <span class="cmt"># C++ RAII wrapper: include/mosaic.hpp (checked in, not generated)</span></code></pre> |
| |
| <h2>Linking</h2> |
| <p>Link against the shared library and include the appropriate header:</p> |
| <pre><code><span class="cmt"># macOS</span> |
| g++ -std=c++17 -I include/ example.cpp \ |
| -L target/release -lmosaic_ffi -o example |
| |
| <span class="cmt"># Linux</span> |
| g++ -std=c++17 -I include/ example.cpp \ |
| -L target/release -lmosaic_ffi -Wl,-rpath,target/release -o example</code></pre> |
| |
| <h2>Writing (C++)</h2> |
| <p> |
| Data is written as Arrow RecordBatches via the |
| <a href="https://arrow.apache.org/docs/format/CDataInterface.html">Arrow C Data Interface</a>. |
| Build your data as Arrow arrays, export via <code>ArrowArray</code> / <code>ArrowSchema</code>, |
| then pass to the writer: |
| </p> |
| <pre><code><span class="kw">#include</span> <span class="str">"mosaic.hpp"</span> |
| <span class="kw">#include</span> <arrow/api.h> |
| <span class="kw">#include</span> <arrow/c/bridge.h> |
| |
| <span class="kw">int</span> <span class="fn">main</span>() { |
| <span class="kw">try</span> { |
| <span class="cmt">// 1. Set up output stream callbacks</span> |
| <span class="kw">auto</span>* fp = std::fopen(<span class="str">"output.mosaic"</span>, <span class="str">"wb"</span>); |
| <span class="kw">auto</span> file = std::shared_ptr<FILE>(fp, [](<span class="ty">FILE</span>* f) { std::fclose(f); }); |
| |
| <span class="ty">mosaic</span>::<span class="ty">OutputFile</span> cbs; |
| cbs.write_fn = [file](<span class="kw">const uint8_t</span>* data, <span class="kw">size_t</span> len) -> <span class="kw">int</span> { |
| <span class="kw">size_t</span> written = std::fwrite(data, <span class="num">1</span>, len, file.get()); |
| <span class="kw">return</span> (written == len) ? <span class="num">0</span> : <span class="num">-1</span>; |
| }; |
| cbs.flush_fn = [file]() -> <span class="kw">int</span> { <span class="kw">return</span> std::fflush(file.get()); }; |
| cbs.get_pos_fn = [file]() -> <span class="kw">int64_t</span> { <span class="kw">return</span> std::ftell(file.get()); }; |
| |
| <span class="cmt">// 2. Build an Arrow RecordBatch and write it</span> |
| arrow::Int32Builder age_builder; |
| arrow::StringBuilder name_builder; |
| arrow::DoubleBuilder score_builder; |
| <span class="kw">for</span> (<span class="kw">int</span> i = <span class="num">0</span>; i < <span class="num">10000</span>; i++) { |
| name_builder.Append(<span class="str">"user_"</span> + std::to_string(i)); |
| age_builder.Append(<span class="num">20</span> + (i % <span class="num">50</span>)); |
| score_builder.Append(i * <span class="num">1.5</span>); |
| } |
| <span class="kw">auto</span> batch = arrow::RecordBatch::Make( |
| arrow::schema({ |
| arrow::field(<span class="str">"name"</span>, arrow::utf8()), |
| arrow::field(<span class="str">"age"</span>, arrow::int32()), |
| arrow::field(<span class="str">"score"</span>, arrow::float64()), |
| }), |
| <span class="num">10000</span>, |
| {name_builder.Finish().ValueOrDie(), |
| age_builder.Finish().ValueOrDie(), |
| score_builder.Finish().ValueOrDie()}); |
| |
| <span class="cmt">// 3. Export via Arrow C Data Interface, create writer, and write</span> |
| ArrowArray ffi_array; |
| ArrowSchema ffi_schema; |
| arrow::ExportRecordBatch(*batch, &ffi_array, &ffi_schema); |
| |
| <span class="ty">mosaic</span>::<span class="ty">WriterOptions</span> opts; |
| opts.compression = <span class="num">1</span>; <span class="cmt">// ZSTD</span> |
| opts.zstd_level = <span class="num">1</span>; |
| opts.num_buckets = <span class="num">2</span>; |
| <span class="ty">mosaic</span>::<span class="ty">Writer</span> writer(std::move(cbs), &ffi_schema, opts); |
| writer.write(&ffi_array, &ffi_schema); |
| |
| <span class="cmt">// 4. Close (also happens on destructor)</span> |
| writer.close(); |
| |
| } <span class="kw">catch</span> (<span class="kw">const</span> <span class="ty">mosaic</span>::<span class="ty">Error</span>& e) { |
| fprintf(stderr, <span class="str">"Error: %s\n"</span>, e.what()); |
| <span class="kw">return</span> <span class="num">1</span>; |
| } |
| <span class="kw">return</span> <span class="num">0</span>; |
| }</code></pre> |
| |
| <h2>C++ API Reference</h2> |
| |
| <h3>Writer Options</h3> |
| <table> |
| <thead> |
| <tr><th>Field</th><th>Type</th><th>Default</th><th>Description</th></tr> |
| </thead> |
| <tbody> |
| <tr><td><code>num_buckets</code></td><td>uint32_t</td><td>0</td><td>Number of buckets (0 = auto)</td></tr> |
| <tr><td><code>compression</code></td><td>uint8_t</td><td>1</td><td>0=none, 1=zstd</td></tr> |
| <tr><td><code>zstd_level</code></td><td>int32_t</td><td>1</td><td>Zstd compression level</td></tr> |
| <tr><td><code>row_group_max_size</code></td><td>uint64_t</td><td>256 MB</td><td>Max row group size</td></tr> |
| <tr><td><code>max_dict_total_bytes</code></td><td>uint32_t</td><td>32 KB</td><td>Max dict size per column</td></tr> |
| <tr><td><code>max_dict_entries</code></td><td>uint32_t</td><td>255</td><td>Max dict entries per column</td></tr> |
| <tr><td><code>stats_columns</code></td><td>const uint32_t*</td><td>NULL</td><td>Column indices to build min/max stats for</td></tr> |
| <tr><td><code>num_stats_columns</code></td><td>uint32_t</td><td>0</td><td>Length of stats_columns array</td></tr> |
| <tr><td><code>page_size_threshold</code></td><td>uint32_t</td><td>32 KB</td><td>Min avg column page size to enable paged mode</td></tr> |
| </tbody> |
| </table> |
| |
| <h3>Writer Methods</h3> |
| <table> |
| <thead> |
| <tr><th>Method</th><th>Return</th><th>Description</th></tr> |
| </thead> |
| <tbody> |
| <tr><td><code>write(&ffi_array, &ffi_schema)</code></td><td><code>void</code></td><td>Write an Arrow RecordBatch via C Data Interface</td></tr> |
| <tr><td><code>estimated_file_size()</code></td><td><code>int64_t</code></td><td>Estimated output file size in bytes (for file rolling)</td></tr> |
| <tr><td><code>close()</code></td><td><code>void</code></td><td>Flush remaining data and write footer</td></tr> |
| <tr><td><code>num_row_groups()</code></td><td><code>uint32_t</code></td><td>Number of row groups written (available after close)</td></tr> |
| <tr><td><code>get_row_group_statistics(rg)</code></td><td><code>vector<ColumnStatistics></code></td><td>Column statistics for a row group (available after close)</td></tr> |
| </tbody> |
| </table> |
| |
| <h2>Reading a File</h2> |
| |
| <h3>1. Open the Reader</h3> |
| <p> |
| Construct a <code>Reader</code> by providing an <code>InputFile</code> |
| with your I/O implementation: |
| </p> |
| <pre><code><span class="cmt">// Example: memory-mapped reader</span> |
| <span class="ty">mosaic</span>::<span class="ty">InputFile</span> input; |
| input.read_at_fn = [data](<span class="kw">uint64_t</span> offset, <span class="kw">uint8_t</span>* buf, <span class="kw">size_t</span> len) -> <span class="kw">int</span> { |
| std::memcpy(buf, data + offset, len); |
| <span class="kw">return</span> <span class="num">0</span>; |
| }; |
| |
| <span class="kw">auto</span> reader = <span class="ty">mosaic</span>::make_reader(std::move(input), file_size);</code></pre> |
| |
| <h3>2. Inspect the Schema</h3> |
| <p> |
| Export the schema via the |
| <a href="https://arrow.apache.org/docs/format/CDataInterface.html">Arrow C Data Interface</a> |
| and import into Arrow C++: |
| </p> |
| <pre><code>ArrowSchema ffi_schema; |
| reader.export_schema(&ffi_schema); |
| <span class="kw">auto</span> schema = arrow::ImportSchema(&ffi_schema).ValueOrDie();</code></pre> |
| |
| <h3>Reader Methods</h3> |
| <table> |
| <thead> |
| <tr><th>Method</th><th>Return</th><th>Description</th></tr> |
| </thead> |
| <tbody> |
| <tr><td><code>num_row_groups()</code></td><td><code>uint32_t</code></td><td>Row group count</td></tr> |
| <tr><td><code>export_schema(&ffi_schema)</code></td><td><code>void</code></td><td>Export schema via Arrow C Data Interface</td></tr> |
| <tr><td><code>read_row_group(rg, &array, &schema)</code></td><td><code>void</code></td><td>Read all columns of a row group</td></tr> |
| <tr><td><code>read_row_group(rg, cols, n, &array, &schema)</code></td><td><code>void</code></td><td>Read projected columns of a row group</td></tr> |
| <tr><td><code>get_row_group_statistics(rg)</code></td><td><code>vector<ColumnStatistics></code></td><td>Column statistics for a row group</td></tr> |
| </tbody> |
| </table> |
| |
| <h3>3. Read Row Groups as Arrow RecordBatch</h3> |
| <p> |
| Each row group is read directly via <code>read_row_group()</code>, which exports |
| via the <a href="https://arrow.apache.org/docs/format/CDataInterface.html">Arrow C Data Interface</a> |
| for zero-copy import into Arrow C++: |
| </p> |
| <pre><code><span class="kw">#include</span> <arrow/c/bridge.h> |
| <span class="kw">#include</span> <span class="str">"mosaic.hpp"</span> |
| |
| <span class="kw">for</span> (<span class="kw">uint32_t</span> rg = <span class="num">0</span>; rg < reader.num_row_groups(); rg++) { |
| ArrowArray ffi_array; |
| ArrowSchema ffi_schema; |
| reader.read_row_group(rg, &ffi_array, &ffi_schema); |
| |
| <span class="cmt">// Import into Arrow C++</span> |
| <span class="kw">auto</span> batch = arrow::ImportRecordBatch(&ffi_array, &ffi_schema).ValueOrDie(); |
| printf(<span class="str">"row group %u: %lld rows, %d cols\n"</span>, |
| rg, batch->num_rows(), batch->num_columns()); |
| |
| <span class="cmt">// Access columns via Arrow C++ API</span> |
| <span class="kw">auto</span> ages = std::static_pointer_cast<arrow::Int32Array>( |
| batch->GetColumnByName(<span class="str">"age"</span>)); |
| <span class="kw">for</span> (<span class="kw">int64_t</span> i = <span class="num">0</span>; i < ages->length(); i++) { |
| <span class="kw">if</span> (!ages->IsNull(i)) { |
| printf(<span class="str">"age=%d\n"</span>, ages->Value(i)); |
| } |
| } |
| }</code></pre> |
| |
| <h3>Projection Pushdown</h3> |
| <p> |
| Use <code>read_row_group</code> with column indices to read only specific columns. |
| Only the buckets containing the projected columns are decompressed, reducing |
| I/O and memory for wide tables. The returned batch contains only the projected columns. |
| </p> |
| <pre><code><span class="kw">uint32_t</span> projected[] = { <span class="num">0</span>, <span class="num">2</span> }; |
| ArrowArray ffi_array; |
| ArrowSchema ffi_schema; |
| reader.read_row_group(<span class="num">0</span>, projected, <span class="num">2</span>, &ffi_array, &ffi_schema); |
| <span class="kw">auto</span> batch = arrow::ImportRecordBatch(&ffi_array, &ffi_schema).ValueOrDie(); |
| <span class="cmt">// batch contains only the projected columns</span></code></pre> |
| |
| <h3>Column Statistics (Filter Pushdown)</h3> |
| <p> |
| When stats columns are configured during writing, statistics are available both |
| from the writer (after close) and from the reader: |
| </p> |
| <pre><code><span class="cmt">// Writing with stats (arrow_schema is an ArrowSchema* from C Data Interface)</span> |
| <span class="kw">uint32_t</span> stats_cols[] = { <span class="num">0</span>, <span class="num">2</span> }; |
| <span class="ty">mosaic</span>::<span class="ty">WriterOptions</span> opts; |
| opts.compression = <span class="num">1</span>; |
| opts.stats_columns = stats_cols; |
| opts.num_stats_columns = <span class="num">2</span>; |
| <span class="ty">mosaic</span>::<span class="ty">Writer</span> writer(std::move(cbs), arrow_schema, opts);</code></pre> |
| <pre><code><span class="cmt">// Get stats directly from the writer after close</span> |
| writer.close(); |
| <span class="kw">for</span> (<span class="kw">uint32_t</span> rg = <span class="num">0</span>; rg < writer.num_row_groups(); rg++) { |
| <span class="kw">auto</span> stats = writer.get_row_group_statistics(rg); |
| <span class="kw">for</span> (<span class="kw">const auto</span>& stat : stats) { |
| <span class="kw">uint32_t</span> col_idx = stat.column_index; |
| <span class="kw">uint64_t</span> null_count = stat.null_count; |
| <span class="kw">if</span> (stat.has_min_max()) { |
| <span class="cmt">// stat.min_value / stat.max_value are std::vector<uint8_t></span> |
| } |
| } |
| }</code></pre> |
| <pre><code><span class="cmt">// Or read stats from the reader</span> |
| <span class="kw">for</span> (<span class="kw">uint32_t</span> rg = <span class="num">0</span>; rg < reader.num_row_groups(); rg++) { |
| <span class="kw">auto</span> stats = reader.get_row_group_statistics(rg); |
| <span class="kw">for</span> (<span class="kw">const auto</span>& stat : stats) { |
| <span class="kw">uint32_t</span> col_idx = stat.column_index; |
| <span class="kw">uint64_t</span> null_count = stat.null_count; |
| <span class="kw">if</span> (stat.has_min_max()) { |
| <span class="cmt">// stat.min_value / stat.max_value are std::vector<uint8_t></span> |
| } |
| } |
| }</code></pre> |
| |
| <h4>ColumnStatistics</h4> |
| <table> |
| <thead> |
| <tr><th>Field</th><th>Type</th><th>Description</th></tr> |
| </thead> |
| <tbody> |
| <tr><td><code>column_index</code></td><td><code>uint32_t</code></td><td>Column index in the schema</td></tr> |
| <tr><td><code>null_count</code></td><td><code>uint64_t</code></td><td>Number of null values</td></tr> |
| <tr><td><code>has_min_max()</code></td><td><code>bool</code></td><td>Whether min/max are available</td></tr> |
| <tr><td><code>min_value</code></td><td><code>vector<uint8_t></code></td><td>Min value as big-endian bytes (empty if all-null)</td></tr> |
| <tr><td><code>max_value</code></td><td><code>vector<uint8_t></code></td><td>Max value as big-endian bytes (empty if all-null)</td></tr> |
| </tbody> |
| </table> |
| <p> |
| Min/max values are returned as big-endian byte arrays matching the type's wire format |
| (e.g., 4 bytes for INTEGER, 8 bytes for BIGINT/DOUBLE, raw UTF-8 bytes for STRING). |
| </p> |
| |
| <h2>Complete Example</h2> |
| <pre><code><span class="kw">#include</span> <span class="str">"mosaic.hpp"</span> |
| <span class="kw">#include</span> <arrow/api.h> |
| <span class="kw">#include</span> <arrow/c/bridge.h> |
| <span class="kw">#include</span> <cstdio> |
| <span class="kw">#include</span> <cstring> |
| <span class="kw">#include</span> <vector> |
| |
| <span class="kw">int</span> <span class="fn">main</span>() { |
| <span class="kw">try</span> { |
| <span class="cmt">// 1. Write to a buffer</span> |
| std::vector<<span class="kw">uint8_t</span>> buf; |
| { |
| <span class="ty">mosaic</span>::<span class="ty">OutputFile</span> w_cbs; |
| w_cbs.write_fn = [&buf](<span class="kw">const uint8_t</span>* data, <span class="kw">size_t</span> len) -> <span class="kw">int</span> { |
| buf.insert(buf.end(), data, data + len); |
| <span class="kw">return</span> <span class="num">0</span>; |
| }; |
| w_cbs.flush_fn = []() -> <span class="kw">int</span> { <span class="kw">return</span> <span class="num">0</span>; }; |
| w_cbs.get_pos_fn = [&buf]() -> <span class="kw">int64_t</span> { |
| <span class="kw">return</span> <span class="kw">static_cast</span><<span class="kw">int64_t</span>>(buf.size()); |
| }; |
| |
| <span class="cmt">// Build an Arrow RecordBatch</span> |
| arrow::Int32Builder id_builder; |
| arrow::StringBuilder name_builder; |
| arrow::DoubleBuilder score_builder; |
| <span class="kw">for</span> (<span class="kw">int</span> i = <span class="num">0</span>; i < <span class="num">100</span>; i++) { |
| id_builder.Append(i); |
| name_builder.Append(<span class="str">"user_"</span> + std::to_string(i)); |
| score_builder.Append(i * <span class="num">1.5</span>); |
| } |
| <span class="kw">auto</span> batch = arrow::RecordBatch::Make( |
| arrow::schema({ |
| arrow::field(<span class="str">"id"</span>, arrow::int32(), <span class="kw">false</span>), |
| arrow::field(<span class="str">"name"</span>, arrow::utf8()), |
| arrow::field(<span class="str">"score"</span>, arrow::float64()), |
| }), |
| <span class="num">100</span>, |
| {id_builder.Finish().ValueOrDie(), |
| name_builder.Finish().ValueOrDie(), |
| score_builder.Finish().ValueOrDie()}); |
| |
| <span class="cmt">// Export via Arrow C Data Interface and create writer</span> |
| ArrowArray ffi_array; |
| ArrowSchema ffi_schema; |
| arrow::ExportRecordBatch(*batch, &ffi_array, &ffi_schema); |
| |
| <span class="ty">mosaic</span>::<span class="ty">WriterOptions</span> w_opts; |
| w_opts.num_buckets = <span class="num">2</span>; |
| <span class="ty">mosaic</span>::<span class="ty">Writer</span> writer(std::move(w_cbs), &ffi_schema, w_opts); |
| writer.write(&ffi_array, &ffi_schema); |
| writer.close(); |
| } |
| |
| <span class="cmt">// 2. Read from the buffer</span> |
| <span class="ty">mosaic</span>::<span class="ty">InputFile</span> input; |
| input.read_at_fn = [&buf](<span class="kw">uint64_t</span> offset, <span class="kw">uint8_t</span>* dst, <span class="kw">size_t</span> len) -> <span class="kw">int</span> { |
| std::memcpy(dst, buf.data() + offset, len); |
| <span class="kw">return</span> <span class="num">0</span>; |
| }; |
| |
| <span class="kw">auto</span> reader = <span class="ty">mosaic</span>::make_reader(std::move(input), buf.size()); |
| |
| ArrowArray ffi_array; |
| ArrowSchema ffi_schema; |
| reader.read_row_group(<span class="num">0</span>, &ffi_array, &ffi_schema); |
| <span class="kw">auto</span> result = arrow::ImportRecordBatch(&ffi_array, &ffi_schema).ValueOrDie(); |
| |
| <span class="kw">auto</span> ids = std::static_pointer_cast<arrow::Int32Array>(result->GetColumnByName(<span class="str">"id"</span>)); |
| <span class="kw">auto</span> names = std::static_pointer_cast<arrow::StringArray>(result->GetColumnByName(<span class="str">"name"</span>)); |
| <span class="kw">auto</span> scores = std::static_pointer_cast<arrow::DoubleArray>(result->GetColumnByName(<span class="str">"score"</span>)); |
| |
| } <span class="kw">catch</span> (<span class="kw">const</span> <span class="ty">mosaic</span>::<span class="ty">Error</span>& e) { |
| fprintf(stderr, <span class="str">"Error: %s\n"</span>, e.what()); |
| <span class="kw">return</span> <span class="num">1</span>; |
| } |
| <span class="kw">return</span> <span class="num">0</span>; |
| }</code></pre> |
| |
| <div class="warning"> |
| <strong>Column ordering</strong> |
| The reader preserves the original schema column order. |
| Use <code>export_schema()</code> to inspect the schema via Arrow C Data Interface. |
| </div> |
| |
| <div class="tip"> |
| <strong>Resource management</strong> |
| All C++ wrapper classes (<code>Writer</code>, |
| <code>Reader</code>) |
| are move-only RAII types. Resources are freed automatically when objects go out of scope. |
| </div> |
| </div> |
| </main> |
| </body> |
| </html> |