Skip to content

Commit aae6eba

Browse files
authored
fix: FFI example fixed up, hooked into CI (#3684)
1 parent b1af388 commit aae6eba

File tree

5 files changed

+161
-17
lines changed

5 files changed

+161
-17
lines changed

.github/workflows/ci.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,15 @@ jobs:
421421
with:
422422
command: check ${{ matrix.checks }}
423423

424+
ffi-test:
425+
name: "FFI example test"
426+
runs-on: ubuntu-latest
427+
steps:
428+
- uses: actions/checkout@v4
429+
- uses: ./.github/actions/setup-rust
430+
- run: cargo build -p vortex-ffi
431+
- run: cargo run -p vortex-ffi --example hello_vortex
432+
424433
wasm-integration:
425434
name: "wasm-integration"
426435
runs-on: ubuntu-latest

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vortex-ffi/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ rust-version = { workspace = true }
1717
categories = { workspace = true }
1818

1919
[dependencies]
20-
futures = { workspace = true }
2120
itertools = { workspace = true }
2221
log = { workspace = true }
2322
mimalloc = { workspace = true, optional = true }
@@ -31,6 +30,9 @@ tokio-stream = { workspace = true }
3130
url = { workspace = true, features = [] }
3231
vortex = { workspace = true, features = ["object_store", "proto"] }
3332

33+
[dev-dependencies]
34+
tempfile = { workspace = true }
35+
3436
[features]
3537
default = ["mimalloc"]
3638
mimalloc = ["dep:mimalloc"]

vortex-ffi/examples/hello-vortex.c

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,40 +3,65 @@
33
#include "vortex.h"
44

55
int main(int argc, char *argv[]) {
6-
// enable logging
7-
vortex_init_logging(LOG_LEVEL_INFO);
8-
96
if (argc < 2) {
107
printf("Usage: %s <VORTEX_FILE_URI>\n", argv[0]);
118
return 1;
129
}
1310

11+
vx_error *error = NULL;
12+
vx_session *session = vx_session_new();
13+
if (session == NULL) {
14+
fprintf(stderr, "vx_session_new return NULL\n");
15+
return -1;
16+
}
17+
1418
// Open the file
1519
char *path = argv[1];
16-
FileOpenOptions open_opts = {
20+
21+
printf("Opening file %s\n", path);
22+
23+
vx_file_open_options open_opts = {
1724
.uri = path,
1825
.property_keys = NULL,
1926
.property_vals = NULL,
2027
.property_len = 0,
2128
};
2229
printf("Scanning file: %s\n", path);
23-
File *file = File_open(&open_opts);
30+
31+
const vx_file *file = vx_file_open_reader(&open_opts, session, &error);
32+
if (error != NULL) {
33+
fprintf(stderr, "error opening file\n");
34+
35+
return -1;
36+
}
2437

2538
// Start scanning, read new rows.
26-
ArrayStream *stream = File_scan(file, NULL);
39+
vx_array_iterator *scan = vx_file_scan(file, NULL, &error);
40+
2741
int chunk = 0;
28-
while (FFIArrayStream_next(stream)) {
29-
Array *array = FFIArrayStream_current(stream);
30-
int len = FFIArray_len(array);
31-
printf("Chunk %d: %d\n", chunk++, len);
32-
FFIArray_free(array);
42+
const vx_array *batch = vx_array_iterator_next(scan, &error);
43+
while (batch != NULL) {
44+
size_t len = vx_array_len(batch);
45+
printf("chunk %d has length %ld\n", chunk++, len);
46+
47+
// free the batch
48+
vx_array_free(batch);
49+
// grab the next batch.
50+
batch = vx_array_iterator_next(scan, &error);
3351
}
3452

35-
printf("Scanning complete\n");
53+
vx_array_iterator_free(scan);
54+
vx_file_free(file);
55+
56+
if (error != NULL) {
57+
fprintf(stderr, "failed in scan operation\n");
58+
vx_session_free(session);
59+
vx_error_free(error);
60+
return -1;
61+
}
3662

37-
// Cleanup resources.
38-
FFIArrayStream_free(stream);
39-
File_free(file);
63+
printf("Scanning completed successfully\n");
4064

65+
vx_session_free(session);
4166
return 0;
4267
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
#![allow(clippy::unwrap_used, clippy::expect_used)]
2+
//! This example shows usage of the Vortex C FFI to read a Vortex file written by a Rust client.
3+
//!
4+
//! You can invoke this example from a checkout by running
5+
//!
6+
//! ```ignore
7+
//!cargo run -p vortex-ffi --example hello_vortex
8+
//! ```
9+
10+
use std::env;
11+
use std::path::{Path, PathBuf};
12+
use std::process::Command;
13+
use std::sync::LazyLock;
14+
15+
use tempfile::NamedTempFile;
16+
use tokio::fs::File as TokioFile;
17+
use tokio::runtime::Runtime;
18+
use vortex::arrays::{ChunkedArray, StructArray};
19+
use vortex::buffer::Buffer;
20+
use vortex::error::VortexResult;
21+
use vortex::file::VortexWriteOptions;
22+
use vortex::{Array, ArrayRef, IntoArray};
23+
24+
static RUNTIME: LazyLock<Runtime> = LazyLock::new(|| Runtime::new().unwrap());
25+
26+
const BIN_NAME: &str = "hello_vortex";
27+
28+
pub fn main() -> VortexResult<()> {
29+
let bin_path = PathBuf::new()
30+
.join(env!("CARGO_MANIFEST_DIR"))
31+
.join("examples")
32+
.join(BIN_NAME);
33+
34+
let exit = Command::new("cc")
35+
.arg("-o")
36+
.arg(&bin_path)
37+
.arg("-I")
38+
.arg(
39+
PathBuf::new()
40+
.join(env!("CARGO_MANIFEST_DIR"))
41+
.join("cinclude")
42+
.display()
43+
.to_string(),
44+
)
45+
.arg(
46+
PathBuf::new()
47+
.join(env!("CARGO_MANIFEST_DIR"))
48+
.join("examples")
49+
.join("hello-vortex.c")
50+
.display()
51+
.to_string(),
52+
)
53+
.arg("-L")
54+
.arg(
55+
PathBuf::new()
56+
.join(env!("CARGO_MANIFEST_DIR"))
57+
.join("..")
58+
.join("target")
59+
.join("debug"),
60+
)
61+
.arg("-l")
62+
.arg("vortex_ffi")
63+
.status()?;
64+
65+
assert!(exit.success());
66+
67+
let file = NamedTempFile::with_suffix(".vortex")?.into_temp_path();
68+
69+
// Write the test data
70+
RUNTIME.block_on(write_vortex_file(&file))?;
71+
72+
let uri = format!("file://{}", file.display());
73+
74+
// Invoke the binary we just created
75+
let exit = Command::new(&bin_path).arg(uri).status()?;
76+
assert!(exit.success());
77+
78+
Ok(())
79+
}
80+
81+
async fn write_vortex_file(path: impl AsRef<Path>) -> VortexResult<()> {
82+
let file = TokioFile::create(path).await?;
83+
84+
let chunk1 = chunk((0..1000).collect(), (0..1000).map(|x| x as f32).collect());
85+
let chunk2 = chunk(
86+
(1000..2000).collect(),
87+
(1000..2000).map(|x| x as f32).collect(),
88+
);
89+
let chunk3 = chunk(
90+
(2000..3000).collect(),
91+
(2000..3000).map(|x| x as f32).collect(),
92+
);
93+
let dtype = chunk1.dtype().clone();
94+
95+
let test_data = ChunkedArray::try_new(vec![chunk1, chunk2, chunk3], dtype)?;
96+
97+
VortexWriteOptions::default()
98+
.write(file, test_data.to_array_stream())
99+
.await?;
100+
101+
Ok(())
102+
}
103+
104+
fn chunk(nums: Buffer<i32>, floats: Buffer<f32>) -> ArrayRef {
105+
StructArray::try_from_iter([("nums", nums.into_array()), ("floats", floats.into_array())])
106+
.unwrap()
107+
.into_array()
108+
}

0 commit comments

Comments
 (0)