Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions java/src/main/java/ai/rapids/cudf/JoinKind.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

package ai.rapids.cudf;

/**
* Join semantics to apply when filtering equality-join gather maps of the corresponding kind.
* See {@link Table#filterJoinGatherMaps} for the input-map contract.
*/
public enum JoinKind {
/** Retain only row pairs that satisfy the condition. */
INNER(0),
/** Retain every left row, using an invalid right index when no pair satisfies the condition. */
LEFT(1),
/**
* Retain passing pairs and every row from both sides, using one invalid opposite-side index
* for each row with no pair that satisfies the condition.
*/
FULL(2);
Comment thread
wjxiz1992 marked this conversation as resolved.

final int nativeId;

JoinKind(int nativeId) {
this.nativeId = nativeId;
}
}
66 changes: 66 additions & 0 deletions java/src/main/java/ai/rapids/cudf/Table.java
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,15 @@ private static native long[] fullJoinGatherMaps(long leftKeys, long rightKeys,
private static native long[] fullHashJoinGatherMapsWithCount(long leftTable, long rightHashJoin,
long outputRowCount) throws CudfException;

private static native long[] filterJoinGatherMaps(long leftGatherMapAddress,
long leftGatherMapLength,
long rightGatherMapAddress,
long rightGatherMapLength,
long leftTable,
long rightTable,
long condition,
int joinKind) throws CudfException;

private static native long[] leftSemiJoinGatherMap(long leftKeys, long rightKeys,
boolean compareNullsEqual) throws CudfException;

Expand Down Expand Up @@ -2767,6 +2776,63 @@ private static GatherMap[] buildJoinGatherMaps(long[] gatherMapData) {
return maps;
}

/**
* Filters a pair of join gather maps by evaluating a conditional expression on the
* corresponding rows from the left and right tables.
*
* <p>The maps must be the paired results of an equality join of the same kind as
* {@code joinKind}: INNER maps for {@link JoinKind#INNER}, LEFT maps for {@link JoinKind#LEFT},
* and FULL maps for {@link JoinKind#FULL}. For example, maps from
* {@link #leftJoinGatherMaps(HashJoin)} can be filtered with {@code JoinKind.LEFT}. Equivalent
* equality-join maps from other producers are also supported. Each conditional table must have
* the same row count and row numbering as its corresponding equality-join source table; its
* columns may differ. The maps must have the same length, and entries at the same position
* identify a candidate row pair. The join origin and index validity are not checked, and
* converting maps between join kinds is unsupported.
*
* <p>{@link Integer#MIN_VALUE} denotes an unmatched row in an outer-join map. Such pairs pass
* through without evaluating the condition. For pairs with two valid indices, the condition
* must produce a Boolean result; false or null means no match. LEFT and FULL retain one
* unmatched entry for each retained-side row with no passing candidate. Empty input maps
* produce empty output maps; this method does not complete an outer join from empty INNER
* maps. In particular, LEFT maps for a nonempty left table and an empty right table must
* already contain the unmatched left rows.
*
* <p>The input gather maps are not modified or closed. Two new {@link GatherMap} instances
* with independent storage are returned for the left and right tables, respectively. The
* outputs remain valid after closing the inputs, and closing the outputs does not prevent
* reusing the inputs. Output row order is unspecified.
*
* <p>It is the responsibility of the caller to close the resulting gather map instances.
*
* @param leftGatherMap input gather map for the left table
* @param rightGatherMap input gather map for the right table
* @param leftTable left table containing the columns referenced by the condition
* @param rightTable right table containing the columns referenced by the condition
* @param condition Boolean conditional expression to evaluate for each valid pair
* @param joinKind kind of the input equality join and the filtered output join
* @return filtered left and right table gather maps
* @throws IllegalArgumentException if the input gather maps have different lengths
*/
public static GatherMap[] filterJoinGatherMaps(GatherMap leftGatherMap,
GatherMap rightGatherMap,
Table leftTable,
Table rightTable,
CompiledExpression condition,
JoinKind joinKind) {
long leftLength = leftGatherMap.getBufferLength();
long rightLength = rightGatherMap.getBufferLength();
if (leftLength != rightLength) {
throw new IllegalArgumentException("left and right gather maps must have the same length");
}
long[] gatherMapData = filterJoinGatherMaps(
leftGatherMap.getBufferAddress(), leftLength,
rightGatherMap.getBufferAddress(), rightLength,
leftTable.getNativeView(), rightTable.getNativeView(),
condition.getNativeHandle(), joinKind.nativeId);
return buildJoinGatherMaps(gatherMapData);
}

/**
* Computes the gather maps that can be used to manifest the result of a left equi-join between
* two tables. It is assumed this table instance holds the key columns from the left table, and
Expand Down
55 changes: 55 additions & 0 deletions java/src/main/native/src/TableJni.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3535,6 +3535,61 @@ JNIEXPORT jlongArray JNICALL Java_ai_rapids_cudf_Table_fullHashJoinGatherMapsWit
});
}

JNIEXPORT jlongArray JNICALL
Java_ai_rapids_cudf_Table_filterJoinGatherMaps(JNIEnv* env,
jclass,
jlong j_left_gather_map_address,
jlong j_left_gather_map_length,
jlong j_right_gather_map_address,
jlong j_right_gather_map_length,
jlong j_left_table,
jlong j_right_table,
jlong j_condition,
jint j_join_kind)
{
constexpr jlong index_size = sizeof(cudf::size_type);
if (j_left_gather_map_length < 0 || j_left_gather_map_length % index_size != 0) {
JNI_THROW_NEW(
env, cudf::jni::ILLEGAL_ARG_EXCEPTION_CLASS, "invalid left gather map length", NULL);
}
if (j_right_gather_map_length != j_left_gather_map_length) {
JNI_THROW_NEW(env,
cudf::jni::ILLEGAL_ARG_EXCEPTION_CLASS,
"left and right gather maps must have the same length",
NULL);
}
if (j_left_gather_map_length != 0) {
JNI_NULL_CHECK(env, j_left_gather_map_address, "left gather map is null", NULL);
JNI_NULL_CHECK(env, j_right_gather_map_address, "right gather map is null", NULL);
}
JNI_NULL_CHECK(env, j_left_table, "left table is null", NULL);
JNI_NULL_CHECK(env, j_right_table, "right table is null", NULL);
JNI_NULL_CHECK(env, j_condition, "condition is null", NULL);

JNI_TRY
{
cudf::jni::auto_set_device(env);
auto const map_size = static_cast<std::size_t>(j_left_gather_map_length / index_size);
auto const left_indices = cudf::device_span<cudf::size_type const>{
reinterpret_cast<cudf::size_type const*>(j_left_gather_map_address), map_size};
auto const right_indices = cudf::device_span<cudf::size_type const>{
reinterpret_cast<cudf::size_type const*>(j_right_gather_map_address), map_size};
auto const left_table = reinterpret_cast<cudf::table_view const*>(j_left_table);
auto const right_table = reinterpret_cast<cudf::table_view const*>(j_right_table);
auto const condition = reinterpret_cast<cudf::jni::ast::compiled_expr const*>(j_condition);
auto const join_kind = static_cast<cudf::join_kind>(j_join_kind);

return cudf::jni::gather_maps_to_java(env,
cudf::filter_join_indices(*left_table,
*right_table,
left_indices,
right_indices,
condition->get_top_expression(),
join_kind));
}
JNI_CATCH(env, NULL);
}

JNIEXPORT jlongArray JNICALL Java_ai_rapids_cudf_Table_conditionalFullJoinGatherMaps(
JNIEnv* env, jclass, jlong j_left_table, jlong j_right_table, jlong j_condition)
{
Expand Down
Loading
Loading