Skip to content

Commit

Permalink
Add minimum C API
Browse files Browse the repository at this point in the history
Closes #1113

This exports minimum C API to write the following Rust code in C:

    use datafusion::prelude::*;

    #[tokio::main]
    async fn main() -> datafusion::error::Result<()> {
      // register the table
      let mut ctx = ExecutionContext::new();

      // create a plan to run a SQL query
      let df = ctx.sql("SELECT 1").await?;

      // execute and print results
      df.show().await?;
      Ok(())
    }

See datafusion/c/examples/sql.c for C version. You can build and run
datafusion/c/examples/sql.c by the following command lines:

    $ cargo build
    $ cc -o target/debug/sql datafusion/c/examples/sql.c -Idatafusion/c/include -Ltarget/debug -Wl,--rpath=target/debug -ldatafusion_c
    $ target/debug/sql
    +----------+
    | Int64(1) |
    +----------+
    | 1        |
    +----------+

This implementation doesn't export Future like
datafusion-python. Async functions are block_on()-ed in exported
API. But I think that we can export Future in follow-up tasks.

Follow-up tasks:

  * Add support for testing by "cargo test"
  * Add support for building and running examples by "cargo ..."
  * Add support for installing datafusion.h
  • Loading branch information
kou committed May 26, 2022
1 parent 894be67 commit 1b439bb
Show file tree
Hide file tree
Showing 5 changed files with 293 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

[workspace]
members = [
"datafusion/c",
"datafusion/common",
"datafusion/core",
"datafusion/data-access",
Expand Down
46 changes: 46 additions & 0 deletions datafusion/c/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# 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.

[package]
name = "datafusion_c"
description = "DataFusion C API"
version = "8.0.0"
homepage = "https://github.com/apache/arrow-datafusion"
repository = "https://github.com/apache/arrow-datafusion"
readme = "../../README.md"
authors = ["Apache Arrow <dev@arrow.apache.org>"]
license = "Apache-2.0"
keywords = ["arrow", "c"]
include = [
"src/**/*.rs",
"Cargo.toml",
]
edition = "2021"
rust-version = "1.59"

[lib]
name = "datafusion_c"
path = "src/lib.rs"
crate-type = ["cdylib"]

[features]
default = []

[dependencies]
datafusion = { path = "../core", version = "8.0.0" }
libc = "0.2"
tokio = { version = "1.0", features = ["macros", "rt", "rt-multi-thread", "sync", "fs", "parking_lot"] }
45 changes: 45 additions & 0 deletions datafusion/c/examples/sql.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* 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.
*/

#include <datafusion.h>

#include <stdio.h>
#include <stdlib.h>

int
main(void)
{
DFSessionContext *context = df_session_context_new();
DFError *error = NULL;
DFDataFrame *data_frame = df_session_context_sql(context, "SELECT 1;", &error);
if (error) {
printf("failed to run SQL: %s\n", df_error_get_message(error));
df_error_free(error);
df_session_context_free(context);
return EXIT_FAILURE;
}
df_data_frame_show(data_frame, &error);
if (error) {
printf("failed to show data frame: %s\n", df_error_get_message(error));
df_error_free(error);
}
df_data_frame_unref(data_frame);
df_session_context_free(context);
return EXIT_SUCCESS;
}
46 changes: 46 additions & 0 deletions datafusion/c/include/datafusion.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* 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.
*/

#pragma once

#ifdef __cplusplus
extern "C" {
#endif


typedef struct DFError_ DFError;
extern void df_error_free(DFError *error);
extern const char *df_error_get_message(DFError *error);


typedef struct DFDataFrame_ DFDataFrame;
extern void df_data_frame_unref(DFDataFrame *data_frame);
extern void df_data_frame_show(DFDataFrame *data_frame, DFError **error);


typedef struct DFSessionContext_ DFSessionContext;
extern DFSessionContext *df_session_context_new(void);
extern void df_session_context_free(DFSessionContext *ctx);
extern DFDataFrame *df_session_context_sql(DFSessionContext *ctx,
const char *sql,
DFError **error);

#ifdef __cplusplus
}
#endif
155 changes: 155 additions & 0 deletions datafusion/c/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// 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.

use std::boxed::Box;
use std::ffi::CStr;
use std::ffi::CString;
use std::future::Future;
use std::sync::Arc;

use datafusion::dataframe::DataFrame;
use datafusion::execution::context::SessionContext;

#[repr(C)]
pub struct DFError {
code: u32,
message: *mut libc::c_char,
}

impl DFError {
pub fn new(code: u32, message: *mut libc::c_char) -> Self {
Self {
code: code,
message: message,
}
}
}

#[no_mangle]
pub extern "C" fn df_error_new(code: u32, message: *const libc::c_char) -> *mut DFError {
let error = DFError::new(code, unsafe { libc::strdup(message) });
return Box::into_raw(Box::new(error));
}

#[no_mangle]
pub extern "C" fn df_error_free(error: *mut DFError) {
unsafe {
libc::free((*error).message as *mut libc::c_void);
Box::from_raw(error)
};
}

#[no_mangle]
pub extern "C" fn df_error_get_message(error: *mut DFError) -> *const libc::c_char {
unsafe { (*error).message }
}

trait IntoDFError {
type Value;
fn into_df_error(
self,
error: *mut *mut DFError,
error_value: Option<Self::Value>,
) -> Option<Self::Value>;
}

impl<V, E: std::fmt::Display> IntoDFError for Result<V, E> {
type Value = V;
fn into_df_error(
self,
error: *mut *mut DFError,
error_value: Option<Self::Value>,
) -> Option<Self::Value> {
match self {
Ok(value) => Some(value),
Err(e) => {
if !error.is_null() {
let c_string_message = match CString::new(format!("{}", e)) {
Ok(c_string_message) => c_string_message,
Err(_) => return error_value,
};
unsafe {
*error = df_error_new(1, c_string_message.as_ptr());
};
}
error_value
}
}
}
}

fn block_on<F: Future>(future: F) -> F::Output {
tokio::runtime::Runtime::new().unwrap().block_on(future)
}

#[repr(C)]
pub struct DFDataFrame {
data_frame: Arc<DataFrame>,
}

impl DFDataFrame {
pub fn new(data_frame: Arc<DataFrame>) -> Self {
Self {
data_frame: data_frame,
}
}
}

#[no_mangle]
pub extern "C" fn df_data_frame_unref(data_frame: *mut DFDataFrame) {
unsafe { Box::from_raw(data_frame) };
}

#[no_mangle]
pub extern "C" fn df_data_frame_show(
data_frame: *mut DFDataFrame,
error: *mut *mut DFError,
) {
let future = unsafe { (*data_frame).data_frame.show() };
block_on(future).into_df_error(error, None);
}

#[no_mangle]
pub extern "C" fn df_session_context_new() -> *mut SessionContext {
let ctx = SessionContext::new();
return Box::into_raw(Box::new(ctx));
}

#[no_mangle]
pub extern "C" fn df_session_context_free(ctx: *mut SessionContext) {
unsafe { Box::from_raw(ctx) };
}

#[no_mangle]
pub extern "C" fn df_session_context_sql(
ctx: *mut SessionContext,
sql: *const libc::c_char,
error: *mut *mut DFError,
) -> *mut DFDataFrame {
let cstr_sql = unsafe { CStr::from_ptr(sql) };
let maybe_rs_sql = cstr_sql.to_str().into_df_error(error, None);
let rs_sql = match maybe_rs_sql {
Some(rs_sql) => rs_sql,
None => return std::ptr::null_mut(),
};
let result = block_on(unsafe { (*ctx).sql(rs_sql) });
let maybe_data_frame = result.into_df_error(error, None);
match maybe_data_frame {
Some(data_frame) => Box::into_raw(Box::new(DFDataFrame::new(data_frame))),
None => std::ptr::null_mut(),
}
}

0 comments on commit 1b439bb

Please sign in to comment.