|
@@ -86,13 +86,13 @@ public final class BulkRequestParser {
|
|
|
.withRestApiVersion(restApiVersion);
|
|
|
}
|
|
|
|
|
|
- private static int findNextMarker(byte marker, int from, BytesReference data, boolean isIncremental) {
|
|
|
+ private static int findNextMarker(byte marker, int from, BytesReference data, boolean lastData) {
|
|
|
final int res = data.indexOf(marker, from);
|
|
|
if (res != -1) {
|
|
|
assert res >= 0;
|
|
|
return res;
|
|
|
}
|
|
|
- if (from != data.length() && isIncremental == false) {
|
|
|
+ if (from != data.length() && lastData) {
|
|
|
throw new IllegalArgumentException("The bulk request must be terminated by a newline [\\n]");
|
|
|
}
|
|
|
return res;
|
|
@@ -137,13 +137,7 @@ public final class BulkRequestParser {
|
|
|
Consumer<UpdateRequest> updateRequestConsumer,
|
|
|
Consumer<DeleteRequest> deleteRequestConsumer
|
|
|
) throws IOException {
|
|
|
- // Bulk requests can contain a lot of repeated strings for the index, pipeline and routing parameters. This map is used to
|
|
|
- // deduplicate duplicate strings parsed for these parameters. While it does not prevent instantiating the duplicate strings, it
|
|
|
- // reduces their lifetime to the lifetime of this parse call instead of the lifetime of the full bulk request.
|
|
|
- final Map<String, String> stringDeduplicator = new HashMap<>();
|
|
|
-
|
|
|
- incrementalParse(
|
|
|
- data,
|
|
|
+ IncrementalParser incrementalParser = new IncrementalParser(
|
|
|
defaultIndex,
|
|
|
defaultRouting,
|
|
|
defaultFetchSourceContext,
|
|
@@ -155,53 +149,164 @@ public final class BulkRequestParser {
|
|
|
xContentType,
|
|
|
indexRequestConsumer,
|
|
|
updateRequestConsumer,
|
|
|
- deleteRequestConsumer,
|
|
|
- false,
|
|
|
- stringDeduplicator
|
|
|
+ deleteRequestConsumer
|
|
|
);
|
|
|
+
|
|
|
+ incrementalParser.parse(data, true);
|
|
|
}
|
|
|
|
|
|
- public int incrementalParse(
|
|
|
- BytesReference data,
|
|
|
- String defaultIndex,
|
|
|
- String defaultRouting,
|
|
|
- FetchSourceContext defaultFetchSourceContext,
|
|
|
- String defaultPipeline,
|
|
|
- Boolean defaultRequireAlias,
|
|
|
- Boolean defaultRequireDataStream,
|
|
|
- Boolean defaultListExecutedPipelines,
|
|
|
+ public IncrementalParser incrementalParser(
|
|
|
+ @Nullable String defaultIndex,
|
|
|
+ @Nullable String defaultRouting,
|
|
|
+ @Nullable FetchSourceContext defaultFetchSourceContext,
|
|
|
+ @Nullable String defaultPipeline,
|
|
|
+ @Nullable Boolean defaultRequireAlias,
|
|
|
+ @Nullable Boolean defaultRequireDataStream,
|
|
|
+ @Nullable Boolean defaultListExecutedPipelines,
|
|
|
boolean allowExplicitIndex,
|
|
|
XContentType xContentType,
|
|
|
BiConsumer<IndexRequest, String> indexRequestConsumer,
|
|
|
Consumer<UpdateRequest> updateRequestConsumer,
|
|
|
- Consumer<DeleteRequest> deleteRequestConsumer,
|
|
|
- boolean isIncremental,
|
|
|
- Map<String, String> stringDeduplicator
|
|
|
- ) throws IOException {
|
|
|
- XContent xContent = xContentType.xContent();
|
|
|
- byte marker = xContent.bulkSeparator();
|
|
|
+ Consumer<DeleteRequest> deleteRequestConsumer
|
|
|
+ ) {
|
|
|
+ return new IncrementalParser(
|
|
|
+ defaultIndex,
|
|
|
+ defaultRouting,
|
|
|
+ defaultFetchSourceContext,
|
|
|
+ defaultPipeline,
|
|
|
+ defaultRequireAlias,
|
|
|
+ defaultRequireDataStream,
|
|
|
+ defaultListExecutedPipelines,
|
|
|
+ allowExplicitIndex,
|
|
|
+ xContentType,
|
|
|
+ indexRequestConsumer,
|
|
|
+ updateRequestConsumer,
|
|
|
+ deleteRequestConsumer
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ public class IncrementalParser {
|
|
|
+
|
|
|
+ // Bulk requests can contain a lot of repeated strings for the index, pipeline and routing parameters. This map is used to
|
|
|
+ // deduplicate duplicate strings parsed for these parameters. While it does not prevent instantiating the duplicate strings, it
|
|
|
+ // reduces their lifetime to the lifetime of this parse call instead of the lifetime of the full bulk request.
|
|
|
+ private final Map<String, String> stringDeduplicator = new HashMap<>();
|
|
|
+
|
|
|
+ private final String defaultIndex;
|
|
|
+ private final String defaultRouting;
|
|
|
+ private final FetchSourceContext defaultFetchSourceContext;
|
|
|
+ private final String defaultPipeline;
|
|
|
+ private final Boolean defaultRequireAlias;
|
|
|
+ private final Boolean defaultRequireDataStream;
|
|
|
+ private final Boolean defaultListExecutedPipelines;
|
|
|
+ private final boolean allowExplicitIndex;
|
|
|
+
|
|
|
+ private final XContentType xContentType;
|
|
|
+ private final byte marker;
|
|
|
+ private final BiConsumer<IndexRequest, String> indexRequestConsumer;
|
|
|
+ private final Consumer<UpdateRequest> updateRequestConsumer;
|
|
|
+ private final Consumer<DeleteRequest> deleteRequestConsumer;
|
|
|
+
|
|
|
+ private Exception failure = null;
|
|
|
+ private int incrementalFromOffset = 0;
|
|
|
+ private int line = 0;
|
|
|
boolean typesDeprecationLogged = false;
|
|
|
|
|
|
- int line = 0;
|
|
|
- int from = 0;
|
|
|
- int consumed = 0;
|
|
|
+ private DocWriteRequest<?> currentRequest = null;
|
|
|
+ private String currentType = null;
|
|
|
+ private String currentPipeline = null;
|
|
|
+ private boolean currentListExecutedPipelines = false;
|
|
|
+ private FetchSourceContext currentFetchSourceContext = null;
|
|
|
|
|
|
- while (true) {
|
|
|
- int nextMarker = findNextMarker(marker, from, data, isIncremental);
|
|
|
- if (nextMarker == -1) {
|
|
|
- break;
|
|
|
+ private IncrementalParser(
|
|
|
+ @Nullable String defaultIndex,
|
|
|
+ @Nullable String defaultRouting,
|
|
|
+ @Nullable FetchSourceContext defaultFetchSourceContext,
|
|
|
+ @Nullable String defaultPipeline,
|
|
|
+ @Nullable Boolean defaultRequireAlias,
|
|
|
+ @Nullable Boolean defaultRequireDataStream,
|
|
|
+ @Nullable Boolean defaultListExecutedPipelines,
|
|
|
+ boolean allowExplicitIndex,
|
|
|
+ XContentType xContentType,
|
|
|
+ BiConsumer<IndexRequest, String> indexRequestConsumer,
|
|
|
+ Consumer<UpdateRequest> updateRequestConsumer,
|
|
|
+ Consumer<DeleteRequest> deleteRequestConsumer
|
|
|
+ ) {
|
|
|
+ this.defaultIndex = defaultIndex;
|
|
|
+ this.defaultRouting = defaultRouting;
|
|
|
+ this.defaultFetchSourceContext = defaultFetchSourceContext;
|
|
|
+ this.defaultPipeline = defaultPipeline;
|
|
|
+ this.defaultRequireAlias = defaultRequireAlias;
|
|
|
+ this.defaultRequireDataStream = defaultRequireDataStream;
|
|
|
+ this.defaultListExecutedPipelines = defaultListExecutedPipelines;
|
|
|
+ this.allowExplicitIndex = allowExplicitIndex;
|
|
|
+ this.xContentType = xContentType;
|
|
|
+ this.marker = xContentType.xContent().bulkSeparator();
|
|
|
+ this.indexRequestConsumer = indexRequestConsumer;
|
|
|
+ this.updateRequestConsumer = updateRequestConsumer;
|
|
|
+ this.deleteRequestConsumer = deleteRequestConsumer;
|
|
|
+ }
|
|
|
+
|
|
|
+ public int parse(BytesReference data, boolean lastData) throws IOException {
|
|
|
+ if (failure != null) {
|
|
|
+ assert false : failure.getMessage();
|
|
|
+ throw new IllegalStateException("Parser has already encountered exception", failure);
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ return tryParse(data, lastData);
|
|
|
+ } catch (Exception e) {
|
|
|
+ failure = e;
|
|
|
+ throw e;
|
|
|
}
|
|
|
- line++;
|
|
|
+ }
|
|
|
+
|
|
|
+ private int tryParse(BytesReference data, boolean lastData) throws IOException {
|
|
|
+ int from = 0;
|
|
|
+ int consumed = 0;
|
|
|
+
|
|
|
+ while (true) {
|
|
|
+ int nextMarker = findNextMarker(marker, incrementalFromOffset, data, lastData);
|
|
|
+ if (nextMarker == -1) {
|
|
|
+ incrementalFromOffset = data.length() - consumed;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ incrementalFromOffset = nextMarker + 1;
|
|
|
+ line++;
|
|
|
+
|
|
|
+ if (currentRequest == null) {
|
|
|
+ if (parseActionLine(data, from, nextMarker)) {
|
|
|
+ if (currentRequest instanceof DeleteRequest deleteRequest) {
|
|
|
+ deleteRequestConsumer.accept(deleteRequest);
|
|
|
+ currentRequest = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ parseAndConsumeDocumentLine(data, from, nextMarker);
|
|
|
+ currentRequest = null;
|
|
|
+ }
|
|
|
|
|
|
- // now parse the action
|
|
|
- try (XContentParser parser = createParser(xContent, data, from, nextMarker)) {
|
|
|
- // move pointers
|
|
|
from = nextMarker + 1;
|
|
|
+ consumed = from;
|
|
|
+ }
|
|
|
+
|
|
|
+ return lastData ? from : consumed;
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean parseActionLine(BytesReference data, int from, int to) throws IOException {
|
|
|
+ assert currentRequest == null;
|
|
|
+
|
|
|
+ // Reset the fields which are accessed during document line parsing
|
|
|
+ currentType = null;
|
|
|
+ currentPipeline = defaultPipeline;
|
|
|
+ currentListExecutedPipelines = defaultListExecutedPipelines != null && defaultListExecutedPipelines;
|
|
|
+ currentFetchSourceContext = defaultFetchSourceContext;
|
|
|
+
|
|
|
+ try (XContentParser parser = createParser(xContentType.xContent(), data, from, to)) {
|
|
|
|
|
|
// Move to START_OBJECT
|
|
|
XContentParser.Token token = parser.nextToken();
|
|
|
if (token == null) {
|
|
|
- continue;
|
|
|
+ return false;
|
|
|
}
|
|
|
if (token != XContentParser.Token.START_OBJECT) {
|
|
|
throw new IllegalArgumentException(
|
|
@@ -239,20 +344,16 @@ public final class BulkRequestParser {
|
|
|
}
|
|
|
|
|
|
String index = defaultIndex;
|
|
|
- String type = null;
|
|
|
String id = null;
|
|
|
String routing = defaultRouting;
|
|
|
- FetchSourceContext fetchSourceContext = defaultFetchSourceContext;
|
|
|
String opType = null;
|
|
|
long version = Versions.MATCH_ANY;
|
|
|
VersionType versionType = VersionType.INTERNAL;
|
|
|
long ifSeqNo = SequenceNumbers.UNASSIGNED_SEQ_NO;
|
|
|
long ifPrimaryTerm = UNASSIGNED_PRIMARY_TERM;
|
|
|
int retryOnConflict = 0;
|
|
|
- String pipeline = defaultPipeline;
|
|
|
boolean requireAlias = defaultRequireAlias != null && defaultRequireAlias;
|
|
|
boolean requireDataStream = defaultRequireDataStream != null && defaultRequireDataStream;
|
|
|
- boolean listExecutedPipelines = defaultListExecutedPipelines != null && defaultListExecutedPipelines;
|
|
|
Map<String, String> dynamicTemplates = Map.of();
|
|
|
|
|
|
// at this stage, next token can either be END_OBJECT (and use default index and type, with auto generated id)
|
|
@@ -283,7 +384,7 @@ public final class BulkRequestParser {
|
|
|
"Action/metadata line [" + line + "] contains an unknown parameter [" + currentFieldName + "]"
|
|
|
);
|
|
|
}
|
|
|
- type = stringDeduplicator.computeIfAbsent(parser.text(), Function.identity());
|
|
|
+ currentType = stringDeduplicator.computeIfAbsent(parser.text(), Function.identity());
|
|
|
} else if (ID.match(currentFieldName, parser.getDeprecationHandler())) {
|
|
|
id = parser.text();
|
|
|
} else if (ROUTING.match(currentFieldName, parser.getDeprecationHandler())) {
|
|
@@ -301,15 +402,15 @@ public final class BulkRequestParser {
|
|
|
} else if (RETRY_ON_CONFLICT.match(currentFieldName, parser.getDeprecationHandler())) {
|
|
|
retryOnConflict = parser.intValue();
|
|
|
} else if (PIPELINE.match(currentFieldName, parser.getDeprecationHandler())) {
|
|
|
- pipeline = stringDeduplicator.computeIfAbsent(parser.text(), Function.identity());
|
|
|
+ currentPipeline = stringDeduplicator.computeIfAbsent(parser.text(), Function.identity());
|
|
|
} else if (SOURCE.match(currentFieldName, parser.getDeprecationHandler())) {
|
|
|
- fetchSourceContext = FetchSourceContext.fromXContent(parser);
|
|
|
+ currentFetchSourceContext = FetchSourceContext.fromXContent(parser);
|
|
|
} else if (REQUIRE_ALIAS.match(currentFieldName, parser.getDeprecationHandler())) {
|
|
|
requireAlias = parser.booleanValue();
|
|
|
} else if (REQUIRE_DATA_STREAM.match(currentFieldName, parser.getDeprecationHandler())) {
|
|
|
requireDataStream = parser.booleanValue();
|
|
|
} else if (LIST_EXECUTED_PIPELINES.match(currentFieldName, parser.getDeprecationHandler())) {
|
|
|
- listExecutedPipelines = parser.booleanValue();
|
|
|
+ currentListExecutedPipelines = parser.booleanValue();
|
|
|
} else {
|
|
|
throw new IllegalArgumentException(
|
|
|
"Action/metadata line [" + line + "] contains an unknown parameter [" + currentFieldName + "]"
|
|
@@ -330,7 +431,7 @@ public final class BulkRequestParser {
|
|
|
dynamicTemplates = parser.mapStrings();
|
|
|
} else if (token == XContentParser.Token.START_OBJECT
|
|
|
&& SOURCE.match(currentFieldName, parser.getDeprecationHandler())) {
|
|
|
- fetchSourceContext = FetchSourceContext.fromXContent(parser);
|
|
|
+ currentFetchSourceContext = FetchSourceContext.fromXContent(parser);
|
|
|
} else if (token != XContentParser.Token.VALUE_NULL) {
|
|
|
throw new IllegalArgumentException(
|
|
|
"Malformed action/metadata line ["
|
|
@@ -364,22 +465,13 @@ public final class BulkRequestParser {
|
|
|
"Delete request in line [" + line + "] does not accept " + DYNAMIC_TEMPLATES.getPreferredName()
|
|
|
);
|
|
|
}
|
|
|
- deleteRequestConsumer.accept(
|
|
|
- new DeleteRequest(index).id(id)
|
|
|
- .routing(routing)
|
|
|
- .version(version)
|
|
|
- .versionType(versionType)
|
|
|
- .setIfSeqNo(ifSeqNo)
|
|
|
- .setIfPrimaryTerm(ifPrimaryTerm)
|
|
|
- );
|
|
|
- consumed = from;
|
|
|
+ currentRequest = new DeleteRequest(index).id(id)
|
|
|
+ .routing(routing)
|
|
|
+ .version(version)
|
|
|
+ .versionType(versionType)
|
|
|
+ .setIfSeqNo(ifSeqNo)
|
|
|
+ .setIfPrimaryTerm(ifPrimaryTerm);
|
|
|
} else {
|
|
|
- nextMarker = findNextMarker(marker, from, data, isIncremental);
|
|
|
- if (nextMarker == -1) {
|
|
|
- break;
|
|
|
- }
|
|
|
- line++;
|
|
|
-
|
|
|
// we use internalAdd so we don't fork here, this allows us not to copy over the big byte array to small chunks
|
|
|
// of index request.
|
|
|
if ("index".equals(action) || "create".equals(action)) {
|
|
@@ -387,20 +479,19 @@ public final class BulkRequestParser {
|
|
|
.routing(routing)
|
|
|
.version(version)
|
|
|
.versionType(versionType)
|
|
|
- .setPipeline(pipeline)
|
|
|
+ .setPipeline(currentPipeline)
|
|
|
.setIfSeqNo(ifSeqNo)
|
|
|
.setIfPrimaryTerm(ifPrimaryTerm)
|
|
|
- .source(sliceTrimmingCarriageReturn(data, from, nextMarker, xContentType), xContentType)
|
|
|
.setDynamicTemplates(dynamicTemplates)
|
|
|
.setRequireAlias(requireAlias)
|
|
|
.setRequireDataStream(requireDataStream)
|
|
|
- .setListExecutedPipelines(listExecutedPipelines);
|
|
|
+ .setListExecutedPipelines(currentListExecutedPipelines);
|
|
|
if ("create".equals(action)) {
|
|
|
indexRequest = indexRequest.create(true);
|
|
|
} else if (opType != null) {
|
|
|
indexRequest = indexRequest.create("create".equals(opType));
|
|
|
}
|
|
|
- indexRequestConsumer.accept(indexRequest, type);
|
|
|
+ currentRequest = indexRequest;
|
|
|
} else if ("update".equals(action)) {
|
|
|
if (version != Versions.MATCH_ANY || versionType != VersionType.INTERNAL) {
|
|
|
throw new IllegalArgumentException(
|
|
@@ -427,31 +518,38 @@ public final class BulkRequestParser {
|
|
|
.setIfPrimaryTerm(ifPrimaryTerm)
|
|
|
.setRequireAlias(requireAlias)
|
|
|
.routing(routing);
|
|
|
- try (
|
|
|
- XContentParser sliceParser = createParser(
|
|
|
- xContent,
|
|
|
- sliceTrimmingCarriageReturn(data, from, nextMarker, xContentType)
|
|
|
- )
|
|
|
- ) {
|
|
|
- updateRequest.fromXContent(sliceParser);
|
|
|
- }
|
|
|
- if (fetchSourceContext != null) {
|
|
|
- updateRequest.fetchSource(fetchSourceContext);
|
|
|
- }
|
|
|
- IndexRequest upsertRequest = updateRequest.upsertRequest();
|
|
|
- if (upsertRequest != null) {
|
|
|
- upsertRequest.setPipeline(pipeline).setListExecutedPipelines(listExecutedPipelines);
|
|
|
- }
|
|
|
-
|
|
|
- updateRequestConsumer.accept(updateRequest);
|
|
|
+ currentRequest = updateRequest;
|
|
|
}
|
|
|
- // move pointers
|
|
|
- from = nextMarker + 1;
|
|
|
- consumed = from;
|
|
|
}
|
|
|
}
|
|
|
+ return true;
|
|
|
}
|
|
|
- return isIncremental ? consumed : from;
|
|
|
+
|
|
|
+ private void parseAndConsumeDocumentLine(BytesReference data, int from, int to) throws IOException {
|
|
|
+ assert currentRequest != null && currentRequest instanceof DeleteRequest == false;
|
|
|
+ if (currentRequest instanceof IndexRequest indexRequest) {
|
|
|
+ indexRequest.source(sliceTrimmingCarriageReturn(data, from, to, xContentType), xContentType);
|
|
|
+ indexRequestConsumer.accept(indexRequest, currentType);
|
|
|
+ } else if (currentRequest instanceof UpdateRequest updateRequest) {
|
|
|
+ try (
|
|
|
+ XContentParser sliceParser = createParser(
|
|
|
+ xContentType.xContent(),
|
|
|
+ sliceTrimmingCarriageReturn(data, from, to, xContentType)
|
|
|
+ )
|
|
|
+ ) {
|
|
|
+ updateRequest.fromXContent(sliceParser);
|
|
|
+ }
|
|
|
+ if (currentFetchSourceContext != null) {
|
|
|
+ updateRequest.fetchSource(currentFetchSourceContext);
|
|
|
+ }
|
|
|
+ IndexRequest upsertRequest = updateRequest.upsertRequest();
|
|
|
+ if (upsertRequest != null) {
|
|
|
+ upsertRequest.setPipeline(currentPipeline).setListExecutedPipelines(currentListExecutedPipelines);
|
|
|
+ }
|
|
|
+ updateRequestConsumer.accept(updateRequest);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
}
|
|
|
|
|
|
@UpdateForV9
|