Class SourceExtractors
java.lang.Object
org.elasticsearch.xpack.esql.datasources.SourceExtractors
- All Implemented Interfaces:
Closeable,AutoCloseable,Releasable
Driver-scoped lookup table of per-file
The synthetic column flowing between source and extract carries a
ColumnExtractors, addressed by a compact
int id allocated at registration time. Acts as the rendezvous between an
AsyncExternalSourceOperatorFactory (which registers a new extractor
each time it opens a file) and an ExternalFieldExtractOperator (which decodes the
synthetic _rowPosition carried on pages and routes extraction
requests to the correct extractor).
_rowPosition encoding
The synthetic column flowing between source and extract carries a long per row that
packs the extractor id and the file-local position into a single value:
bit 63 reserved zero (keeps the encoded value non-negative for signed long order) bits 62..48 extractor id (15 bits, 0..32767) bits 47..0 file-local row position (48 bits, 0..2^48-1)Concretely:
encoded = ((long) id << 48) | (localPos & MAX_LOCAL_POSITION). This keeps
the row identifier a primitive long, so the existing LongBlock type carries it
end-to-end without any new block-type plumbing. Bit 63 is deliberately reserved so every
encoded value is non-negative; that way Java's signed-long ordering (used by ESQL's TopN
encoder, which calls NumericUtils.longToSortableBytes) coincides with
(id, localPos) lexicographic order, and the encoding remains usable as a stable
tiebreaker should anything downstream sort by it.
Lifecycle
OneSourceExtractors per driver. The source operator registers extractors during the
async producer loop; the extract operator reads from it on the driver thread. Both sides hold
the same instance via AsyncExternalSourceOperatorFactory#sourceExtractorsFor(DriverContext).
The extract operator owns close(); closing is idempotent.
Threading
register(org.elasticsearch.xpack.esql.datasources.spi.ColumnExtractor) is invoked from source executor threads (one at a time per driver; the source
loop is single-producer within a driver). get(int) and materialize(long[], int, java.util.List<java.lang.String>, java.util.List<org.elasticsearch.xpack.esql.core.type.DataType>, org.elasticsearch.compute.data.BlockFactory) are invoked
from the driver thread. The data-flow invariant — the source publishes a page only after the
file's extractor has been registered — establishes the necessary happens-before via the page
buffer's internal synchronization. We use a synchronized ArrayList as a defensive
choice; the cost is negligible for the few-dozen registrations per driver in practice.-
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final intNumber of bits reserved for the file-local row position; the rest go to the extractor id.static final intMaximum representable extractor id (inclusive):2^15 - 1 = 32 767.static final longMaximum representable file-local row position (inclusive):2^48 - 1. -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionvoidclose()static intdecodeExtractorId(long encoded) Extract the extractor id (high 15 bits) from an encoded row reference.static longdecodeLocalPosition(long encoded) Extract the file-local position (low 48 bits) from an encoded row reference.static longencode(int extractorId, long localPosition) Pack an(extractorId, localPosition)pair into a singlelong.get(int id) Look up a registered extractor by id.Block[]materialize(long[] encodedRefs, int count, List<String> columns, List<DataType> targetTypes, BlockFactory factory) Materializecolumnsfor the rows referenced byencodedRefs[0..count).intregister(ColumnExtractor extractor) Register a newColumnExtractorand return the id assigned to it.voidregisterTrailingCloseable(Closeable closeable) Attach a closeable whose lifecycle must extend beyond the source operator's.intsize()Number of extractors currently registered.
-
Field Details
-
LOCAL_POSITION_BITS
public static final int LOCAL_POSITION_BITSNumber of bits reserved for the file-local row position; the rest go to the extractor id. MirrorsColumnExtractor.LOCAL_POSITION_BITS(the SPI-side constant iterator implementations OR against when they pre-encode_rowPositionvalues); kept aligned via theassertbelow so a divergent edit surfaces in tests instead of as a wire- format mismatch.- See Also:
-
MAX_LOCAL_POSITION
public static final long MAX_LOCAL_POSITIONMaximum representable file-local row position (inclusive):2^48 - 1.- See Also:
-
MAX_EXTRACTOR_ID
public static final int MAX_EXTRACTOR_IDMaximum representable extractor id (inclusive):2^15 - 1 = 32 767. We use 15 bits (not the 16 available betweenLOCAL_POSITION_BITSandLong.SIZE) so that bit 63 stays clear and every encoded value is non-negative — see class-level javadoc.- See Also:
-
-
Constructor Details
-
SourceExtractors
public SourceExtractors()
-
-
Method Details
-
encode
public static long encode(int extractorId, long localPosition) Pack an(extractorId, localPosition)pair into a singlelong. The packed value is the on-the-wire representation of_rowPositiondownstream of the source. -
decodeExtractorId
public static int decodeExtractorId(long encoded) Extract the extractor id (high 15 bits) from an encoded row reference. -
decodeLocalPosition
public static long decodeLocalPosition(long encoded) Extract the file-local position (low 48 bits) from an encoded row reference. -
register
Register a newColumnExtractorand return the id assigned to it. Ids are allocated sequentially starting at 0, in registration order. The returned id is what callers must pack into the_rowPositionvalues they emit for rows coming from this extractor's file.- Throws:
IllegalStateException- if the table is closed or if the id space is exhausted
-
get
Look up a registered extractor by id. ThrowsIllegalArgumentExceptionfor ids that were never assigned — this should never happen in normal operation because the id always comes from a previously-encoded row reference, so failure here indicates a wiring bug. -
size
public int size()Number of extractors currently registered. -
registerTrailingCloseable
Attach a closeable whose lifecycle must extend beyond the source operator's. The closeable is closed duringclose()after all registered extractors, so any storage objects the extractors hold get released before the trailing resource (the per-query concurrency budget) is torn down.If the registry has already been closed, the closeable is closed immediately to preserve the invariant that registered resources are eventually closed exactly once.
-
materialize
public Block[] materialize(long[] encodedRefs, int count, List<String> columns, List<DataType> targetTypes, BlockFactory factory) Materializecolumnsfor the rows referenced byencodedRefs[0..count). Output blocks preserve the input order: rowiof each output block corresponds toencodedRefs[i].Algorithm: classify each input ref by its extractor id, build per-extractor packed arrays of file-local positions, dispatch one
ColumnExtractor.extract(java.lang.String[], org.elasticsearch.xpack.esql.core.type.DataType[], long[], org.elasticsearch.compute.data.BlockFactory)call per (column, id) pair, then reassemble the output by walking the original slot order. The grouping makes each underlying extractor see a single batched call per column rather than one call per row, which matters for parquet because each call walks the file forward.- Parameters:
encodedRefs- encoded row references (as emitted via_rowPosition)count- number of valid entries inencodedRefscolumns- column names to materializetargetTypes- planner/declared type per column, aligned withcolumns; threads the declared-type coercion contract intoColumnExtractor.extract(java.lang.String[], org.elasticsearch.xpack.esql.core.type.DataType[], long[], org.elasticsearch.compute.data.BlockFactory)(null— the list or an entry — disables coercion for that column)factory- block factory for memory accounting- Returns:
- one Block per column, each with exactly
countrows; in caller-supplied order
-
close
public void close()- Specified by:
closein interfaceAutoCloseable- Specified by:
closein interfaceCloseable- Specified by:
closein interfaceReleasable
-