feat(dataframe): expose sort and repartition (#66)
diff --git a/core/src/main/java/org/apache/datafusion/DataFrame.java b/core/src/main/java/org/apache/datafusion/DataFrame.java
index 3669b55..38ec2f5 100644
--- a/core/src/main/java/org/apache/datafusion/DataFrame.java
+++ b/core/src/main/java/org/apache/datafusion/DataFrame.java
@@ -335,6 +335,87 @@
}
/**
+ * Order the rows by the supplied sort keys. Each {@link SortExpr} names a column and a direction
+ * ({@link SortExpr#asc(String)} / {@link SortExpr#desc(String)}); call {@link
+ * SortExpr#nullsFirst(boolean)} to override null placement.
+ *
+ * <p>An empty {@code exprs} array is a no-op (matches DataFusion's {@code sort(vec![])}). The
+ * receiver remains usable and must still be closed independently.
+ *
+ * @throws IllegalArgumentException if {@code exprs} or any element is {@code null}.
+ * @throws RuntimeException if a sort column does not exist in this DataFrame's schema.
+ */
+ public DataFrame sort(SortExpr... exprs) {
+ if (nativeHandle == 0) {
+ throw new IllegalStateException("DataFrame is closed or already collected");
+ }
+ if (exprs == null) {
+ throw new IllegalArgumentException("sort exprs must be non-null");
+ }
+ String[] columns = new String[exprs.length];
+ boolean[] ascending = new boolean[exprs.length];
+ boolean[] nullsFirst = new boolean[exprs.length];
+ for (int i = 0; i < exprs.length; i++) {
+ SortExpr e = exprs[i];
+ if (e == null) {
+ throw new IllegalArgumentException("sort exprs[" + i + "] must be non-null");
+ }
+ columns[i] = e.column();
+ ascending[i] = e.ascending();
+ nullsFirst[i] = e.nullsFirst();
+ }
+ return new DataFrame(sortRows(nativeHandle, columns, ascending, nullsFirst));
+ }
+
+ /**
+ * Repartition this DataFrame using a round-robin scheme across {@code numPartitions} output
+ * partitions. The receiver remains usable and must still be closed independently.
+ *
+ * @throws IllegalArgumentException if {@code numPartitions <= 0}.
+ * @throws RuntimeException if the underlying repartition plan rejects the request.
+ */
+ public DataFrame repartitionRoundRobin(int numPartitions) {
+ if (nativeHandle == 0) {
+ throw new IllegalStateException("DataFrame is closed or already collected");
+ }
+ if (numPartitions <= 0) {
+ throw new IllegalArgumentException("numPartitions must be positive, was " + numPartitions);
+ }
+ return new DataFrame(repartitionRoundRobinRows(nativeHandle, numPartitions));
+ }
+
+ /**
+ * Repartition this DataFrame by hashing the named columns into {@code numPartitions} output
+ * partitions. v1 supports column-name keys only; expression keys are deferred until the Java
+ * binding gains an {@code Expr} builder. The receiver remains usable and must still be closed
+ * independently.
+ *
+ * @throws IllegalArgumentException if {@code numPartitions <= 0}, {@code columns} is {@code null}
+ * or empty, or any element of {@code columns} is {@code null}.
+ * @throws RuntimeException if a partition column does not exist in this DataFrame's schema.
+ */
+ public DataFrame repartitionHash(int numPartitions, String... columns) {
+ if (nativeHandle == 0) {
+ throw new IllegalStateException("DataFrame is closed or already collected");
+ }
+ if (numPartitions <= 0) {
+ throw new IllegalArgumentException("numPartitions must be positive, was " + numPartitions);
+ }
+ if (columns == null) {
+ throw new IllegalArgumentException("repartitionHash columns must be non-null");
+ }
+ if (columns.length == 0) {
+ throw new IllegalArgumentException("repartitionHash requires at least one column");
+ }
+ for (int i = 0; i < columns.length; i++) {
+ if (columns[i] == null) {
+ throw new IllegalArgumentException("repartitionHash columns[" + i + "] must be non-null");
+ }
+ }
+ return new DataFrame(repartitionHashRows(nativeHandle, numPartitions, columns));
+ }
+
+ /**
* Equi-join this DataFrame with {@code right} on the named columns, using the given {@link
* JoinType}. The receiver and {@code right} both remain usable and must still be closed
* independently.
@@ -587,6 +668,13 @@
private static native long unnestColumns(long handle, String[] columns, boolean preserveNulls);
+ private static native long sortRows(
+ long handle, String[] columns, boolean[] ascending, boolean[] nullsFirst);
+
+ private static native long repartitionRoundRobinRows(long handle, int numPartitions);
+
+ private static native long repartitionHashRows(long handle, int numPartitions, String[] columns);
+
private static native long joinDataFrame(
long leftHandle,
long rightHandle,
diff --git a/core/src/main/java/org/apache/datafusion/SortExpr.java b/core/src/main/java/org/apache/datafusion/SortExpr.java
new file mode 100644
index 0000000..e94997d
--- /dev/null
+++ b/core/src/main/java/org/apache/datafusion/SortExpr.java
@@ -0,0 +1,82 @@
+/*
+ * 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 org.apache.datafusion;
+
+/**
+ * A single sort key passed to {@link DataFrame#sort(SortExpr...)}, mirroring DataFusion's {@code
+ * expr::Sort{ expr, asc, nulls_first }}. Build via {@link #asc(String)} or {@link #desc(String)};
+ * tweak null placement via {@link #nullsFirst(boolean)}.
+ *
+ * <p>v1 accepts column names only -- the {@code column} string is interpreted as a column
+ * reference, not a SQL expression. Complex sort keys (e.g. {@code "a + b"}) are intentionally
+ * deferred until the Java binding gains an {@code Expr} builder.
+ *
+ * <p>Defaults match DataFusion: {@link #asc(String)} sorts ascending with NULLs last; {@link
+ * #desc(String)} sorts descending with NULLs first.
+ */
+public final class SortExpr {
+
+ private final String column;
+ private final boolean ascending;
+ private boolean nullsFirst;
+
+ private SortExpr(String column, boolean ascending, boolean nullsFirst) {
+ this.column = column;
+ this.ascending = ascending;
+ this.nullsFirst = nullsFirst;
+ }
+
+ /** Sort the given column ascending, with NULLs placed last. */
+ public static SortExpr asc(String column) {
+ if (column == null) {
+ throw new IllegalArgumentException("SortExpr column must be non-null");
+ }
+ return new SortExpr(column, true, false);
+ }
+
+ /** Sort the given column descending, with NULLs placed first. */
+ public static SortExpr desc(String column) {
+ if (column == null) {
+ throw new IllegalArgumentException("SortExpr column must be non-null");
+ }
+ return new SortExpr(column, false, true);
+ }
+
+ /** Override the default NULL placement. {@code true} = NULLs first, {@code false} = last. */
+ public SortExpr nullsFirst(boolean v) {
+ this.nullsFirst = v;
+ return this;
+ }
+
+ /** The column name this sort key references. */
+ public String column() {
+ return column;
+ }
+
+ /** {@code true} if ascending, {@code false} if descending. */
+ public boolean ascending() {
+ return ascending;
+ }
+
+ /** {@code true} if NULLs are placed first, {@code false} if last. */
+ public boolean nullsFirst() {
+ return nullsFirst;
+ }
+}
diff --git a/core/src/test/java/org/apache/datafusion/DataFrameTransformationsTest.java b/core/src/test/java/org/apache/datafusion/DataFrameTransformationsTest.java
index 495f4ce..ab977a9 100644
--- a/core/src/test/java/org/apache/datafusion/DataFrameTransformationsTest.java
+++ b/core/src/test/java/org/apache/datafusion/DataFrameTransformationsTest.java
@@ -135,6 +135,9 @@
assertThrows(IllegalStateException.class, () -> df.withColumnRenamed("x", "y"));
assertThrows(IllegalStateException.class, () -> df.withColumn("y", "x + 1"));
assertThrows(IllegalStateException.class, () -> df.unnestColumns("x"));
+ assertThrows(IllegalStateException.class, () -> df.sort(SortExpr.asc("x")));
+ assertThrows(IllegalStateException.class, () -> df.repartitionRoundRobin(1));
+ assertThrows(IllegalStateException.class, () -> df.repartitionHash(1, "x"));
assertThrows(IllegalStateException.class, df::count);
assertThrows(IllegalStateException.class, df::show);
assertThrows(IllegalStateException.class, () -> df.show(5));
@@ -158,6 +161,9 @@
assertThrows(IllegalStateException.class, () -> df.withColumnRenamed("x", "y"));
assertThrows(IllegalStateException.class, () -> df.withColumn("y", "x + 1"));
assertThrows(IllegalStateException.class, () -> df.unnestColumns("x"));
+ assertThrows(IllegalStateException.class, () -> df.sort(SortExpr.asc("x")));
+ assertThrows(IllegalStateException.class, () -> df.repartitionRoundRobin(1));
+ assertThrows(IllegalStateException.class, () -> df.repartitionHash(1, "x"));
assertThrows(IllegalStateException.class, df::count);
assertThrows(IllegalStateException.class, df::show);
assertThrows(IllegalStateException.class, () -> df.show(5));
@@ -504,4 +510,219 @@
() -> df.unnestColumns(new UnnestOptions(), (String[]) null));
}
}
+
+ // -- sort -----------------------------------------------------------------
+
+ @Test
+ void sortAscOrdersAscending() throws Exception {
+ try (BufferAllocator allocator = new RootAllocator();
+ SessionContext ctx = new SessionContext();
+ DataFrame df = ctx.sql("SELECT * FROM (VALUES (3), (1), (2)) AS t(x)");
+ DataFrame sorted = df.sort(SortExpr.asc("x"));
+ ArrowReader reader = sorted.collect(allocator)) {
+ assertTrue(reader.loadNextBatch());
+ VectorSchemaRoot root = reader.getVectorSchemaRoot();
+ BigIntVector x = (BigIntVector) root.getVector("x");
+ assertEquals(3, x.getValueCount());
+ assertEquals(1L, x.get(0));
+ assertEquals(2L, x.get(1));
+ assertEquals(3L, x.get(2));
+ }
+ }
+
+ @Test
+ void sortDescOrdersDescending() throws Exception {
+ try (BufferAllocator allocator = new RootAllocator();
+ SessionContext ctx = new SessionContext();
+ DataFrame df = ctx.sql("SELECT * FROM (VALUES (3), (1), (2)) AS t(x)");
+ DataFrame sorted = df.sort(SortExpr.desc("x"));
+ ArrowReader reader = sorted.collect(allocator)) {
+ assertTrue(reader.loadNextBatch());
+ VectorSchemaRoot root = reader.getVectorSchemaRoot();
+ BigIntVector x = (BigIntVector) root.getVector("x");
+ assertEquals(3, x.getValueCount());
+ assertEquals(3L, x.get(0));
+ assertEquals(2L, x.get(1));
+ assertEquals(1L, x.get(2));
+ }
+ }
+
+ @Test
+ void sortMultipleKeysMixedDirection() throws Exception {
+ try (BufferAllocator allocator = new RootAllocator();
+ SessionContext ctx = new SessionContext();
+ DataFrame df = ctx.sql("SELECT * FROM (VALUES (1, 1), (1, 2), (2, 1)) AS t(x, y)");
+ DataFrame sorted = df.sort(SortExpr.asc("x"), SortExpr.desc("y"));
+ ArrowReader reader = sorted.collect(allocator)) {
+ assertTrue(reader.loadNextBatch());
+ VectorSchemaRoot root = reader.getVectorSchemaRoot();
+ BigIntVector x = (BigIntVector) root.getVector("x");
+ BigIntVector y = (BigIntVector) root.getVector("y");
+ // (1,2), (1,1), (2,1) -- x asc, y desc within each x group.
+ assertEquals(1L, x.get(0));
+ assertEquals(2L, y.get(0));
+ assertEquals(1L, x.get(1));
+ assertEquals(1L, y.get(1));
+ assertEquals(2L, x.get(2));
+ assertEquals(1L, y.get(2));
+ }
+ }
+
+ @Test
+ void sortNullsFirstPlacesNullsFirst() throws Exception {
+ String sql =
+ "SELECT * FROM (VALUES (CAST(1 AS BIGINT)), (CAST(NULL AS BIGINT)),"
+ + " (CAST(2 AS BIGINT))) AS t(x)";
+ try (BufferAllocator allocator = new RootAllocator();
+ SessionContext ctx = new SessionContext();
+ DataFrame df = ctx.sql(sql);
+ DataFrame sorted = df.sort(SortExpr.asc("x").nullsFirst(true));
+ ArrowReader reader = sorted.collect(allocator)) {
+ assertTrue(reader.loadNextBatch());
+ VectorSchemaRoot root = reader.getVectorSchemaRoot();
+ BigIntVector x = (BigIntVector) root.getVector("x");
+ assertEquals(3, x.getValueCount());
+ assertTrue(x.isNull(0), "expected NULL first");
+ assertEquals(1L, x.get(1));
+ assertEquals(2L, x.get(2));
+ }
+ }
+
+ @Test
+ void sortNullsLastPlacesNullsLast() throws Exception {
+ String sql =
+ "SELECT * FROM (VALUES (CAST(1 AS BIGINT)), (CAST(NULL AS BIGINT)),"
+ + " (CAST(2 AS BIGINT))) AS t(x)";
+ try (BufferAllocator allocator = new RootAllocator();
+ SessionContext ctx = new SessionContext();
+ DataFrame df = ctx.sql(sql);
+ DataFrame sorted = df.sort(SortExpr.asc("x").nullsFirst(false));
+ ArrowReader reader = sorted.collect(allocator)) {
+ assertTrue(reader.loadNextBatch());
+ VectorSchemaRoot root = reader.getVectorSchemaRoot();
+ BigIntVector x = (BigIntVector) root.getVector("x");
+ assertEquals(3, x.getValueCount());
+ assertEquals(1L, x.get(0));
+ assertEquals(2L, x.get(1));
+ assertTrue(x.isNull(2), "expected NULL last");
+ }
+ }
+
+ @Test
+ void sortIsNonDestructive() {
+ try (SessionContext ctx = new SessionContext();
+ DataFrame source = ctx.sql("SELECT * FROM (VALUES (3), (1), (2)) AS t(x)")) {
+ try (DataFrame sorted = source.sort(SortExpr.asc("x"))) {
+ assertEquals(3L, sorted.count());
+ }
+ assertEquals(3L, source.count());
+ }
+ }
+
+ @Test
+ void sortRejectsNullArgs() {
+ try (SessionContext ctx = new SessionContext();
+ DataFrame df = ctx.sql("SELECT 1 AS x")) {
+ assertThrows(IllegalArgumentException.class, () -> df.sort((SortExpr[]) null));
+ assertThrows(IllegalArgumentException.class, () -> df.sort(new SortExpr[] {null}));
+ }
+ }
+
+ @Test
+ void sortUnknownColumnThrows() {
+ // Sort plan-builds lazily; DataFusion may not validate column names until the plan is
+ // actually executed. Drive a count() to force planning + optimization.
+ try (SessionContext ctx = new SessionContext();
+ DataFrame df = ctx.sql("SELECT 1 AS x")) {
+ assertThrows(
+ RuntimeException.class,
+ () -> {
+ try (DataFrame sorted = df.sort(SortExpr.asc("not_a_column"))) {
+ sorted.count();
+ }
+ });
+ }
+ }
+
+ // -- repartitionRoundRobin -------------------------------------------------
+
+ @Test
+ void repartitionRoundRobinPreservesRows() {
+ try (SessionContext ctx = new SessionContext();
+ DataFrame source = ctx.sql("SELECT * FROM (VALUES (1), (2), (3), (4), (5)) AS t(x)");
+ DataFrame repartitioned = source.repartitionRoundRobin(3)) {
+ assertEquals(5L, repartitioned.count());
+ }
+ }
+
+ @Test
+ void repartitionRoundRobinIsNonDestructive() {
+ try (SessionContext ctx = new SessionContext();
+ DataFrame source = ctx.sql("SELECT * FROM (VALUES (1), (2), (3)) AS t(x)")) {
+ try (DataFrame rp = source.repartitionRoundRobin(2)) {
+ assertEquals(3L, rp.count());
+ }
+ assertEquals(3L, source.count());
+ }
+ }
+
+ @Test
+ void repartitionRoundRobinRejectsNonPositive() {
+ try (SessionContext ctx = new SessionContext();
+ DataFrame df = ctx.sql("SELECT 1 AS x")) {
+ assertThrows(IllegalArgumentException.class, () -> df.repartitionRoundRobin(0));
+ assertThrows(IllegalArgumentException.class, () -> df.repartitionRoundRobin(-1));
+ }
+ }
+
+ // -- repartitionHash -------------------------------------------------------
+
+ @Test
+ void repartitionHashPreservesRows() {
+ try (SessionContext ctx = new SessionContext();
+ DataFrame source = ctx.sql("SELECT * FROM (VALUES (1), (2), (3), (4), (5)) AS t(x)");
+ DataFrame repartitioned = source.repartitionHash(3, "x")) {
+ assertEquals(5L, repartitioned.count());
+ }
+ }
+
+ @Test
+ void repartitionHashIsNonDestructive() {
+ try (SessionContext ctx = new SessionContext();
+ DataFrame source = ctx.sql("SELECT * FROM (VALUES (1), (2), (3)) AS t(x)")) {
+ try (DataFrame rp = source.repartitionHash(2, "x")) {
+ assertEquals(3L, rp.count());
+ }
+ assertEquals(3L, source.count());
+ }
+ }
+
+ @Test
+ void repartitionHashRejectsBadArgs() {
+ try (SessionContext ctx = new SessionContext();
+ DataFrame df = ctx.sql("SELECT 1 AS x")) {
+ assertThrows(IllegalArgumentException.class, () -> df.repartitionHash(0, "x"));
+ assertThrows(IllegalArgumentException.class, () -> df.repartitionHash(-1, "x"));
+ assertThrows(IllegalArgumentException.class, () -> df.repartitionHash(2, (String[]) null));
+ assertThrows(IllegalArgumentException.class, () -> df.repartitionHash(2));
+ assertThrows(
+ IllegalArgumentException.class, () -> df.repartitionHash(2, new String[] {null}));
+ }
+ }
+
+ @Test
+ void repartitionHashUnknownColumnThrows() {
+ // Repartition plan-builds lazily; DataFusion may not validate column names until the plan
+ // is actually executed. Drive a count() to force planning + optimization.
+ try (SessionContext ctx = new SessionContext();
+ DataFrame df = ctx.sql("SELECT 1 AS x")) {
+ assertThrows(
+ RuntimeException.class,
+ () -> {
+ try (DataFrame rp = df.repartitionHash(2, "not_a_column")) {
+ rp.count();
+ }
+ });
+ }
+ }
}
diff --git a/core/src/test/java/org/apache/datafusion/SortExprTest.java b/core/src/test/java/org/apache/datafusion/SortExprTest.java
new file mode 100644
index 0000000..7287205
--- /dev/null
+++ b/core/src/test/java/org/apache/datafusion/SortExprTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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 org.apache.datafusion;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+class SortExprTest {
+
+ @Test
+ void ascDefaultsToNullsLast() {
+ SortExpr s = SortExpr.asc("a");
+ assertTrue(s.ascending());
+ assertFalse(s.nullsFirst());
+ }
+
+ @Test
+ void descDefaultsToNullsFirst() {
+ SortExpr s = SortExpr.desc("a");
+ assertFalse(s.ascending());
+ assertTrue(s.nullsFirst());
+ }
+
+ @Test
+ void nullsFirstSetterRoundTrips() {
+ SortExpr s = SortExpr.asc("a").nullsFirst(true);
+ assertTrue(s.nullsFirst());
+ s.nullsFirst(false);
+ assertFalse(s.nullsFirst());
+ }
+
+ @Test
+ void factoryRejectsNullColumn() {
+ assertThrows(IllegalArgumentException.class, () -> SortExpr.asc(null));
+ assertThrows(IllegalArgumentException.class, () -> SortExpr.desc(null));
+ }
+
+ @Test
+ void accessorReturnsColumnName() {
+ assertEquals("foo", SortExpr.asc("foo").column());
+ assertEquals("bar", SortExpr.desc("bar").column());
+ }
+}
diff --git a/native/src/lib.rs b/native/src/lib.rs
index ccfb38e..b59d4fd 100644
--- a/native/src/lib.rs
+++ b/native/src/lib.rs
@@ -49,10 +49,10 @@
use datafusion::execution::runtime_env::RuntimeEnvBuilder;
use datafusion::execution::SendableRecordBatchStream;
use datafusion::logical_expr::Expr;
-use datafusion::logical_expr::{ScalarUDF, Signature};
+use datafusion::logical_expr::{col, Partitioning, ScalarUDF, Signature, SortExpr};
use datafusion::prelude::{ParquetReadOptions, SessionConfig, SessionContext};
use futures::StreamExt;
-use jni::objects::{JByteArray, JClass, JObject, JObjectArray, JString};
+use jni::objects::{JBooleanArray, JByteArray, JClass, JObject, JObjectArray, JString};
use jni::sys::{jboolean, jbyte, jbyteArray, jint, jlong};
use jni::JNIEnv;
use jni::JavaVM;
@@ -727,6 +727,91 @@
}
#[no_mangle]
+pub extern "system" fn Java_org_apache_datafusion_DataFrame_sortRows<'local>(
+ mut env: JNIEnv<'local>,
+ _class: JClass<'local>,
+ handle: jlong,
+ columns: JObjectArray<'local>,
+ ascending: JBooleanArray<'local>,
+ nulls_first: JBooleanArray<'local>,
+) -> jlong {
+ try_unwrap_or_throw(&mut env, 0, |env| -> JniResult<jlong> {
+ if handle == 0 {
+ return Err("DataFrame handle is null".into());
+ }
+ let df = unsafe { &*(handle as *const DataFrame) }.clone();
+
+ let len = env.get_array_length(&columns)? as usize;
+ let mut names: Vec<String> = Vec::with_capacity(len);
+ for i in 0..len {
+ let elem = env.get_object_array_element(&columns, i as i32)?;
+ let jstr: JString = elem.into();
+ names.push(env.get_string(&jstr)?.into());
+ }
+
+ // Decode the two parallel boolean arrays via primitive-array region copies.
+ let mut asc_buf = vec![0u8; len];
+ env.get_boolean_array_region(&ascending, 0, &mut asc_buf)?;
+ let mut nf_buf = vec![0u8; len];
+ env.get_boolean_array_region(&nulls_first, 0, &mut nf_buf)?;
+
+ let sort_exprs: Vec<SortExpr> = names
+ .into_iter()
+ .zip(asc_buf.into_iter().zip(nf_buf))
+ .map(|(name, (asc, nf))| SortExpr::new(col(&name), asc != 0, nf != 0))
+ .collect();
+
+ let new_df = df.sort(sort_exprs)?;
+ Ok(Box::into_raw(Box::new(new_df)) as jlong)
+ })
+}
+
+#[no_mangle]
+pub extern "system" fn Java_org_apache_datafusion_DataFrame_repartitionRoundRobinRows<'local>(
+ mut env: JNIEnv<'local>,
+ _class: JClass<'local>,
+ handle: jlong,
+ num_partitions: jint,
+) -> jlong {
+ try_unwrap_or_throw(&mut env, 0, |_env| -> JniResult<jlong> {
+ if handle == 0 {
+ return Err("DataFrame handle is null".into());
+ }
+ let df = unsafe { &*(handle as *const DataFrame) }.clone();
+ let new_df = df.repartition(Partitioning::RoundRobinBatch(num_partitions as usize))?;
+ Ok(Box::into_raw(Box::new(new_df)) as jlong)
+ })
+}
+
+#[no_mangle]
+pub extern "system" fn Java_org_apache_datafusion_DataFrame_repartitionHashRows<'local>(
+ mut env: JNIEnv<'local>,
+ _class: JClass<'local>,
+ handle: jlong,
+ num_partitions: jint,
+ columns: JObjectArray<'local>,
+) -> jlong {
+ try_unwrap_or_throw(&mut env, 0, |env| -> JniResult<jlong> {
+ if handle == 0 {
+ return Err("DataFrame handle is null".into());
+ }
+ let df = unsafe { &*(handle as *const DataFrame) }.clone();
+
+ let len = env.get_array_length(&columns)?;
+ let mut owned: Vec<String> = Vec::with_capacity(len as usize);
+ for i in 0..len {
+ let elem = env.get_object_array_element(&columns, i)?;
+ let jstr: JString = elem.into();
+ owned.push(env.get_string(&jstr)?.into());
+ }
+ let exprs = owned.iter().map(|s| col(s.as_str())).collect();
+
+ let new_df = df.repartition(Partitioning::Hash(exprs, num_partitions as usize))?;
+ Ok(Box::into_raw(Box::new(new_df)) as jlong)
+ })
+}
+
+#[no_mangle]
pub extern "system" fn Java_org_apache_datafusion_DataFrame_writeParquetWithOptions<'local>(
mut env: JNIEnv<'local>,
_class: JClass<'local>,