Skip to content

Commit 0cb2190

Browse files
expanibharath-techie
authored andcommitted
Integrated InstructionHandler and introduce delegation handle for data node execution
Signed-off-by: expani <anijainc@amazon.com>
1 parent 6888345 commit 0cb2190

23 files changed

Lines changed: 1139 additions & 139 deletions

File tree

sandbox/libs/analytics-framework/src/main/java/org/opensearch/analytics/backend/ShardScanExecutionContext.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010

1111
import org.apache.arrow.memory.BufferAllocator;
1212
import org.opensearch.analytics.spi.CommonExecutionContext;
13+
import org.opensearch.index.IndexSettings;
1314
import org.opensearch.index.engine.exec.IndexReaderProvider.Reader;
15+
import org.opensearch.index.mapper.MapperService;
1416
import org.opensearch.tasks.Task;
1517

1618
/**
@@ -26,6 +28,8 @@ public class ShardScanExecutionContext implements CommonExecutionContext {
2628
private final Task task;
2729
private byte[] fragmentBytes;
2830
private BufferAllocator allocator;
31+
private MapperService mapperService;
32+
private IndexSettings indexSettings;
2933

3034
/**
3135
* Constructs an execution context.
@@ -73,4 +77,24 @@ public BufferAllocator getAllocator() {
7377
public void setAllocator(BufferAllocator allocator) {
7478
this.allocator = allocator;
7579
}
80+
81+
/** Returns the shard's mapper service for field type resolution. */
82+
public MapperService getMapperService() {
83+
return mapperService;
84+
}
85+
86+
/** Sets the shard's mapper service. */
87+
public void setMapperService(MapperService mapperService) {
88+
this.mapperService = mapperService;
89+
}
90+
91+
/** Returns the shard's index settings. */
92+
public IndexSettings getIndexSettings() {
93+
return indexSettings;
94+
}
95+
96+
/** Sets the shard's index settings. */
97+
public void setIndexSettings(IndexSettings indexSettings) {
98+
this.indexSettings = indexSettings;
99+
}
76100
}

sandbox/libs/analytics-framework/src/main/java/org/opensearch/analytics/spi/AnalyticsSearchBackendPlugin.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
package org.opensearch.analytics.spi;
1010

11+
import java.util.List;
12+
1113
/**
1214
* SPI extension point for backend query engine plugins.
1315
*
@@ -81,4 +83,34 @@ default ExchangeSinkProvider getExchangeSinkProvider() {
8183
default FragmentInstructionHandlerFactory getInstructionHandlerFactory() {
8284
throw new UnsupportedOperationException("getInstructionHandlerFactory not implemented for [" + name() + "]");
8385
}
86+
87+
/**
88+
* Prepare a filter delegation handle for the given delegated expressions.
89+
* Called by Core after all instruction handlers have run, when the plan has delegation.
90+
*
91+
* <p>The accepting backend initializes its internal state (e.g., DirectoryReader,
92+
* QueryShardContext, compiled Queries) and returns a handle that the driving backend
93+
* will call into during execution.
94+
*
95+
* @param expressions the delegated expressions (annotationId + serialized query bytes)
96+
* @param ctx the shared execution context (Reader, MapperService, IndexSettings)
97+
* @return a handle the driving backend calls into via FFM upcalls
98+
*/
99+
default FilterDelegationHandle getFilterDelegationHandle(List<DelegatedExpression> expressions, CommonExecutionContext ctx) {
100+
throw new UnsupportedOperationException("getFilterDelegationHandle not implemented for [" + name() + "]");
101+
}
102+
103+
/**
104+
* Configure the driving backend to use the given delegation handle during execution.
105+
* Called by Core after obtaining the handle from the accepting backend.
106+
*
107+
* <p>The driving backend registers the handle so that FFM upcalls from Rust
108+
* (createProvider, createCollector, collectDocs) route to it.
109+
*
110+
* @param handle the delegation handle from the accepting backend
111+
* @param backendContext the driving backend's execution context (from instruction handlers)
112+
*/
113+
default void configureFilterDelegation(FilterDelegationHandle handle, BackendExecutionContext backendContext) {
114+
throw new UnsupportedOperationException("configureFilterDelegation not implemented for [" + name() + "]");
115+
}
84116
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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.analytics.spi;
10+
11+
import org.opensearch.core.common.io.stream.StreamInput;
12+
import org.opensearch.core.common.io.stream.StreamOutput;
13+
import org.opensearch.core.common.io.stream.Writeable;
14+
15+
import java.io.IOException;
16+
import java.util.ArrayList;
17+
import java.util.List;
18+
19+
/**
20+
* Describes the delegation metadata for a plan alternative. Carried on the wire
21+
* alongside the instruction list so that Core can orchestrate the handle exchange
22+
* between accepting and driving backends at the data node.
23+
*
24+
* @opensearch.internal
25+
*/
26+
public record DelegationDescriptor(
27+
FilterTreeShape treeShape,
28+
int delegatedPredicateCount,
29+
List<DelegatedExpression> delegatedExpressions
30+
) implements Writeable {
31+
32+
public DelegationDescriptor(StreamInput in) throws IOException {
33+
this(
34+
in.readEnum(FilterTreeShape.class),
35+
in.readVInt(),
36+
readExpressions(in)
37+
);
38+
}
39+
40+
@Override
41+
public void writeTo(StreamOutput out) throws IOException {
42+
out.writeEnum(treeShape);
43+
out.writeVInt(delegatedPredicateCount);
44+
out.writeVInt(delegatedExpressions.size());
45+
for (DelegatedExpression expr : delegatedExpressions) {
46+
expr.writeTo(out);
47+
}
48+
}
49+
50+
private static List<DelegatedExpression> readExpressions(StreamInput in) throws IOException {
51+
int count = in.readVInt();
52+
List<DelegatedExpression> expressions = new ArrayList<>(count);
53+
for (int i = 0; i < count; i++) {
54+
expressions.add(new DelegatedExpression(in));
55+
}
56+
return expressions;
57+
}
58+
}
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.analytics.spi;
10+
11+
import java.io.Closeable;
12+
import java.lang.foreign.MemorySegment;
13+
14+
/**
15+
* Callback surface for filter delegation between a driving backend and an accepting backend.
16+
*
17+
* <p>One handle per query per shard. The accepting backend implements this interface;
18+
* the driving backend calls into it via FFM upcalls during execution. Core closes it
19+
* after execution completes.
20+
*
21+
* <p>Lifecycle:
22+
* <ol>
23+
* <li>Rust calls {@link #createProvider(int)} once per delegated predicate (per annotationId)</li>
24+
* <li>Rust calls {@link #createCollector(int, int, int, int)} per (provider × segment)</li>
25+
* <li>Rust calls {@link #collectDocs(int, int, int, MemorySegment)} per row group</li>
26+
* <li>Rust calls {@link #releaseCollector(int)} when done with a segment</li>
27+
* <li>Rust calls {@link #releaseProvider(int)} when the query ends</li>
28+
* </ol>
29+
*
30+
* @opensearch.internal
31+
*/
32+
public interface FilterDelegationHandle extends Closeable {
33+
34+
/**
35+
* Create a provider for the given annotation ID. The accepting backend looks up
36+
* the pre-compiled query for this annotation and prepares it for segment iteration.
37+
*
38+
* @param annotationId the annotation ID identifying the delegated predicate
39+
* @return a provider key {@code >= 0}, or {@code -1} on failure
40+
*/
41+
int createProvider(int annotationId);
42+
43+
/**
44+
* Create a collector for one (segment, [minDoc, maxDoc)) range.
45+
*
46+
* @param providerKey key returned by {@link #createProvider(int)}
47+
* @param segmentOrd the segment ordinal
48+
* @param minDoc inclusive lower bound
49+
* @param maxDoc exclusive upper bound
50+
* @return a collector key {@code >= 0}, or {@code -1} on failure
51+
*/
52+
int createCollector(int providerKey, int segmentOrd, int minDoc, int maxDoc);
53+
54+
/**
55+
* Fill {@code out} with the matching doc-id bitset for the given collector.
56+
*
57+
* <p>Bit layout: word {@code i} contains matches for docs
58+
* {@code [minDoc + i*64, minDoc + (i+1)*64)}, LSB-first within each word.
59+
*
60+
* @param collectorKey key returned by {@link #createCollector(int, int, int, int)}
61+
* @param minDoc inclusive lower bound
62+
* @param maxDoc exclusive upper bound
63+
* @param out destination buffer; implementation writes up to {@code out.byteSize() / 8} words
64+
* @return number of words written, or {@code -1} on error
65+
*/
66+
int collectDocs(int collectorKey, int minDoc, int maxDoc, MemorySegment out);
67+
68+
/**
69+
* Release resources for a collector.
70+
*/
71+
void releaseCollector(int collectorKey);
72+
73+
/**
74+
* Release resources for a provider.
75+
*/
76+
void releaseProvider(int providerKey);
77+
}

sandbox/libs/analytics-framework/src/main/java/org/opensearch/analytics/spi/FilterDelegationInstructionNode.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public FilterDelegationInstructionNode(StreamInput in) throws IOException {
4444

4545
@Override
4646
public InstructionType type() {
47-
return InstructionType.SETUP_FILTER_DELEGATION_FOR_INDEX;
47+
return InstructionType.SETUP_SHARD_SCAN_WITH_DELEGATION;
4848
}
4949

5050
@Override

sandbox/libs/analytics-framework/src/main/java/org/opensearch/analytics/spi/InstructionType.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,14 @@ public enum InstructionType {
2323
/** Base scan setup — reader acquisition, SessionContext creation, default table provider. */
2424
SETUP_SHARD_SCAN,
2525
/**
26-
* Filter delegation to an index backend — bridge setup, UDF registration, IndexedTableProvider.
26+
* Filter delegation to an index backend — bridge setup, UDF registration, custom scan operator.
2727
*
2828
* <p>TODO: add a DelegationStrategy field (BACKEND_DRIVEN vs CENTRALLY_DRIVEN) to the
2929
* instruction node when centrally-driven delegation is implemented. Currently only
3030
* BACKEND_DRIVEN exists — derived from the backend declaring
3131
* {@code supportedDelegations(DelegationType.FILTER)}.
3232
*/
33-
SETUP_FILTER_DELEGATION_FOR_INDEX,
33+
SETUP_SHARD_SCAN_WITH_DELEGATION,
3434
/** Partial aggregate mode — disable combine optimizer, cut plan to partial-only. */
3535
SETUP_PARTIAL_AGGREGATE,
3636
/** Final aggregate for coordinator reduce — ExchangeSink path, final-only agg. */
@@ -40,7 +40,7 @@ public enum InstructionType {
4040
public InstructionNode readNode(StreamInput in) throws IOException {
4141
return switch (this) {
4242
case SETUP_SHARD_SCAN -> new ShardScanInstructionNode(in);
43-
case SETUP_FILTER_DELEGATION_FOR_INDEX -> new FilterDelegationInstructionNode(in);
43+
case SETUP_SHARD_SCAN_WITH_DELEGATION -> new ShardScanWithDelegationInstructionNode(in);
4444
case SETUP_PARTIAL_AGGREGATE -> new PartialAggregateInstructionNode(in);
4545
case SETUP_FINAL_AGGREGATE -> new FinalAggregateInstructionNode(in);
4646
};
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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.analytics.spi;
10+
11+
import org.opensearch.core.common.io.stream.StreamInput;
12+
import org.opensearch.core.common.io.stream.StreamOutput;
13+
14+
import java.io.IOException;
15+
16+
/**
17+
* Instruction node for shard scan with filter delegation — extends base shard scan
18+
* with {@link FilterTreeShape} and delegated predicate count so the driving backend
19+
* can configure its indexed execution path (UDF registration, IndexedTableProvider)
20+
* in a single FFM call.
21+
*
22+
* @opensearch.internal
23+
*/
24+
public class ShardScanWithDelegationInstructionNode extends ShardScanInstructionNode {
25+
26+
private final FilterTreeShape treeShape;
27+
private final int delegatedPredicateCount;
28+
29+
public ShardScanWithDelegationInstructionNode(FilterTreeShape treeShape, int delegatedPredicateCount) {
30+
this.treeShape = treeShape;
31+
this.delegatedPredicateCount = delegatedPredicateCount;
32+
}
33+
34+
public ShardScanWithDelegationInstructionNode(StreamInput in) throws IOException {
35+
super(in);
36+
this.treeShape = in.readEnum(FilterTreeShape.class);
37+
this.delegatedPredicateCount = in.readVInt();
38+
}
39+
40+
@Override
41+
public InstructionType type() {
42+
return InstructionType.SETUP_SHARD_SCAN_WITH_DELEGATION;
43+
}
44+
45+
@Override
46+
public void writeTo(StreamOutput out) throws IOException {
47+
super.writeTo(out);
48+
out.writeEnum(treeShape);
49+
out.writeVInt(delegatedPredicateCount);
50+
}
51+
52+
public FilterTreeShape getTreeShape() {
53+
return treeShape;
54+
}
55+
56+
public int getDelegatedPredicateCount() {
57+
return delegatedPredicateCount;
58+
}
59+
}

sandbox/plugins/analytics-backend-datafusion/rust/src/ffm.rs

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,27 @@ pub unsafe extern "C" fn df_create_session_context(
476476
.map_err(|e| e.to_string())
477477
}
478478

479+
#[ffm_safe]
480+
#[no_mangle]
481+
pub unsafe extern "C" fn df_create_session_context_indexed(
482+
shard_view_ptr: i64,
483+
runtime_ptr: i64,
484+
table_name_ptr: *const u8,
485+
table_name_len: i64,
486+
context_id: i64,
487+
tree_shape: i32,
488+
delegated_predicate_count: i32,
489+
) -> i64 {
490+
let table_name = str_from_raw(table_name_ptr, table_name_len)
491+
.map_err(|e| format!("df_create_session_context_indexed: {}", e))?;
492+
let mgr = get_rt_manager()?;
493+
mgr.io_runtime
494+
.block_on(crate::session_context::create_session_context_indexed(
495+
runtime_ptr, shard_view_ptr, table_name, context_id, tree_shape, delegated_predicate_count,
496+
))
497+
.map_err(|e| e.to_string())
498+
}
499+
479500
#[ffm_safe]
480501
#[no_mangle]
481502
pub unsafe extern "C" fn df_cache_manager_remove_files(
@@ -618,11 +639,24 @@ pub unsafe extern "C" fn df_execute_with_context(
618639
let mgr = get_rt_manager()?;
619640
let plan_bytes = slice::from_raw_parts(plan_ptr, plan_len as usize);
620641
let cpu_executor = mgr.cpu_executor();
621-
mgr.io_runtime
622-
.block_on(crate::query_executor::execute_with_context(
623-
session_handle,
624-
plan_bytes,
625-
cpu_executor,
626-
))
627-
.map_err(|e| e.to_string())
642+
643+
// Route based on whether the session was configured for indexed execution
644+
let handle_ref = &*(session_ctx_ptr as *const crate::session_context::SessionContextHandle);
645+
if handle_ref.indexed_config.is_some() {
646+
mgr.io_runtime
647+
.block_on(crate::indexed_executor::execute_indexed_with_context(
648+
session_ctx_ptr,
649+
plan_bytes.to_vec(),
650+
cpu_executor,
651+
))
652+
.map_err(|e| e.to_string())
653+
} else {
654+
mgr.io_runtime
655+
.block_on(crate::query_executor::execute_with_context(
656+
session_handle,
657+
plan_bytes,
658+
cpu_executor,
659+
))
660+
.map_err(|e| e.to_string())
661+
}
628662
}

0 commit comments

Comments
 (0)