Skip to content

Commit 8d0dfb6

Browse files
committed
Initial commit of plugin engine-datafusion
Signed-off-by: Marc Handalian <marc.handalian@gmail.com>
1 parent d52d404 commit 8d0dfb6

15 files changed

Lines changed: 1133 additions & 0 deletions
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Gradle
2+
.gradle/
3+
build/
4+
5+
# Java
6+
*.class
7+
*.jar
8+
*.war
9+
*.ear
10+
hs_err_pid*
11+
12+
# IDE
13+
.idea/
14+
*.iml
15+
*.ipr
16+
*.iws
17+
.vscode/
18+
.settings/
19+
.project
20+
.classpath
21+
22+
# OS
23+
.DS_Store
24+
Thumbs.db
25+
26+
# Rust
27+
jni/target/
28+
jni/Cargo.lock
29+
30+
# Native libraries
31+
src/main/resources/native/
32+
33+
# Logs
34+
*.log
35+
36+
# Temporary files
37+
*.tmp
38+
*.temp
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*/
8+
9+
apply plugin: 'java'
10+
apply plugin: 'idea'
11+
apply plugin: 'opensearch.internal-cluster-test'
12+
apply plugin: 'opensearch.yaml-rest-test'
13+
apply plugin: 'opensearch.pluginzip'
14+
15+
def pluginName = 'engine-datafusion'
16+
def pluginDescription = 'OpenSearch plugin providing access to DataFusion via JNI'
17+
def projectPath = 'org.opensearch'
18+
def pathToPlugin = 'datafusion.DataFusionPlugin'
19+
def pluginClassName = 'DataFusionPlugin'
20+
21+
opensearchplugin {
22+
name = pluginName
23+
description = pluginDescription
24+
classname = "${projectPath}.${pathToPlugin}"
25+
licenseFile = rootProject.file('LICENSE.txt')
26+
noticeFile = rootProject.file('NOTICE.txt')
27+
}
28+
29+
dependencies {
30+
implementation "org.apache.logging.log4j:log4j-api:${versions.log4j}"
31+
implementation "org.apache.logging.log4j:log4j-core:${versions.log4j}"
32+
testImplementation "junit:junit:${versions.junit}"
33+
testImplementation "org.hamcrest:hamcrest:${versions.hamcrest}"
34+
testImplementation "org.mockito:mockito-core:${versions.mockito}"
35+
}
36+
37+
// Task to build the Rust JNI library
38+
task buildRustLibrary(type: Exec) {
39+
description = 'Build the Rust JNI library using Cargo'
40+
group = 'build'
41+
42+
workingDir file('jni')
43+
44+
// Determine the target directory and library name based on OS
45+
def osName = System.getProperty('os.name').toLowerCase()
46+
def libPrefix = osName.contains('windows') ? '' : 'lib'
47+
def libExtension = osName.contains('windows') ? '.dll' : (osName.contains('mac') ? '.dylib' : '.so')
48+
49+
// Use debug build for development, release for production
50+
def buildType = project.hasProperty('rustRelease') ? 'release' : 'debug'
51+
def targetDir = "target/${buildType}"
52+
53+
def cargoArgs = ['cargo', 'build']
54+
if (buildType == 'release') {
55+
cargoArgs.add('--release')
56+
}
57+
58+
if (osName.contains('windows')) {
59+
commandLine cargoArgs
60+
} else {
61+
commandLine cargoArgs
62+
}
63+
64+
// Set environment variables for cross-compilation if needed
65+
environment 'CARGO_TARGET_DIR', file('jni/target').absolutePath
66+
67+
inputs.files fileTree('jni/src')
68+
inputs.file 'jni/Cargo.toml'
69+
outputs.files file("jni/${targetDir}/${libPrefix}opensearch_datafusion_jni${libExtension}")
70+
System.out.println("Building Rust library in ${buildType} mode");
71+
}
72+
73+
// Task to copy the native library to resources
74+
task copyNativeLibrary(type: Copy, dependsOn: buildRustLibrary) {
75+
description = 'Copy the native library to Java resources'
76+
group = 'build'
77+
78+
def osName = System.getProperty('os.name').toLowerCase()
79+
def libPrefix = osName.contains('windows') ? '' : 'lib'
80+
def libExtension = osName.contains('windows') ? '.dll' : (osName.contains('mac') ? '.dylib' : '.so')
81+
def buildType = project.hasProperty('rustRelease') ? 'release' : 'debug'
82+
83+
from file("jni/target/${buildType}/${libPrefix}opensearch_datafusion_jni${libExtension}")
84+
into file('src/main/resources/native')
85+
86+
// Rename to a standard name for Java to load
87+
rename { filename ->
88+
"libopensearch_datafusion_jni${libExtension}"
89+
}
90+
}
91+
92+
// Ensure native library is built before Java compilation
93+
compileJava.dependsOn copyNativeLibrary
94+
95+
// Ensure processResources depends on copyNativeLibrary
96+
processResources.dependsOn copyNativeLibrary
97+
98+
// Clean task should also clean Rust artifacts
99+
clean {
100+
delete file('jni/target')
101+
delete file('src/main/resources/native')
102+
}
103+
104+
test {
105+
// Set system property to help tests find the native library
106+
systemProperty 'java.library.path', file('src/main/resources/native').absolutePath
107+
}
108+
109+
yamlRestTest {
110+
systemProperty 'tests.security.manager', 'false'
111+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
[package]
2+
name = "opensearch-datafusion-jni"
3+
version = "0.1.0"
4+
edition = "2021"
5+
description = "JNI bindings for DataFusion integration with OpenSearch"
6+
license = "Apache-2.0"
7+
8+
[lib]
9+
name = "opensearch_datafusion_jni"
10+
crate-type = ["cdylib"]
11+
12+
[dependencies]
13+
datafusion = "49.0.0"
14+
arrow = "55.2"
15+
arrow-json = "55.2"
16+
17+
# JNI dependencies
18+
jni = "0.21"
19+
20+
# Async runtime
21+
tokio = { version = "1.0", features = ["rt", "rt-multi-thread", "macros"] }
22+
23+
# Serialization
24+
serde = { version = "1.0", features = ["derive"] }
25+
serde_json = "1.0"
26+
27+
# Error handling
28+
anyhow = "1.0"
29+
thiserror = "1.0"
30+
31+
# Logging
32+
log = "0.4"
33+
34+
[profile.release]
35+
lto = true
36+
codegen-units = 1
37+
panic = "abort"
38+
39+
[profile.dev]
40+
opt-level = 1 # Some optimization for reasonable performance
41+
lto = false # Disable LTO for faster builds
42+
codegen-units = 16 # More parallel compilation
43+
incremental = true # Enable incremental compilation
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*/
8+
9+
use jni::objects::JClass;
10+
use jni::sys::{jlong, jstring};
11+
use jni::JNIEnv;
12+
13+
use datafusion::execution::context::SessionContext;
14+
15+
use datafusion::DATAFUSION_VERSION;
16+
use datafusion::prelude::SessionConfig;
17+
18+
/// Create a new DataFusion session context
19+
#[no_mangle]
20+
pub extern "system" fn Java_org_opensearch_datafusion_DataFusionJNI_createContext(
21+
_env: JNIEnv,
22+
_class: JClass,
23+
) -> jlong {
24+
let config = SessionConfig::new().with_repartition_aggregations(true);
25+
let context = SessionContext::new_with_config(config);
26+
let ctx = Box::into_raw(Box::new(context)) as jlong;
27+
ctx
28+
}
29+
30+
/// Close and cleanup a DataFusion context
31+
#[no_mangle]
32+
pub extern "system" fn Java_org_opensearch_datafusion_DataFusionJNI_closeContext(
33+
_env: JNIEnv,
34+
_class: JClass,
35+
context_id: jlong,
36+
) {
37+
let _ = unsafe { Box::from_raw(context_id as *mut SessionContext) };
38+
}
39+
40+
/// Get version information
41+
#[no_mangle]
42+
pub extern "system" fn Java_org_opensearch_datafusion_DataFusionJNI_getVersion(
43+
env: JNIEnv,
44+
_class: JClass,
45+
) -> jstring {
46+
env.new_string(DATAFUSION_VERSION).expect("Couldn't create Java string").as_raw()
47+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*/
8+
9+
package org.opensearch.datafusion;
10+
11+
import java.io.IOException;
12+
import java.io.InputStream;
13+
import java.nio.file.Files;
14+
import java.nio.file.Path;
15+
import java.nio.file.StandardCopyOption;
16+
17+
/**
18+
* JNI wrapper for DataFusion operations
19+
*/
20+
public class DataFusionJNI {
21+
22+
private static boolean libraryLoaded = false;
23+
24+
static {
25+
loadNativeLibrary();
26+
}
27+
28+
/**
29+
* Load the native library from resources
30+
*/
31+
private static synchronized void loadNativeLibrary() {
32+
if (libraryLoaded) {
33+
return;
34+
}
35+
36+
try {
37+
String osName = System.getProperty("os.name").toLowerCase();
38+
String libExtension;
39+
String libName;
40+
41+
if (osName.contains("windows")) {
42+
libExtension = ".dll";
43+
libName = "libopensearch_datafusion_jni.dll";
44+
} else if (osName.contains("mac")) {
45+
libExtension = ".dylib";
46+
libName = "libopensearch_datafusion_jni.dylib";
47+
} else {
48+
libExtension = ".so";
49+
libName = "libopensearch_datafusion_jni.so";
50+
}
51+
52+
// Try to load from resources first
53+
InputStream libStream = DataFusionJNI.class.getResourceAsStream("/native/" + libName);
54+
if (libStream != null) {
55+
// Extract to temporary file and load
56+
Path tempLib = Files.createTempFile("libopensearch_datafusion_jni", libExtension);
57+
Files.copy(libStream, tempLib, StandardCopyOption.REPLACE_EXISTING);
58+
tempLib.toFile().deleteOnExit();
59+
System.load(tempLib.toAbsolutePath().toString());
60+
libStream.close();
61+
} else {
62+
// Fallback to system library path
63+
System.loadLibrary("opensearch_datafusion_jni");
64+
}
65+
66+
libraryLoaded = true;
67+
} catch (IOException | UnsatisfiedLinkError e) {
68+
throw new RuntimeException("Failed to load DataFusion JNI library", e);
69+
}
70+
}
71+
72+
/**
73+
* Get version information
74+
* @return JSON string with version information
75+
*/
76+
public static native String getVersion();
77+
}

0 commit comments

Comments
 (0)