|  | @@ -8,116 +8,39 @@
 | 
	
		
			
				|  |  |  package org.elasticsearch.xpack.esql.enrich;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  import org.elasticsearch.TransportVersions;
 | 
	
		
			
				|  |  | -import org.elasticsearch.action.ActionListener;
 | 
	
		
			
				|  |  | -import org.elasticsearch.action.ActionListenerResponseHandler;
 | 
	
		
			
				|  |  | -import org.elasticsearch.action.IndicesRequest;
 | 
	
		
			
				|  |  | -import org.elasticsearch.action.UnavailableShardsException;
 | 
	
		
			
				|  |  | -import org.elasticsearch.action.support.ChannelActionListener;
 | 
	
		
			
				|  |  | -import org.elasticsearch.action.support.ContextPreservingActionListener;
 | 
	
		
			
				|  |  | -import org.elasticsearch.action.support.IndicesOptions;
 | 
	
		
			
				|  |  | -import org.elasticsearch.cluster.ClusterState;
 | 
	
		
			
				|  |  | -import org.elasticsearch.cluster.node.DiscoveryNode;
 | 
	
		
			
				|  |  | -import org.elasticsearch.cluster.routing.GroupShardsIterator;
 | 
	
		
			
				|  |  | -import org.elasticsearch.cluster.routing.ShardIterator;
 | 
	
		
			
				|  |  | -import org.elasticsearch.cluster.routing.ShardRouting;
 | 
	
		
			
				|  |  |  import org.elasticsearch.cluster.service.ClusterService;
 | 
	
		
			
				|  |  |  import org.elasticsearch.common.io.stream.StreamInput;
 | 
	
		
			
				|  |  |  import org.elasticsearch.common.io.stream.StreamOutput;
 | 
	
		
			
				|  |  | -import org.elasticsearch.common.settings.Settings;
 | 
	
		
			
				|  |  |  import org.elasticsearch.common.util.BigArrays;
 | 
	
		
			
				|  |  | -import org.elasticsearch.common.util.concurrent.ThreadContext;
 | 
	
		
			
				|  |  |  import org.elasticsearch.compute.data.Block;
 | 
	
		
			
				|  |  |  import org.elasticsearch.compute.data.BlockFactory;
 | 
	
		
			
				|  |  |  import org.elasticsearch.compute.data.BlockStreamInput;
 | 
	
		
			
				|  |  | -import org.elasticsearch.compute.data.BytesRefBlock;
 | 
	
		
			
				|  |  | -import org.elasticsearch.compute.data.ElementType;
 | 
	
		
			
				|  |  | -import org.elasticsearch.compute.data.IntVector;
 | 
	
		
			
				|  |  | -import org.elasticsearch.compute.data.LocalCircuitBreaker;
 | 
	
		
			
				|  |  | -import org.elasticsearch.compute.data.OrdinalBytesRefBlock;
 | 
	
		
			
				|  |  |  import org.elasticsearch.compute.data.Page;
 | 
	
		
			
				|  |  | -import org.elasticsearch.compute.lucene.ValuesSourceReaderOperator;
 | 
	
		
			
				|  |  | -import org.elasticsearch.compute.operator.Driver;
 | 
	
		
			
				|  |  | -import org.elasticsearch.compute.operator.DriverContext;
 | 
	
		
			
				|  |  | -import org.elasticsearch.compute.operator.Operator;
 | 
	
		
			
				|  |  | -import org.elasticsearch.compute.operator.OutputOperator;
 | 
	
		
			
				|  |  | -import org.elasticsearch.core.AbstractRefCounted;
 | 
	
		
			
				|  |  | -import org.elasticsearch.core.RefCounted;
 | 
	
		
			
				|  |  | -import org.elasticsearch.core.Releasable;
 | 
	
		
			
				|  |  | -import org.elasticsearch.core.Releasables;
 | 
	
		
			
				|  |  | -import org.elasticsearch.index.mapper.BlockLoader;
 | 
	
		
			
				|  |  |  import org.elasticsearch.index.mapper.MappedFieldType;
 | 
	
		
			
				|  |  |  import org.elasticsearch.index.query.SearchExecutionContext;
 | 
	
		
			
				|  |  |  import org.elasticsearch.index.shard.ShardId;
 | 
	
		
			
				|  |  |  import org.elasticsearch.search.SearchService;
 | 
	
		
			
				|  |  | -import org.elasticsearch.search.internal.AliasFilter;
 | 
	
		
			
				|  |  | -import org.elasticsearch.search.internal.SearchContext;
 | 
	
		
			
				|  |  | -import org.elasticsearch.search.internal.ShardSearchRequest;
 | 
	
		
			
				|  |  | -import org.elasticsearch.tasks.CancellableTask;
 | 
	
		
			
				|  |  | -import org.elasticsearch.tasks.Task;
 | 
	
		
			
				|  |  |  import org.elasticsearch.tasks.TaskId;
 | 
	
		
			
				|  |  | -import org.elasticsearch.threadpool.ThreadPool;
 | 
	
		
			
				|  |  | -import org.elasticsearch.transport.TransportChannel;
 | 
	
		
			
				|  |  | -import org.elasticsearch.transport.TransportRequest;
 | 
	
		
			
				|  |  | -import org.elasticsearch.transport.TransportRequestHandler;
 | 
	
		
			
				|  |  | -import org.elasticsearch.transport.TransportRequestOptions;
 | 
	
		
			
				|  |  | -import org.elasticsearch.transport.TransportResponse;
 | 
	
		
			
				|  |  |  import org.elasticsearch.transport.TransportService;
 | 
	
		
			
				|  |  | -import org.elasticsearch.xpack.core.ClientHelper;
 | 
	
		
			
				|  |  | -import org.elasticsearch.xpack.core.XPackSettings;
 | 
	
		
			
				|  |  | -import org.elasticsearch.xpack.core.security.SecurityContext;
 | 
	
		
			
				|  |  | -import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesAction;
 | 
	
		
			
				|  |  | -import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesRequest;
 | 
	
		
			
				|  |  | -import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesResponse;
 | 
	
		
			
				|  |  | -import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
 | 
	
		
			
				|  |  |  import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilegeResolver;
 | 
	
		
			
				|  |  | -import org.elasticsearch.xpack.core.security.support.Exceptions;
 | 
	
		
			
				|  |  | -import org.elasticsearch.xpack.core.security.user.User;
 | 
	
		
			
				|  |  |  import org.elasticsearch.xpack.esql.EsqlIllegalArgumentException;
 | 
	
		
			
				|  |  |  import org.elasticsearch.xpack.esql.action.EsqlQueryAction;
 | 
	
		
			
				|  |  | -import org.elasticsearch.xpack.esql.core.expression.Alias;
 | 
	
		
			
				|  |  |  import org.elasticsearch.xpack.esql.core.expression.NamedExpression;
 | 
	
		
			
				|  |  |  import org.elasticsearch.xpack.esql.core.type.DataType;
 | 
	
		
			
				|  |  |  import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput;
 | 
	
		
			
				|  |  |  import org.elasticsearch.xpack.esql.io.stream.PlanStreamOutput;
 | 
	
		
			
				|  |  | -import org.elasticsearch.xpack.esql.planner.EsPhysicalOperationProviders;
 | 
	
		
			
				|  |  | -import org.elasticsearch.xpack.esql.planner.PlannerUtils;
 | 
	
		
			
				|  |  | -import org.elasticsearch.xpack.esql.plugin.EsqlPlugin;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  import java.io.IOException;
 | 
	
		
			
				|  |  | -import java.util.ArrayList;
 | 
	
		
			
				|  |  |  import java.util.List;
 | 
	
		
			
				|  |  | -import java.util.Map;
 | 
	
		
			
				|  |  | -import java.util.Objects;
 | 
	
		
			
				|  |  | -import java.util.concurrent.Executor;
 | 
	
		
			
				|  |  | -import java.util.concurrent.atomic.AtomicReference;
 | 
	
		
			
				|  |  | -import java.util.function.Function;
 | 
	
		
			
				|  |  | -import java.util.stream.Collectors;
 | 
	
		
			
				|  |  | -import java.util.stream.IntStream;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  /**
 | 
	
		
			
				|  |  | - * {@link EnrichLookupService} performs enrich lookup for a given input page. The lookup process consists of three stages:
 | 
	
		
			
				|  |  | - * - Stage 1: Finding matching document IDs for the input page. This stage is done by the {@link EnrichQuerySourceOperator} or its variants.
 | 
	
		
			
				|  |  | - * The output page of this stage is represented as [DocVector, IntBlock: positions of the input terms].
 | 
	
		
			
				|  |  | - * <p>
 | 
	
		
			
				|  |  | - * - Stage 2: Extracting field values for the matched document IDs. The output page is represented as
 | 
	
		
			
				|  |  | - * [DocVector, IntBlock: positions, Block: field1, Block: field2,...].
 | 
	
		
			
				|  |  | - * <p>
 | 
	
		
			
				|  |  | - * - Stage 3: Combining the extracted values based on positions and filling nulls for positions without matches.
 | 
	
		
			
				|  |  | - * This is done by {@link MergePositionsOperator}. The output page is represented as [Block: field1, Block: field2,...].
 | 
	
		
			
				|  |  | - * <p>
 | 
	
		
			
				|  |  | - * The positionCount of the output page must be equal to the positionCount of the input page.
 | 
	
		
			
				|  |  | + * {@link EnrichLookupService} performs enrich lookup for a given input page.
 | 
	
		
			
				|  |  | + * See {@link AbstractLookupService} for how it works where it refers to this
 | 
	
		
			
				|  |  | + * process as a {@code LEFT JOIN}. Which is mostly is.
 | 
	
		
			
				|  |  |   */
 | 
	
		
			
				|  |  | -public class EnrichLookupService {
 | 
	
		
			
				|  |  | +public class EnrichLookupService extends AbstractLookupService<EnrichLookupService.Request, EnrichLookupService.TransportRequest> {
 | 
	
		
			
				|  |  |      public static final String LOOKUP_ACTION_NAME = EsqlQueryAction.NAME + "/lookup";
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    private final ClusterService clusterService;
 | 
	
		
			
				|  |  | -    private final SearchService searchService;
 | 
	
		
			
				|  |  | -    private final TransportService transportService;
 | 
	
		
			
				|  |  | -    private final Executor executor;
 | 
	
		
			
				|  |  | -    private final BigArrays bigArrays;
 | 
	
		
			
				|  |  | -    private final BlockFactory blockFactory;
 | 
	
		
			
				|  |  | -    private final LocalCircuitBreaker.SizeSettings localBreakerSettings;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |      public EnrichLookupService(
 | 
	
		
			
				|  |  |          ClusterService clusterService,
 | 
	
		
			
				|  |  |          SearchService searchService,
 | 
	
	
		
			
				|  | @@ -125,353 +48,107 @@ public class EnrichLookupService {
 | 
	
		
			
				|  |  |          BigArrays bigArrays,
 | 
	
		
			
				|  |  |          BlockFactory blockFactory
 | 
	
		
			
				|  |  |      ) {
 | 
	
		
			
				|  |  | -        this.clusterService = clusterService;
 | 
	
		
			
				|  |  | -        this.searchService = searchService;
 | 
	
		
			
				|  |  | -        this.transportService = transportService;
 | 
	
		
			
				|  |  | -        this.executor = transportService.getThreadPool().executor(ThreadPool.Names.SEARCH);
 | 
	
		
			
				|  |  | -        this.bigArrays = bigArrays;
 | 
	
		
			
				|  |  | -        this.blockFactory = blockFactory;
 | 
	
		
			
				|  |  | -        this.localBreakerSettings = new LocalCircuitBreaker.SizeSettings(clusterService.getSettings());
 | 
	
		
			
				|  |  | -        transportService.registerRequestHandler(
 | 
	
		
			
				|  |  | +        super(
 | 
	
		
			
				|  |  |              LOOKUP_ACTION_NAME,
 | 
	
		
			
				|  |  | -            transportService.getThreadPool().executor(EsqlPlugin.ESQL_WORKER_THREAD_POOL_NAME),
 | 
	
		
			
				|  |  | -            in -> new LookupRequest(in, blockFactory),
 | 
	
		
			
				|  |  | -            new TransportHandler()
 | 
	
		
			
				|  |  | +            ClusterPrivilegeResolver.MONITOR_ENRICH.name(),
 | 
	
		
			
				|  |  | +            clusterService,
 | 
	
		
			
				|  |  | +            searchService,
 | 
	
		
			
				|  |  | +            transportService,
 | 
	
		
			
				|  |  | +            bigArrays,
 | 
	
		
			
				|  |  | +            blockFactory,
 | 
	
		
			
				|  |  | +            TransportRequest::readFrom
 | 
	
		
			
				|  |  |          );
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    public void lookupAsync(
 | 
	
		
			
				|  |  | -        String sessionId,
 | 
	
		
			
				|  |  | -        CancellableTask parentTask,
 | 
	
		
			
				|  |  | -        String index,
 | 
	
		
			
				|  |  | -        DataType inputDataType,
 | 
	
		
			
				|  |  | -        String matchType,
 | 
	
		
			
				|  |  | -        String matchField,
 | 
	
		
			
				|  |  | -        List<NamedExpression> extractFields,
 | 
	
		
			
				|  |  | -        Page inputPage,
 | 
	
		
			
				|  |  | -        ActionListener<Page> outListener
 | 
	
		
			
				|  |  | -    ) {
 | 
	
		
			
				|  |  | -        ThreadContext threadContext = transportService.getThreadPool().getThreadContext();
 | 
	
		
			
				|  |  | -        ActionListener<Page> listener = ContextPreservingActionListener.wrapPreservingContext(outListener, threadContext);
 | 
	
		
			
				|  |  | -        hasEnrichPrivilege(listener.delegateFailureAndWrap((delegate, ignored) -> {
 | 
	
		
			
				|  |  | -            ClusterState clusterState = clusterService.state();
 | 
	
		
			
				|  |  | -            GroupShardsIterator<ShardIterator> shardIterators = clusterService.operationRouting()
 | 
	
		
			
				|  |  | -                .searchShards(clusterState, new String[] { index }, Map.of(), "_local");
 | 
	
		
			
				|  |  | -            if (shardIterators.size() != 1) {
 | 
	
		
			
				|  |  | -                delegate.onFailure(new EsqlIllegalArgumentException("target index {} has more than one shard", index));
 | 
	
		
			
				|  |  | -                return;
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -            ShardIterator shardIt = shardIterators.get(0);
 | 
	
		
			
				|  |  | -            ShardRouting shardRouting = shardIt.nextOrNull();
 | 
	
		
			
				|  |  | -            ShardId shardId = shardIt.shardId();
 | 
	
		
			
				|  |  | -            if (shardRouting == null) {
 | 
	
		
			
				|  |  | -                delegate.onFailure(new UnavailableShardsException(shardId, "enrich index is not available"));
 | 
	
		
			
				|  |  | -                return;
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -            DiscoveryNode targetNode = clusterState.nodes().get(shardRouting.currentNodeId());
 | 
	
		
			
				|  |  | -            var lookupRequest = new LookupRequest(sessionId, shardId, inputDataType, matchType, matchField, inputPage, extractFields);
 | 
	
		
			
				|  |  | -            // TODO: handle retry and avoid forking for the local lookup
 | 
	
		
			
				|  |  | -            try (ThreadContext.StoredContext unused = threadContext.stashWithOrigin(ClientHelper.ENRICH_ORIGIN)) {
 | 
	
		
			
				|  |  | -                transportService.sendChildRequest(
 | 
	
		
			
				|  |  | -                    targetNode,
 | 
	
		
			
				|  |  | -                    LOOKUP_ACTION_NAME,
 | 
	
		
			
				|  |  | -                    lookupRequest,
 | 
	
		
			
				|  |  | -                    parentTask,
 | 
	
		
			
				|  |  | -                    TransportRequestOptions.EMPTY,
 | 
	
		
			
				|  |  | -                    new ActionListenerResponseHandler<>(
 | 
	
		
			
				|  |  | -                        delegate.map(LookupResponse::takePage),
 | 
	
		
			
				|  |  | -                        in -> new LookupResponse(in, blockFactory),
 | 
	
		
			
				|  |  | -                        executor
 | 
	
		
			
				|  |  | -                    )
 | 
	
		
			
				|  |  | -                );
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -        }));
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    private void hasEnrichPrivilege(ActionListener<Void> outListener) {
 | 
	
		
			
				|  |  | -        final Settings settings = clusterService.getSettings();
 | 
	
		
			
				|  |  | -        if (settings.hasValue(XPackSettings.SECURITY_ENABLED.getKey()) == false || XPackSettings.SECURITY_ENABLED.get(settings) == false) {
 | 
	
		
			
				|  |  | -            outListener.onResponse(null);
 | 
	
		
			
				|  |  | -            return;
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -        final ThreadContext threadContext = transportService.getThreadPool().getThreadContext();
 | 
	
		
			
				|  |  | -        final SecurityContext securityContext = new SecurityContext(Settings.EMPTY, threadContext);
 | 
	
		
			
				|  |  | -        final User user = securityContext.getUser();
 | 
	
		
			
				|  |  | -        if (user == null) {
 | 
	
		
			
				|  |  | -            outListener.onFailure(new IllegalStateException("missing or unable to read authentication info on request"));
 | 
	
		
			
				|  |  | -            return;
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -        HasPrivilegesRequest request = new HasPrivilegesRequest();
 | 
	
		
			
				|  |  | -        request.username(user.principal());
 | 
	
		
			
				|  |  | -        request.clusterPrivileges(ClusterPrivilegeResolver.MONITOR_ENRICH.name());
 | 
	
		
			
				|  |  | -        request.indexPrivileges(new RoleDescriptor.IndicesPrivileges[0]);
 | 
	
		
			
				|  |  | -        request.applicationPrivileges(new RoleDescriptor.ApplicationResourcePrivileges[0]);
 | 
	
		
			
				|  |  | -        ActionListener<HasPrivilegesResponse> listener = outListener.delegateFailureAndWrap((l, resp) -> {
 | 
	
		
			
				|  |  | -            if (resp.isCompleteMatch()) {
 | 
	
		
			
				|  |  | -                l.onResponse(null);
 | 
	
		
			
				|  |  | -                return;
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -            String detailed = resp.getClusterPrivileges()
 | 
	
		
			
				|  |  | -                .entrySet()
 | 
	
		
			
				|  |  | -                .stream()
 | 
	
		
			
				|  |  | -                .filter(e -> e.getValue() == false)
 | 
	
		
			
				|  |  | -                .map(e -> "privilege [" + e.getKey() + "] is missing")
 | 
	
		
			
				|  |  | -                .collect(Collectors.joining(", "));
 | 
	
		
			
				|  |  | -            String message = "user ["
 | 
	
		
			
				|  |  | -                + user.principal()
 | 
	
		
			
				|  |  | -                + "] doesn't have "
 | 
	
		
			
				|  |  | -                + "sufficient privileges to perform enrich lookup: "
 | 
	
		
			
				|  |  | -                + detailed;
 | 
	
		
			
				|  |  | -            l.onFailure(Exceptions.authorizationError(message));
 | 
	
		
			
				|  |  | -        });
 | 
	
		
			
				|  |  | -        transportService.sendRequest(
 | 
	
		
			
				|  |  | -            transportService.getLocalNode(),
 | 
	
		
			
				|  |  | -            HasPrivilegesAction.NAME,
 | 
	
		
			
				|  |  | -            request,
 | 
	
		
			
				|  |  | -            TransportRequestOptions.EMPTY,
 | 
	
		
			
				|  |  | -            new ActionListenerResponseHandler<>(listener, HasPrivilegesResponse::new, executor)
 | 
	
		
			
				|  |  | +    @Override
 | 
	
		
			
				|  |  | +    protected TransportRequest transportRequest(EnrichLookupService.Request request, ShardId shardId) {
 | 
	
		
			
				|  |  | +        return new TransportRequest(
 | 
	
		
			
				|  |  | +            request.sessionId,
 | 
	
		
			
				|  |  | +            shardId,
 | 
	
		
			
				|  |  | +            request.inputDataType,
 | 
	
		
			
				|  |  | +            request.matchType,
 | 
	
		
			
				|  |  | +            request.matchField,
 | 
	
		
			
				|  |  | +            request.inputPage,
 | 
	
		
			
				|  |  | +            null,
 | 
	
		
			
				|  |  | +            request.extractFields
 | 
	
		
			
				|  |  |          );
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    private void doLookup(
 | 
	
		
			
				|  |  | -        String sessionId,
 | 
	
		
			
				|  |  | -        CancellableTask task,
 | 
	
		
			
				|  |  | -        ShardId shardId,
 | 
	
		
			
				|  |  | -        DataType inputDataType,
 | 
	
		
			
				|  |  | -        String matchType,
 | 
	
		
			
				|  |  | -        String matchField,
 | 
	
		
			
				|  |  | -        Page inputPage,
 | 
	
		
			
				|  |  | -        List<NamedExpression> extractFields,
 | 
	
		
			
				|  |  | -        ActionListener<Page> listener
 | 
	
		
			
				|  |  | -    ) {
 | 
	
		
			
				|  |  | -        Block inputBlock = inputPage.getBlock(0);
 | 
	
		
			
				|  |  | -        if (inputBlock.areAllValuesNull()) {
 | 
	
		
			
				|  |  | -            listener.onResponse(createNullResponse(inputPage.getPositionCount(), extractFields));
 | 
	
		
			
				|  |  | -            return;
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -        final List<Releasable> releasables = new ArrayList<>(6);
 | 
	
		
			
				|  |  | -        boolean started = false;
 | 
	
		
			
				|  |  | -        try {
 | 
	
		
			
				|  |  | -            final ShardSearchRequest shardSearchRequest = new ShardSearchRequest(shardId, 0, AliasFilter.EMPTY);
 | 
	
		
			
				|  |  | -            final SearchContext searchContext = searchService.createSearchContext(shardSearchRequest, SearchService.NO_TIMEOUT);
 | 
	
		
			
				|  |  | -            releasables.add(searchContext);
 | 
	
		
			
				|  |  | -            final LocalCircuitBreaker localBreaker = new LocalCircuitBreaker(
 | 
	
		
			
				|  |  | -                blockFactory.breaker(),
 | 
	
		
			
				|  |  | -                localBreakerSettings.overReservedBytes(),
 | 
	
		
			
				|  |  | -                localBreakerSettings.maxOverReservedBytes()
 | 
	
		
			
				|  |  | -            );
 | 
	
		
			
				|  |  | -            releasables.add(localBreaker);
 | 
	
		
			
				|  |  | -            final DriverContext driverContext = new DriverContext(bigArrays, blockFactory.newChildFactory(localBreaker));
 | 
	
		
			
				|  |  | -            final ElementType[] mergingTypes = new ElementType[extractFields.size()];
 | 
	
		
			
				|  |  | -            for (int i = 0; i < extractFields.size(); i++) {
 | 
	
		
			
				|  |  | -                mergingTypes[i] = PlannerUtils.toElementType(extractFields.get(i).dataType());
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -            final int[] mergingChannels = IntStream.range(0, extractFields.size()).map(i -> i + 2).toArray();
 | 
	
		
			
				|  |  | -            final MergePositionsOperator mergePositionsOperator;
 | 
	
		
			
				|  |  | -            final OrdinalBytesRefBlock ordinalsBytesRefBlock;
 | 
	
		
			
				|  |  | -            if (inputBlock instanceof BytesRefBlock bytesRefBlock && (ordinalsBytesRefBlock = bytesRefBlock.asOrdinals()) != null) {
 | 
	
		
			
				|  |  | -                inputBlock = ordinalsBytesRefBlock.getDictionaryVector().asBlock();
 | 
	
		
			
				|  |  | -                var selectedPositions = ordinalsBytesRefBlock.getOrdinalsBlock();
 | 
	
		
			
				|  |  | -                mergePositionsOperator = new MergePositionsOperator(
 | 
	
		
			
				|  |  | -                    1,
 | 
	
		
			
				|  |  | -                    mergingChannels,
 | 
	
		
			
				|  |  | -                    mergingTypes,
 | 
	
		
			
				|  |  | -                    selectedPositions,
 | 
	
		
			
				|  |  | -                    driverContext.blockFactory()
 | 
	
		
			
				|  |  | -                );
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            } else {
 | 
	
		
			
				|  |  | -                try (var selectedPositions = IntVector.range(0, inputBlock.getPositionCount(), blockFactory).asBlock()) {
 | 
	
		
			
				|  |  | -                    mergePositionsOperator = new MergePositionsOperator(
 | 
	
		
			
				|  |  | -                        1,
 | 
	
		
			
				|  |  | -                        mergingChannels,
 | 
	
		
			
				|  |  | -                        mergingTypes,
 | 
	
		
			
				|  |  | -                        selectedPositions,
 | 
	
		
			
				|  |  | -                        driverContext.blockFactory()
 | 
	
		
			
				|  |  | -                    );
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -            releasables.add(mergePositionsOperator);
 | 
	
		
			
				|  |  | -            SearchExecutionContext searchExecutionContext = searchContext.getSearchExecutionContext();
 | 
	
		
			
				|  |  | -            MappedFieldType fieldType = searchExecutionContext.getFieldType(matchField);
 | 
	
		
			
				|  |  | -            var queryList = switch (matchType) {
 | 
	
		
			
				|  |  | -                case "match", "range" -> QueryList.termQueryList(fieldType, searchExecutionContext, inputBlock, inputDataType);
 | 
	
		
			
				|  |  | -                case "geo_match" -> QueryList.geoShapeQuery(fieldType, searchExecutionContext, inputBlock, inputDataType);
 | 
	
		
			
				|  |  | -                default -> throw new EsqlIllegalArgumentException("illegal match type " + matchType);
 | 
	
		
			
				|  |  | -            };
 | 
	
		
			
				|  |  | -            var queryOperator = new EnrichQuerySourceOperator(
 | 
	
		
			
				|  |  | -                driverContext.blockFactory(),
 | 
	
		
			
				|  |  | -                EnrichQuerySourceOperator.DEFAULT_MAX_PAGE_SIZE,
 | 
	
		
			
				|  |  | -                queryList,
 | 
	
		
			
				|  |  | -                searchExecutionContext.getIndexReader()
 | 
	
		
			
				|  |  | -            );
 | 
	
		
			
				|  |  | -            releasables.add(queryOperator);
 | 
	
		
			
				|  |  | -            var extractFieldsOperator = extractFieldsOperator(searchContext, driverContext, extractFields);
 | 
	
		
			
				|  |  | -            releasables.add(extractFieldsOperator);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            AtomicReference<Page> result = new AtomicReference<>();
 | 
	
		
			
				|  |  | -            OutputOperator outputOperator = new OutputOperator(List.of(), Function.identity(), result::set);
 | 
	
		
			
				|  |  | -            releasables.add(outputOperator);
 | 
	
		
			
				|  |  | -            Driver driver = new Driver(
 | 
	
		
			
				|  |  | -                "enrich-lookup:" + sessionId,
 | 
	
		
			
				|  |  | -                System.currentTimeMillis(),
 | 
	
		
			
				|  |  | -                System.nanoTime(),
 | 
	
		
			
				|  |  | -                driverContext,
 | 
	
		
			
				|  |  | -                () -> lookupDescription(
 | 
	
		
			
				|  |  | -                    sessionId,
 | 
	
		
			
				|  |  | -                    shardId,
 | 
	
		
			
				|  |  | -                    inputDataType,
 | 
	
		
			
				|  |  | -                    matchType,
 | 
	
		
			
				|  |  | -                    matchField,
 | 
	
		
			
				|  |  | -                    extractFields,
 | 
	
		
			
				|  |  | -                    inputPage.getPositionCount()
 | 
	
		
			
				|  |  | -                ),
 | 
	
		
			
				|  |  | -                queryOperator,
 | 
	
		
			
				|  |  | -                List.of(extractFieldsOperator, mergePositionsOperator),
 | 
	
		
			
				|  |  | -                outputOperator,
 | 
	
		
			
				|  |  | -                Driver.DEFAULT_STATUS_INTERVAL,
 | 
	
		
			
				|  |  | -                Releasables.wrap(searchContext, localBreaker)
 | 
	
		
			
				|  |  | -            );
 | 
	
		
			
				|  |  | -            task.addListener(() -> {
 | 
	
		
			
				|  |  | -                String reason = Objects.requireNonNullElse(task.getReasonCancelled(), "task was cancelled");
 | 
	
		
			
				|  |  | -                driver.cancel(reason);
 | 
	
		
			
				|  |  | -            });
 | 
	
		
			
				|  |  | -            var threadContext = transportService.getThreadPool().getThreadContext();
 | 
	
		
			
				|  |  | -            Driver.start(threadContext, executor, driver, Driver.DEFAULT_MAX_ITERATIONS, listener.map(ignored -> {
 | 
	
		
			
				|  |  | -                Page out = result.get();
 | 
	
		
			
				|  |  | -                if (out == null) {
 | 
	
		
			
				|  |  | -                    out = createNullResponse(inputPage.getPositionCount(), extractFields);
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | -                return out;
 | 
	
		
			
				|  |  | -            }));
 | 
	
		
			
				|  |  | -            started = true;
 | 
	
		
			
				|  |  | -        } catch (Exception e) {
 | 
	
		
			
				|  |  | -            listener.onFailure(e);
 | 
	
		
			
				|  |  | -        } finally {
 | 
	
		
			
				|  |  | -            if (started == false) {
 | 
	
		
			
				|  |  | -                Releasables.close(releasables);
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | +    @Override
 | 
	
		
			
				|  |  | +    protected QueryList queryList(TransportRequest request, SearchExecutionContext context, Block inputBlock, DataType inputDataType) {
 | 
	
		
			
				|  |  | +        MappedFieldType fieldType = context.getFieldType(request.matchField);
 | 
	
		
			
				|  |  | +        return switch (request.matchType) {
 | 
	
		
			
				|  |  | +            case "match", "range" -> QueryList.termQueryList(fieldType, context, inputBlock, inputDataType);
 | 
	
		
			
				|  |  | +            case "geo_match" -> QueryList.geoShapeQuery(fieldType, context, inputBlock, inputDataType);
 | 
	
		
			
				|  |  | +            default -> throw new EsqlIllegalArgumentException("illegal match type " + request.matchType);
 | 
	
		
			
				|  |  | +        };
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    private static Operator extractFieldsOperator(
 | 
	
		
			
				|  |  | -        SearchContext searchContext,
 | 
	
		
			
				|  |  | -        DriverContext driverContext,
 | 
	
		
			
				|  |  | -        List<NamedExpression> extractFields
 | 
	
		
			
				|  |  | -    ) {
 | 
	
		
			
				|  |  | -        EsPhysicalOperationProviders.ShardContext shardContext = new EsPhysicalOperationProviders.DefaultShardContext(
 | 
	
		
			
				|  |  | -            0,
 | 
	
		
			
				|  |  | -            searchContext.getSearchExecutionContext(),
 | 
	
		
			
				|  |  | -            searchContext.request().getAliasFilter()
 | 
	
		
			
				|  |  | -        );
 | 
	
		
			
				|  |  | -        List<ValuesSourceReaderOperator.FieldInfo> fields = new ArrayList<>(extractFields.size());
 | 
	
		
			
				|  |  | -        for (NamedExpression extractField : extractFields) {
 | 
	
		
			
				|  |  | -            BlockLoader loader = shardContext.blockLoader(
 | 
	
		
			
				|  |  | -                extractField instanceof Alias a ? ((NamedExpression) a.child()).name() : extractField.name(),
 | 
	
		
			
				|  |  | -                extractField.dataType() == DataType.UNSUPPORTED,
 | 
	
		
			
				|  |  | -                MappedFieldType.FieldExtractPreference.NONE
 | 
	
		
			
				|  |  | -            );
 | 
	
		
			
				|  |  | -            fields.add(
 | 
	
		
			
				|  |  | -                new ValuesSourceReaderOperator.FieldInfo(
 | 
	
		
			
				|  |  | -                    extractField.name(),
 | 
	
		
			
				|  |  | -                    PlannerUtils.toElementType(extractField.dataType()),
 | 
	
		
			
				|  |  | -                    shardIdx -> {
 | 
	
		
			
				|  |  | -                        if (shardIdx != 0) {
 | 
	
		
			
				|  |  | -                            throw new IllegalStateException("only one shard");
 | 
	
		
			
				|  |  | -                        }
 | 
	
		
			
				|  |  | -                        return loader;
 | 
	
		
			
				|  |  | -                    }
 | 
	
		
			
				|  |  | -                )
 | 
	
		
			
				|  |  | -            );
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -        return new ValuesSourceReaderOperator(
 | 
	
		
			
				|  |  | -            driverContext.blockFactory(),
 | 
	
		
			
				|  |  | -            fields,
 | 
	
		
			
				|  |  | -            List.of(new ValuesSourceReaderOperator.ShardContext(searchContext.searcher().getIndexReader(), searchContext::newSourceLoader)),
 | 
	
		
			
				|  |  | -            0
 | 
	
		
			
				|  |  | -        );
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    private Page createNullResponse(int positionCount, List<NamedExpression> extractFields) {
 | 
	
		
			
				|  |  | -        final Block[] blocks = new Block[extractFields.size()];
 | 
	
		
			
				|  |  | -        try {
 | 
	
		
			
				|  |  | -            for (int i = 0; i < extractFields.size(); i++) {
 | 
	
		
			
				|  |  | -                blocks[i] = blockFactory.newConstantNullBlock(positionCount);
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -            return new Page(blocks);
 | 
	
		
			
				|  |  | -        } finally {
 | 
	
		
			
				|  |  | -            if (blocks[blocks.length - 1] == null) {
 | 
	
		
			
				|  |  | -                Releasables.close(blocks);
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | +    public static class Request extends AbstractLookupService.Request {
 | 
	
		
			
				|  |  | +        private final String matchType;
 | 
	
		
			
				|  |  | +        private final String matchField;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    private class TransportHandler implements TransportRequestHandler<LookupRequest> {
 | 
	
		
			
				|  |  | -        @Override
 | 
	
		
			
				|  |  | -        public void messageReceived(LookupRequest request, TransportChannel channel, Task task) {
 | 
	
		
			
				|  |  | -            request.incRef();
 | 
	
		
			
				|  |  | -            ActionListener<LookupResponse> listener = ActionListener.runBefore(new ChannelActionListener<>(channel), request::decRef);
 | 
	
		
			
				|  |  | -            doLookup(
 | 
	
		
			
				|  |  | -                request.sessionId,
 | 
	
		
			
				|  |  | -                (CancellableTask) task,
 | 
	
		
			
				|  |  | -                request.shardId,
 | 
	
		
			
				|  |  | -                request.inputDataType,
 | 
	
		
			
				|  |  | -                request.matchType,
 | 
	
		
			
				|  |  | -                request.matchField,
 | 
	
		
			
				|  |  | -                request.inputPage,
 | 
	
		
			
				|  |  | -                request.extractFields,
 | 
	
		
			
				|  |  | -                listener.delegateFailureAndWrap(
 | 
	
		
			
				|  |  | -                    (l, outPage) -> ActionListener.respondAndRelease(l, new LookupResponse(outPage, blockFactory))
 | 
	
		
			
				|  |  | -                )
 | 
	
		
			
				|  |  | -            );
 | 
	
		
			
				|  |  | +        Request(
 | 
	
		
			
				|  |  | +            String sessionId,
 | 
	
		
			
				|  |  | +            String index,
 | 
	
		
			
				|  |  | +            DataType inputDataType,
 | 
	
		
			
				|  |  | +            String matchType,
 | 
	
		
			
				|  |  | +            String matchField,
 | 
	
		
			
				|  |  | +            Page inputPage,
 | 
	
		
			
				|  |  | +            List<NamedExpression> extractFields
 | 
	
		
			
				|  |  | +        ) {
 | 
	
		
			
				|  |  | +            super(sessionId, index, inputDataType, inputPage, extractFields);
 | 
	
		
			
				|  |  | +            this.matchType = matchType;
 | 
	
		
			
				|  |  | +            this.matchField = matchField;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    private static class LookupRequest extends TransportRequest implements IndicesRequest {
 | 
	
		
			
				|  |  | -        private final String sessionId;
 | 
	
		
			
				|  |  | -        private final ShardId shardId;
 | 
	
		
			
				|  |  | -        private final DataType inputDataType;
 | 
	
		
			
				|  |  | +    protected static class TransportRequest extends AbstractLookupService.TransportRequest {
 | 
	
		
			
				|  |  |          private final String matchType;
 | 
	
		
			
				|  |  |          private final String matchField;
 | 
	
		
			
				|  |  | -        private final Page inputPage;
 | 
	
		
			
				|  |  | -        private final List<NamedExpression> extractFields;
 | 
	
		
			
				|  |  | -        // TODO: Remove this workaround once we have Block RefCount
 | 
	
		
			
				|  |  | -        private final Page toRelease;
 | 
	
		
			
				|  |  | -        private final RefCounted refs = AbstractRefCounted.of(this::releasePage);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        LookupRequest(
 | 
	
		
			
				|  |  | +        TransportRequest(
 | 
	
		
			
				|  |  |              String sessionId,
 | 
	
		
			
				|  |  |              ShardId shardId,
 | 
	
		
			
				|  |  |              DataType inputDataType,
 | 
	
		
			
				|  |  |              String matchType,
 | 
	
		
			
				|  |  |              String matchField,
 | 
	
		
			
				|  |  |              Page inputPage,
 | 
	
		
			
				|  |  | +            Page toRelease,
 | 
	
		
			
				|  |  |              List<NamedExpression> extractFields
 | 
	
		
			
				|  |  |          ) {
 | 
	
		
			
				|  |  | -            this.sessionId = sessionId;
 | 
	
		
			
				|  |  | -            this.shardId = shardId;
 | 
	
		
			
				|  |  | -            this.inputDataType = inputDataType;
 | 
	
		
			
				|  |  | +            super(sessionId, shardId, inputDataType, inputPage, toRelease, extractFields);
 | 
	
		
			
				|  |  |              this.matchType = matchType;
 | 
	
		
			
				|  |  |              this.matchField = matchField;
 | 
	
		
			
				|  |  | -            this.inputPage = inputPage;
 | 
	
		
			
				|  |  | -            this.toRelease = null;
 | 
	
		
			
				|  |  | -            this.extractFields = extractFields;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        LookupRequest(StreamInput in, BlockFactory blockFactory) throws IOException {
 | 
	
		
			
				|  |  | -            super(in);
 | 
	
		
			
				|  |  | -            this.sessionId = in.readString();
 | 
	
		
			
				|  |  | -            this.shardId = new ShardId(in);
 | 
	
		
			
				|  |  | -            String inputDataType = (in.getTransportVersion().onOrAfter(TransportVersions.V_8_14_0)) ? in.readString() : "unknown";
 | 
	
		
			
				|  |  | -            this.inputDataType = DataType.fromTypeName(inputDataType);
 | 
	
		
			
				|  |  | -            this.matchType = in.readString();
 | 
	
		
			
				|  |  | -            this.matchField = in.readString();
 | 
	
		
			
				|  |  | +        static TransportRequest readFrom(StreamInput in, BlockFactory blockFactory) throws IOException {
 | 
	
		
			
				|  |  | +            TaskId parentTaskId = TaskId.readFromStream(in);
 | 
	
		
			
				|  |  | +            String sessionId = in.readString();
 | 
	
		
			
				|  |  | +            ShardId shardId = new ShardId(in);
 | 
	
		
			
				|  |  | +            DataType inputDataType = DataType.fromTypeName(
 | 
	
		
			
				|  |  | +                (in.getTransportVersion().onOrAfter(TransportVersions.V_8_14_0)) ? in.readString() : "unknown"
 | 
	
		
			
				|  |  | +            );
 | 
	
		
			
				|  |  | +            String matchType = in.readString();
 | 
	
		
			
				|  |  | +            String matchField = in.readString();
 | 
	
		
			
				|  |  | +            Page inputPage;
 | 
	
		
			
				|  |  |              try (BlockStreamInput bsi = new BlockStreamInput(in, blockFactory)) {
 | 
	
		
			
				|  |  | -                this.inputPage = new Page(bsi);
 | 
	
		
			
				|  |  | +                inputPage = new Page(bsi);
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  | -            this.toRelease = inputPage;
 | 
	
		
			
				|  |  |              PlanStreamInput planIn = new PlanStreamInput(in, in.namedWriteableRegistry(), null);
 | 
	
		
			
				|  |  | -            this.extractFields = planIn.readNamedWriteableCollectionAsList(NamedExpression.class);
 | 
	
		
			
				|  |  | +            List<NamedExpression> extractFields = planIn.readNamedWriteableCollectionAsList(NamedExpression.class);
 | 
	
		
			
				|  |  | +            TransportRequest result = new TransportRequest(
 | 
	
		
			
				|  |  | +                sessionId,
 | 
	
		
			
				|  |  | +                shardId,
 | 
	
		
			
				|  |  | +                inputDataType,
 | 
	
		
			
				|  |  | +                matchType,
 | 
	
		
			
				|  |  | +                matchField,
 | 
	
		
			
				|  |  | +                inputPage,
 | 
	
		
			
				|  |  | +                inputPage,
 | 
	
		
			
				|  |  | +                extractFields
 | 
	
		
			
				|  |  | +            );
 | 
	
		
			
				|  |  | +            result.setParentTask(parentTaskId);
 | 
	
		
			
				|  |  | +            return result;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          @Override
 | 
	
	
		
			
				|  | @@ -490,144 +167,8 @@ public class EnrichLookupService {
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          @Override
 | 
	
		
			
				|  |  | -        public String[] indices() {
 | 
	
		
			
				|  |  | -            return new String[] { shardId.getIndexName() };
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        @Override
 | 
	
		
			
				|  |  | -        public IndicesOptions indicesOptions() {
 | 
	
		
			
				|  |  | -            return IndicesOptions.strictSingleIndexNoExpandForbidClosed();
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        @Override
 | 
	
		
			
				|  |  | -        public Task createTask(long id, String type, String action, TaskId parentTaskId, Map<String, String> headers) {
 | 
	
		
			
				|  |  | -            return new CancellableTask(id, type, action, "", parentTaskId, headers) {
 | 
	
		
			
				|  |  | -                @Override
 | 
	
		
			
				|  |  | -                public String getDescription() {
 | 
	
		
			
				|  |  | -                    return lookupDescription(
 | 
	
		
			
				|  |  | -                        sessionId,
 | 
	
		
			
				|  |  | -                        shardId,
 | 
	
		
			
				|  |  | -                        inputDataType,
 | 
	
		
			
				|  |  | -                        matchType,
 | 
	
		
			
				|  |  | -                        matchField,
 | 
	
		
			
				|  |  | -                        extractFields,
 | 
	
		
			
				|  |  | -                        inputPage.getPositionCount()
 | 
	
		
			
				|  |  | -                    );
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | -            };
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        private void releasePage() {
 | 
	
		
			
				|  |  | -            if (toRelease != null) {
 | 
	
		
			
				|  |  | -                Releasables.closeExpectNoException(toRelease::releaseBlocks);
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        @Override
 | 
	
		
			
				|  |  | -        public void incRef() {
 | 
	
		
			
				|  |  | -            refs.incRef();
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        @Override
 | 
	
		
			
				|  |  | -        public boolean tryIncRef() {
 | 
	
		
			
				|  |  | -            return refs.tryIncRef();
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        @Override
 | 
	
		
			
				|  |  | -        public boolean decRef() {
 | 
	
		
			
				|  |  | -            return refs.decRef();
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        @Override
 | 
	
		
			
				|  |  | -        public boolean hasReferences() {
 | 
	
		
			
				|  |  | -            return refs.hasReferences();
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    private static String lookupDescription(
 | 
	
		
			
				|  |  | -        String sessionId,
 | 
	
		
			
				|  |  | -        ShardId shardId,
 | 
	
		
			
				|  |  | -        DataType inputDataType,
 | 
	
		
			
				|  |  | -        String matchType,
 | 
	
		
			
				|  |  | -        String matchField,
 | 
	
		
			
				|  |  | -        List<NamedExpression> extractFields,
 | 
	
		
			
				|  |  | -        int positionCount
 | 
	
		
			
				|  |  | -    ) {
 | 
	
		
			
				|  |  | -        return "ENRICH_LOOKUP("
 | 
	
		
			
				|  |  | -            + " session="
 | 
	
		
			
				|  |  | -            + sessionId
 | 
	
		
			
				|  |  | -            + " ,shard="
 | 
	
		
			
				|  |  | -            + shardId
 | 
	
		
			
				|  |  | -            + " ,input_type="
 | 
	
		
			
				|  |  | -            + inputDataType
 | 
	
		
			
				|  |  | -            + " ,match_type="
 | 
	
		
			
				|  |  | -            + matchType
 | 
	
		
			
				|  |  | -            + " ,match_field="
 | 
	
		
			
				|  |  | -            + matchField
 | 
	
		
			
				|  |  | -            + " ,extract_fields="
 | 
	
		
			
				|  |  | -            + extractFields
 | 
	
		
			
				|  |  | -            + " ,positions="
 | 
	
		
			
				|  |  | -            + positionCount
 | 
	
		
			
				|  |  | -            + ")";
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    private static class LookupResponse extends TransportResponse {
 | 
	
		
			
				|  |  | -        private Page page;
 | 
	
		
			
				|  |  | -        private final RefCounted refs = AbstractRefCounted.of(this::releasePage);
 | 
	
		
			
				|  |  | -        private final BlockFactory blockFactory;
 | 
	
		
			
				|  |  | -        private long reservedBytes = 0;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        LookupResponse(Page page, BlockFactory blockFactory) {
 | 
	
		
			
				|  |  | -            this.page = page;
 | 
	
		
			
				|  |  | -            this.blockFactory = blockFactory;
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        LookupResponse(StreamInput in, BlockFactory blockFactory) throws IOException {
 | 
	
		
			
				|  |  | -            try (BlockStreamInput bsi = new BlockStreamInput(in, blockFactory)) {
 | 
	
		
			
				|  |  | -                this.page = new Page(bsi);
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -            this.blockFactory = blockFactory;
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        @Override
 | 
	
		
			
				|  |  | -        public void writeTo(StreamOutput out) throws IOException {
 | 
	
		
			
				|  |  | -            long bytes = page.ramBytesUsedByBlocks();
 | 
	
		
			
				|  |  | -            blockFactory.breaker().addEstimateBytesAndMaybeBreak(bytes, "serialize enrich lookup response");
 | 
	
		
			
				|  |  | -            reservedBytes += bytes;
 | 
	
		
			
				|  |  | -            page.writeTo(out);
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        Page takePage() {
 | 
	
		
			
				|  |  | -            var p = page;
 | 
	
		
			
				|  |  | -            page = null;
 | 
	
		
			
				|  |  | -            return p;
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        private void releasePage() {
 | 
	
		
			
				|  |  | -            blockFactory.breaker().addWithoutBreaking(-reservedBytes);
 | 
	
		
			
				|  |  | -            if (page != null) {
 | 
	
		
			
				|  |  | -                Releasables.closeExpectNoException(page::releaseBlocks);
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        @Override
 | 
	
		
			
				|  |  | -        public void incRef() {
 | 
	
		
			
				|  |  | -            refs.incRef();
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        @Override
 | 
	
		
			
				|  |  | -        public boolean tryIncRef() {
 | 
	
		
			
				|  |  | -            return refs.tryIncRef();
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        @Override
 | 
	
		
			
				|  |  | -        public boolean decRef() {
 | 
	
		
			
				|  |  | -            return refs.decRef();
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        @Override
 | 
	
		
			
				|  |  | -        public boolean hasReferences() {
 | 
	
		
			
				|  |  | -            return refs.hasReferences();
 | 
	
		
			
				|  |  | +        protected String extraDescription() {
 | 
	
		
			
				|  |  | +            return " ,match_type=" + matchType + " ,match_field=" + matchField;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  }
 |