diff --git a/.idea/runConfigurations/Debug_Elasticsearch.xml b/.idea/runConfigurations/Debug_Elasticsearch.xml
index 051b746a1a497..9c062d3cfd790 100644
--- a/.idea/runConfigurations/Debug_Elasticsearch.xml
+++ b/.idea/runConfigurations/Debug_Elasticsearch.xml
@@ -1,5 +1,6 @@
+
@@ -12,4 +13,4 @@
-
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/Debug_Elasticsearch__node_2_.xml b/.idea/runConfigurations/Debug_Elasticsearch__node_2_.xml
index 94bb079398ffd..76bb8f72eafc9 100644
--- a/.idea/runConfigurations/Debug_Elasticsearch__node_2_.xml
+++ b/.idea/runConfigurations/Debug_Elasticsearch__node_2_.xml
@@ -6,6 +6,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/Debug_Elasticsearch__node_3_.xml b/.idea/runConfigurations/Debug_Elasticsearch__node_3_.xml
index aaef20fec729b..a321a38f14e57 100644
--- a/.idea/runConfigurations/Debug_Elasticsearch__node_3_.xml
+++ b/.idea/runConfigurations/Debug_Elasticsearch__node_3_.xml
@@ -6,6 +6,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/server/src/main/java/org/elasticsearch/common/lucene/uid/PerThreadIDVersionAndSeqNoLookup.java b/server/src/main/java/org/elasticsearch/common/lucene/uid/PerThreadIDVersionAndSeqNoLookup.java
index 19a527cf8648b..802ab567f756f 100644
--- a/server/src/main/java/org/elasticsearch/common/lucene/uid/PerThreadIDVersionAndSeqNoLookup.java
+++ b/server/src/main/java/org/elasticsearch/common/lucene/uid/PerThreadIDVersionAndSeqNoLookup.java
@@ -51,6 +51,7 @@ final class PerThreadIDVersionAndSeqNoLookup {
// we keep it around for now, to reduce the amount of e.g. hash lookups by field and stuff
private final TermsEnum termsEnum;
+ private final LeafReader reader;
/** Reused for iteration (when the term exists) */
private PostingsEnum docsEnum;
@@ -66,22 +67,25 @@ final class PerThreadIDVersionAndSeqNoLookup {
* Initialize lookup for the provided segment
*/
PerThreadIDVersionAndSeqNoLookup(LeafReader reader, boolean trackReaderKey, boolean loadTimestampRange) throws IOException {
+ this.reader = reader;
final Terms terms = reader.terms(IdFieldMapper.NAME);
if (terms == null) {
- // If a segment contains only no-ops, it does not have _uid but has both _soft_deletes and _tombstone fields.
- final NumericDocValues softDeletesDV = reader.getNumericDocValues(Lucene.SOFT_DELETES_FIELD);
- final NumericDocValues tombstoneDV = reader.getNumericDocValues(SeqNoFieldMapper.TOMBSTONE_NAME);
- // this is a special case when we pruned away all IDs in a segment since all docs are deleted.
- final boolean allDocsDeleted = (softDeletesDV != null && reader.numDocs() == 0);
- if ((softDeletesDV == null || tombstoneDV == null) && allDocsDeleted == false) {
- throw new IllegalArgumentException(
- "reader does not have _uid terms but not a no-op segment; "
- + "_soft_deletes ["
- + softDeletesDV
- + "], _tombstone ["
- + tombstoneDV
- + "]"
- );
+ if (reader.getSortedDocValues(IdFieldMapper.NAME) == null) {
+ // If a segment contains only no-ops, it does not have _uid but has both _soft_deletes and _tombstone fields.
+ final NumericDocValues softDeletesDV = reader.getNumericDocValues(Lucene.SOFT_DELETES_FIELD);
+ final NumericDocValues tombstoneDV = reader.getNumericDocValues(SeqNoFieldMapper.TOMBSTONE_NAME);
+ // this is a special case when we pruned away all IDs in a segment since all docs are deleted.
+ final boolean allDocsDeleted = (softDeletesDV != null && reader.numDocs() == 0);
+ if ((softDeletesDV == null || tombstoneDV == null) && allDocsDeleted == false) {
+ throw new IllegalArgumentException(
+ "reader does not have _uid terms but not a no-op segment; "
+ + "_soft_deletes ["
+ + softDeletesDV
+ + "], _tombstone ["
+ + tombstoneDV
+ + "]"
+ );
+ }
}
termsEnum = null;
} else {
@@ -171,7 +175,27 @@ private int getDocID(BytesRef id, LeafReaderContext context) throws IOException
}
return docID;
} else {
- return DocIdSetIterator.NO_MORE_DOCS;
+ var idDocValues = reader.getSortedDocValues(IdFieldMapper.NAME);
+ if (idDocValues == null) {
+ return DocIdSetIterator.NO_MORE_DOCS;
+ }
+
+ final Bits liveDocs = context.reader().getLiveDocs();
+ int docID = DocIdSetIterator.NO_MORE_DOCS;
+ // TODO can we use skippers here?
+ // there may be more than one matching docID, in the case of nested docs, so we want the last one:
+ for (int doc = idDocValues.nextDoc(); doc != DocIdSetIterator.NO_MORE_DOCS; doc = idDocValues.nextDoc()) {
+ if (liveDocs != null && liveDocs.get(doc) == false) {
+ continue;
+ }
+
+ int ord = idDocValues.ordValue();
+ var possibleMatch = idDocValues.lookupOrd(ord);
+ if (id.bytesEquals(possibleMatch)) {
+ docID = doc;
+ }
+ }
+ return docID;
}
}
diff --git a/server/src/main/java/org/elasticsearch/index/IndexMode.java b/server/src/main/java/org/elasticsearch/index/IndexMode.java
index 2be1c6fc41d96..91df301c4ea84 100644
--- a/server/src/main/java/org/elasticsearch/index/IndexMode.java
+++ b/server/src/main/java/org/elasticsearch/index/IndexMode.java
@@ -26,6 +26,7 @@
import org.elasticsearch.index.mapper.FieldMapper;
import org.elasticsearch.index.mapper.IdFieldMapper;
import org.elasticsearch.index.mapper.KeywordFieldMapper;
+import org.elasticsearch.index.mapper.LogsdbIdFieldMapper;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.index.mapper.MappingLookup;
import org.elasticsearch.index.mapper.MappingParserContext;
@@ -264,12 +265,12 @@ public CompressedXContent getDefaultMapping(final IndexSettings indexSettings) {
@Override
public IdFieldMapper buildIdFieldMapper(BooleanSupplier fieldDataEnabled) {
- return new ProvidedIdFieldMapper(fieldDataEnabled);
+ return new LogsdbIdFieldMapper();
}
@Override
public IdFieldMapper idFieldMapperWithoutFieldData() {
- return ProvidedIdFieldMapper.NO_FIELD_DATA;
+ return new LogsdbIdFieldMapper();
}
@Override
diff --git a/server/src/main/java/org/elasticsearch/index/engine/InternalEngine.java b/server/src/main/java/org/elasticsearch/index/engine/InternalEngine.java
index bd1c5c26bb450..941013713a7a3 100644
--- a/server/src/main/java/org/elasticsearch/index/engine/InternalEngine.java
+++ b/server/src/main/java/org/elasticsearch/index/engine/InternalEngine.java
@@ -1591,6 +1591,7 @@ private boolean assertDocDoesNotExist(final Index index, final boolean allowDele
}
private void updateDocs(final BytesRef uid, final List docs, final IndexWriter indexWriter) throws IOException {
+ // TODO
final Term uidTerm = new Term(IdFieldMapper.NAME, uid);
if (docs.size() > 1) {
indexWriter.softUpdateDocuments(uidTerm, docs, softDeletesField);
@@ -1822,7 +1823,9 @@ private DeleteResult deleteInLucene(Delete delete, DeletionStrategy plan) throws
if (plan.addStaleOpToLucene || plan.currentlyDeleted) {
indexWriter.addDocument(doc);
} else {
+ // TODO
indexWriter.softUpdateDocument(new Term(IdFieldMapper.NAME, delete.uid()), doc, softDeletesField);
+
}
return new DeleteResult(
plan.versionOfDeletion,
@@ -3460,6 +3463,7 @@ private void restoreVersionMapAndCheckpointTracker(DirectoryReader directoryRead
continue;
}
final CombinedDocValues dv = new CombinedDocValues(leaf.reader());
+ // TODO
final IdStoredFieldLoader idFieldLoader = new IdStoredFieldLoader(leaf.reader());
final DocIdSetIterator iterator = scorer.iterator();
int docId;
diff --git a/server/src/main/java/org/elasticsearch/index/mapper/BlockDocValuesReader.java b/server/src/main/java/org/elasticsearch/index/mapper/BlockDocValuesReader.java
index 736592083a229..de98fdf309136 100644
--- a/server/src/main/java/org/elasticsearch/index/mapper/BlockDocValuesReader.java
+++ b/server/src/main/java/org/elasticsearch/index/mapper/BlockDocValuesReader.java
@@ -549,6 +549,103 @@ public String toString() {
}
}
+ public static class IdDocValuesBlockLoader extends DocValuesBlockLoader {
+ private final String fieldName;
+
+ public IdDocValuesBlockLoader(String fieldName) {
+ this.fieldName = fieldName;
+ }
+
+ @Override
+ public BytesRefBuilder builder(BlockFactory factory, int expectedCount) {
+ return factory.bytesRefs(expectedCount);
+ }
+
+ @Override
+ public AllReader reader(LeafReaderContext context) throws IOException {
+ SortedDocValues sorted = DocValues.getSorted(context.reader(), fieldName);
+ return new IdDocValuesReader(sorted);
+ }
+
+ @Override
+ public boolean supportsOrdinals() {
+ return true;
+ }
+
+ @Override
+ public SortedSetDocValues ordinals(LeafReaderContext context) throws IOException {
+ return DocValues.getSortedSet(context.reader(), fieldName);
+ }
+
+ @Override
+ public String toString() {
+ return "BytesRefsFromOrds[" + fieldName + "]";
+ }
+ }
+
+ private static class IdDocValuesReader extends BlockDocValuesReader {
+ private final SortedDocValues values;
+
+ IdDocValuesReader(SortedDocValues values) {
+ this.values = values;
+ }
+
+ private BlockLoader.Block readSingleDoc(BlockFactory factory, int docId) throws IOException {
+ if (values.advanceExact(docId)) {
+ BytesRef v = values.lookupOrd(values.ordValue());
+ return factory.constantBytes(decode(v));
+ } else {
+ return factory.constantNulls();
+ }
+ }
+
+ private static BytesRef decode(BytesRef v) {
+ String s = Uid.decodeId(v.bytes, v.offset, v.length);
+ return new BytesRef(s);
+ }
+
+ @Override
+ public BlockLoader.Block read(BlockFactory factory, Docs docs) throws IOException {
+ if (docs.count() == 1) {
+ return readSingleDoc(factory, docs.get(0));
+ }
+ try (BytesRefBuilder builder = factory.bytesRefs(docs.count())) {
+ for (int i = 0; i < docs.count(); i++) {
+ int doc = docs.get(i);
+ if (doc < values.docID()) {
+ throw new IllegalStateException("docs within same block must be in order");
+ }
+ if (values.advanceExact(doc)) {
+ builder.appendBytesRef(decode(values.lookupOrd(values.ordValue())));
+ } else {
+ builder.appendNull();
+ }
+ }
+ return builder.build();
+ }
+ }
+
+ @Override
+ public void read(int docId, BlockLoader.StoredFields storedFields, Builder builder) throws IOException {
+ if (values.advanceExact(docId)) {
+ BytesRef encoded = values.lookupOrd(values.ordValue());
+ ((BytesRefBuilder) builder).appendBytesRef(decode(encoded));
+ } else {
+ builder.appendNull();
+ }
+ }
+
+ @Override
+ public int docId() {
+ return values.docID();
+ }
+
+ @Override
+ public String toString() {
+ return "BlockDocValuesReader.IdDocValuesReader";
+ }
+ }
+
private static class SingletonOrdinals extends BlockDocValuesReader {
private final SortedDocValues ordinals;
diff --git a/server/src/main/java/org/elasticsearch/index/mapper/DocumentParserContext.java b/server/src/main/java/org/elasticsearch/index/mapper/DocumentParserContext.java
index e20c592e46aab..5b3d55a7d9605 100644
--- a/server/src/main/java/org/elasticsearch/index/mapper/DocumentParserContext.java
+++ b/server/src/main/java/org/elasticsearch/index/mapper/DocumentParserContext.java
@@ -10,6 +10,7 @@
package org.elasticsearch.index.mapper;
import org.apache.lucene.document.Field;
+import org.apache.lucene.document.SortedDocValuesField;
import org.apache.lucene.document.StringField;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.util.BytesRef;
@@ -717,9 +718,13 @@ public final DocumentParserContext createNestedContext(NestedObjectMapper nested
// and inner hits to fail or yield incorrect results.
IndexableField idField = doc.getParent().getField(IdFieldMapper.NAME);
if (idField != null) {
- // We just need to store the id as indexed field, so that IndexWriter#deleteDocuments(term) can then
- // delete it when the root document is deleted too.
- doc.add(new StringField(IdFieldMapper.NAME, idField.binaryValue(), Field.Store.NO));
+ if (indexSettings().getMode() == IndexMode.LOGSDB) {
+ doc.add(SortedDocValuesField.indexedField(IdFieldMapper.NAME, idField.binaryValue()));
+ } else {
+ // We just need to store the id as indexed field, so that IndexWriter#deleteDocuments(term) can then
+ // delete it when the root document is deleted too.
+ doc.add(new StringField(IdFieldMapper.NAME, idField.binaryValue(), Field.Store.NO));
+ }
} else if (indexSettings().getMode() == IndexMode.TIME_SERIES) {
// For time series indices, the _id is generated from the _tsid, which in turn is generated from the values of the configured
// routing fields. At this point in document parsing, we can't guarantee that we've parsed all the routing fields yet, so the
diff --git a/server/src/main/java/org/elasticsearch/index/mapper/IdLoader.java b/server/src/main/java/org/elasticsearch/index/mapper/IdLoader.java
index 741252f98473b..079e21258deaf 100644
--- a/server/src/main/java/org/elasticsearch/index/mapper/IdLoader.java
+++ b/server/src/main/java/org/elasticsearch/index/mapper/IdLoader.java
@@ -18,6 +18,7 @@
import org.elasticsearch.cluster.metadata.DataStream;
import org.elasticsearch.cluster.routing.IndexRouting;
import org.elasticsearch.index.fieldvisitor.LeafStoredFieldLoader;
+import org.elasticsearch.search.lookup.Source;
import java.io.IOException;
import java.util.List;
@@ -25,7 +26,7 @@
/**
* Responsible for loading the _id from stored fields or for TSDB synthesizing the _id from the routing, _tsid and @timestamp fields.
*/
-public sealed interface IdLoader permits IdLoader.TsIdLoader, IdLoader.StoredIdLoader {
+public sealed interface IdLoader permits IdLoader.LogsdbLoader, IdLoader.TsIdLoader, IdLoader.StoredIdLoader {
/**
* @return returns an {@link IdLoader} instance the loads the _id from stored field.
@@ -41,12 +42,19 @@ static IdLoader createTsIdLoader(IndexRouting.ExtractFromSource indexRouting, Li
return new TsIdLoader(indexRouting, routingPaths);
}
- Leaf leaf(LeafStoredFieldLoader loader, LeafReader reader, int[] docIdsInLeaf) throws IOException;
+ /**
+ * @return returns an {@link IdLoader} instance that loads _id from doc values
+ */
+ static IdLoader createDocValueIdLoader() {
+ return new LogsdbLoader();
+ }
+
+ Leaf leaf(SourceLoader.Leaf leafSourceLoader, LeafStoredFieldLoader loader, LeafReader reader, int[] docIdsInLeaf) throws IOException;
/**
* Returns a leaf instance for a leaf reader that returns the _id for segment level doc ids.
*/
- sealed interface Leaf permits StoredLeaf, TsIdLeaf {
+ sealed interface Leaf permits DocValueLeaf, SourceLeaf, StoredLeaf {
/**
* @param subDocId The segment level doc id for which the return the _id
@@ -66,7 +74,8 @@ final class TsIdLoader implements IdLoader {
this.indexRouting = indexRouting;
}
- public IdLoader.Leaf leaf(LeafStoredFieldLoader loader, LeafReader reader, int[] docIdsInLeaf) throws IOException {
+ public IdLoader.Leaf leaf(SourceLoader.Leaf leafSourceLoader, LeafStoredFieldLoader loader, LeafReader reader, int[] docIdsInLeaf)
+ throws IOException {
IndexRouting.ExtractFromSource.Builder[] builders = null;
if (indexRouting != null) {
builders = new IndexRouting.ExtractFromSource.Builder[docIdsInLeaf.length];
@@ -120,7 +129,32 @@ public IdLoader.Leaf leaf(LeafStoredFieldLoader loader, LeafReader reader, int[]
ids[i] = TsidExtractingIdFieldMapper.createId(routingHash, tsid, timestamp);
}
}
- return new TsIdLeaf(docIdsInLeaf, ids);
+ return new DocValueLeaf(docIdsInLeaf, ids);
+ }
+ }
+
+ final class LogsdbLoader implements IdLoader {
+ public IdLoader.Leaf leaf(SourceLoader.Leaf leafSourceLoader, LeafStoredFieldLoader loader, LeafReader reader, int[] docIdsInLeaf)
+ throws IOException {
+ try {
+ SortedDocValues idDocValues = DocValues.getSorted(reader, IdFieldMapper.NAME);
+ return loadDocValues(idDocValues, docIdsInLeaf);
+ } catch (IllegalStateException e) {
+ // if loaded from source-only snapshot, doc values will not be present. Need to load from _source
+ return new SourceLeaf(loader, leafSourceLoader);
+ }
+ }
+
+ private DocValueLeaf loadDocValues(SortedDocValues idDocValues, int[] docIdsInLeaf) throws IOException {
+ String[] ids = new String[docIdsInLeaf.length];
+ for (int i = 0; i < docIdsInLeaf.length; i++) {
+ int docId = docIdsInLeaf[i];
+ boolean found = idDocValues.advanceExact(docId);
+ assert found;
+ BytesRef id = idDocValues.lookupOrd(idDocValues.ordValue());
+ ids[i] = Uid.decodeId(id.bytes, id.offset, id.length);
+ }
+ return new DocValueLeaf(docIdsInLeaf, ids);
}
}
@@ -129,19 +163,20 @@ final class StoredIdLoader implements IdLoader {
public StoredIdLoader() {}
@Override
- public Leaf leaf(LeafStoredFieldLoader loader, LeafReader reader, int[] docIdsInLeaf) throws IOException {
+ public Leaf leaf(SourceLoader.Leaf leafSourceLoader, LeafStoredFieldLoader loader, LeafReader reader, int[] docIdsInLeaf)
+ throws IOException {
return new StoredLeaf(loader);
}
}
- final class TsIdLeaf implements Leaf {
+ final class DocValueLeaf implements Leaf {
private final String[] ids;
private final int[] docIdsInLeaf;
private int idx = -1;
- TsIdLeaf(int[] docIdsInLeaf, String[] ids) {
+ DocValueLeaf(int[] docIdsInLeaf, String[] ids) {
this.ids = ids;
this.docIdsInLeaf = docIdsInLeaf;
}
@@ -171,4 +206,25 @@ public String getId(int subDocId) {
}
}
+ final class SourceLeaf implements Leaf {
+
+ private final LeafStoredFieldLoader storedFieldLoader;
+ private final SourceLoader.Leaf sourceLoader;
+
+ SourceLeaf(LeafStoredFieldLoader storedFieldLoader, SourceLoader.Leaf sourceLoader) {
+ this.storedFieldLoader = storedFieldLoader;
+ this.sourceLoader = sourceLoader;
+ }
+
+ @Override
+ public String getId(int subDocId) {
+ try {
+ Source source = sourceLoader.source(storedFieldLoader, subDocId);
+ return (String) source.extractValue(IdFieldMapper.NAME, null);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
}
diff --git a/server/src/main/java/org/elasticsearch/index/mapper/LogsdbIdFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/LogsdbIdFieldMapper.java
new file mode 100644
index 0000000000000..a876e8248fc9f
--- /dev/null
+++ b/server/src/main/java/org/elasticsearch/index/mapper/LogsdbIdFieldMapper.java
@@ -0,0 +1,107 @@
+
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the "Elastic License
+ * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
+ * Public License v 1"; you may not use this file except in compliance with, at
+ * your election, the "Elastic License 2.0", the "GNU Affero General Public
+ * License v3.0 only", or the "Server Side Public License, v 1".
+ */
+
+package org.elasticsearch.index.mapper;
+
+import org.apache.lucene.document.SortedDocValuesField;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.util.BytesRef;
+import org.elasticsearch.index.query.SearchExecutionContext;
+
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * A mapper for the _id field.
+ */
+public class LogsdbIdFieldMapper extends IdFieldMapper {
+ public static final LogsdbIdFieldMapper INSTANCE = new LogsdbIdFieldMapper();
+
+ public LogsdbIdFieldMapper() {
+ super(new LogsdbIdFieldType());
+ }
+
+ @Override
+ public void preParse(DocumentParserContext context) {
+ if (context.sourceToParse().id() == null) {
+ throw new IllegalStateException("_id should have been set on the coordinating node");
+ }
+ context.id(context.sourceToParse().id());
+ BytesRef uidEncoded = Uid.encodeId(context.id());
+ context.doc().add(SortedDocValuesField.indexedField(fieldType().name(), uidEncoded));
+ }
+
+ @Override
+ public String documentDescription(DocumentParserContext context) {
+ return "logsdb document with id '" + context.sourceToParse().id() + "'";
+ }
+
+ @Override
+ public String documentDescription(ParsedDocument parsedDocument) {
+ return "[" + parsedDocument.id() + "]";
+ }
+
+ @Override
+ public String reindexId(String id) {
+ return id;
+ }
+
+ protected static class LogsdbIdFieldType extends TermBasedFieldType {
+
+ public LogsdbIdFieldType() {
+ super(IdFieldMapper.NAME, false, false, true, TextSearchInfo.SIMPLE_MATCH_ONLY, Collections.emptyMap());
+ }
+
+ @Override
+ public String typeName() {
+ return IdFieldMapper.CONTENT_TYPE;
+ }
+
+ @Override
+ public boolean isSearchable() {
+ // searchable, but probably not fast
+ return true;
+ }
+
+ @Override
+ public boolean isAggregatable() {
+ return false;
+ }
+
+ @Override
+ public Query termsQuery(Collection> values, SearchExecutionContext context) {
+ var bytesRefs = values.stream().map(LogsdbIdFieldType::encode).map(this::indexedValueForSearch).toList();
+ return SortedDocValuesField.newSlowSetQuery(name(), bytesRefs);
+ }
+
+ @Override
+ public Query termQuery(Object value, SearchExecutionContext context) {
+ return SortedDocValuesField.newSlowExactQuery(name(), indexedValueForSearch(encode(value)));
+ }
+
+ @Override
+ public BlockLoader blockLoader(BlockLoaderContext blContext) {
+ return new BlockDocValuesReader.IdDocValuesBlockLoader(IdFieldMapper.NAME);
+ }
+
+ @Override
+ public ValueFetcher valueFetcher(SearchExecutionContext context, String format) {
+ // TODO can this be done somehow?
+ throw new UnsupportedOperationException("logsdb id cannot be fetched by values since only using doc values");
+ }
+
+ private static BytesRef encode(Object idObject) {
+ if (idObject instanceof BytesRef) {
+ idObject = ((BytesRef) idObject).utf8ToString();
+ }
+ return Uid.encodeId(idObject.toString());
+ }
+ }
+}
diff --git a/server/src/main/java/org/elasticsearch/index/mapper/ParsedDocument.java b/server/src/main/java/org/elasticsearch/index/mapper/ParsedDocument.java
index f2ddf38fe4357..e4037a9c182af 100644
--- a/server/src/main/java/org/elasticsearch/index/mapper/ParsedDocument.java
+++ b/server/src/main/java/org/elasticsearch/index/mapper/ParsedDocument.java
@@ -78,6 +78,7 @@ public static ParsedDocument deleteTombstone(String id) {
seqIdFields.addFields(document);
Field versionField = VersionFieldMapper.versionField();
document.add(versionField);
+ // TODO
document.add(IdFieldMapper.standardIdField(id));
return new ParsedDocument(
versionField,
diff --git a/server/src/main/java/org/elasticsearch/index/query/IdsQueryBuilder.java b/server/src/main/java/org/elasticsearch/index/query/IdsQueryBuilder.java
index bfaeca31730b9..e4d320585c1c1 100644
--- a/server/src/main/java/org/elasticsearch/index/query/IdsQueryBuilder.java
+++ b/server/src/main/java/org/elasticsearch/index/query/IdsQueryBuilder.java
@@ -143,6 +143,7 @@ protected Query doToQuery(SearchExecutionContext context) throws IOException {
if (idField == null || ids.isEmpty()) {
throw new IllegalStateException("Rewrite first");
}
+ // TODO
return idField.termsQuery(new ArrayList<>(ids), context);
}
diff --git a/server/src/main/java/org/elasticsearch/search/DefaultSearchContext.java b/server/src/main/java/org/elasticsearch/search/DefaultSearchContext.java
index c2b526128a9bc..f4ac0d6c71919 100644
--- a/server/src/main/java/org/elasticsearch/search/DefaultSearchContext.java
+++ b/server/src/main/java/org/elasticsearch/search/DefaultSearchContext.java
@@ -972,6 +972,8 @@ public IdLoader newIdLoader() {
}
}
return IdLoader.createTsIdLoader(indexRouting, routingPaths);
+ } else if (indexService.getIndexSettings().getMode() == IndexMode.LOGSDB) {
+ return IdLoader.createDocValueIdLoader();
} else {
return IdLoader.fromLeafStoredFieldLoader();
}
diff --git a/server/src/main/java/org/elasticsearch/search/fetch/FetchPhase.java b/server/src/main/java/org/elasticsearch/search/fetch/FetchPhase.java
index d9a8ee72b47c0..f1b67764de439 100644
--- a/server/src/main/java/org/elasticsearch/search/fetch/FetchPhase.java
+++ b/server/src/main/java/org/elasticsearch/search/fetch/FetchPhase.java
@@ -153,7 +153,7 @@ protected void setNextReader(LeafReaderContext ctx, int[] docsInLeaf) throws IOE
this.leafNestedDocuments = nestedDocuments.getLeafNestedDocuments(ctx);
this.leafStoredFieldLoader = storedFieldLoader.getLoader(ctx, docsInLeaf);
this.leafSourceLoader = sourceLoader.leaf(ctx.reader(), docsInLeaf);
- this.leafIdLoader = idLoader.leaf(leafStoredFieldLoader, ctx.reader(), docsInLeaf);
+ this.leafIdLoader = idLoader.leaf(leafSourceLoader, leafStoredFieldLoader, ctx.reader(), docsInLeaf);
fieldLookupProvider.setNextReader(ctx);
for (FetchSubPhaseProcessor processor : processors) {
processor.setNextReader(ctx);
diff --git a/server/src/test/java/org/elasticsearch/index/mapper/IdLoaderTests.java b/server/src/test/java/org/elasticsearch/index/mapper/IdLoaderTests.java
index 083efccceec16..702f2544da4a1 100644
--- a/server/src/test/java/org/elasticsearch/index/mapper/IdLoaderTests.java
+++ b/server/src/test/java/org/elasticsearch/index/mapper/IdLoaderTests.java
@@ -58,7 +58,7 @@ public void testSynthesizeIdSimple() throws Exception {
assertThat(indexReader.leaves(), hasSize(1));
LeafReader leafReader = indexReader.leaves().get(0).reader();
assertThat(leafReader.numDocs(), equalTo(3));
- var leaf = idLoader.leaf(null, leafReader, new int[] { 0, 1, 2 });
+ var leaf = idLoader.leaf(null, null, leafReader, new int[] { 0, 1, 2 });
// NOTE: time series data is ordered by (tsid, timestamp)
assertThat(leaf.getId(0), equalTo(expectedId(docs.get(2), routingHash)));
assertThat(leaf.getId(1), equalTo(expectedId(docs.get(0), routingHash)));
@@ -108,7 +108,7 @@ public void testSynthesizeIdMultipleSegments() throws Exception {
{
LeafReader leafReader = indexReader.leaves().get(0).reader();
assertThat(leafReader.numDocs(), equalTo(docs1.size()));
- var leaf = idLoader.leaf(null, leafReader, IntStream.range(0, docs1.size()).toArray());
+ var leaf = idLoader.leaf(null, null, leafReader, IntStream.range(0, docs1.size()).toArray());
for (int i = 0; i < docs1.size(); i++) {
assertThat(leaf.getId(i), equalTo(expectedId(docs1.get(i), routingHash)));
}
@@ -116,21 +116,21 @@ public void testSynthesizeIdMultipleSegments() throws Exception {
{
LeafReader leafReader = indexReader.leaves().get(1).reader();
assertThat(leafReader.numDocs(), equalTo(docs2.size()));
- var leaf = idLoader.leaf(null, leafReader, new int[] { 0, 3 });
+ var leaf = idLoader.leaf(null, null, leafReader, new int[] { 0, 3 });
assertThat(leaf.getId(0), equalTo(expectedId(docs2.get(0), routingHash)));
assertThat(leaf.getId(3), equalTo(expectedId(docs2.get(3), routingHash)));
}
{
LeafReader leafReader = indexReader.leaves().get(2).reader();
assertThat(leafReader.numDocs(), equalTo(docs3.size()));
- var leaf = idLoader.leaf(null, leafReader, new int[] { 1, 2 });
+ var leaf = idLoader.leaf(null, null, leafReader, new int[] { 1, 2 });
assertThat(leaf.getId(1), equalTo(expectedId(docs3.get(1), routingHash)));
assertThat(leaf.getId(2), equalTo(expectedId(docs3.get(2), routingHash)));
}
{
LeafReader leafReader = indexReader.leaves().get(2).reader();
assertThat(leafReader.numDocs(), equalTo(docs3.size()));
- var leaf = idLoader.leaf(null, leafReader, new int[] { 3 });
+ var leaf = idLoader.leaf(null, null, leafReader, new int[] { 3 });
expectThrows(IllegalArgumentException.class, () -> leaf.getId(0));
}
};
@@ -168,7 +168,7 @@ public void testSynthesizeIdRandom() throws Exception {
assertThat(indexReader.leaves(), hasSize(1));
LeafReader leafReader = indexReader.leaves().get(0).reader();
assertThat(leafReader.numDocs(), equalTo(randomDocs.size()));
- var leaf = idLoader.leaf(null, leafReader, IntStream.range(0, randomDocs.size()).toArray());
+ var leaf = idLoader.leaf(null, null, leafReader, IntStream.range(0, randomDocs.size()).toArray());
for (int i = 0; i < randomDocs.size(); i++) {
String actualId = leaf.getId(i);
assertTrue("docId=" + i + " id=" + actualId, expectedIDs.remove(actualId));
diff --git a/x-pack/plugin/logsdb/src/internalClusterTest/java/org/elasticsearch/xpack/logsdb/LogsIdIT.java b/x-pack/plugin/logsdb/src/internalClusterTest/java/org/elasticsearch/xpack/logsdb/LogsIdIT.java
new file mode 100644
index 0000000000000..1e3c32c7efc93
--- /dev/null
+++ b/x-pack/plugin/logsdb/src/internalClusterTest/java/org/elasticsearch/xpack/logsdb/LogsIdIT.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.xpack.logsdb;
+
+import org.elasticsearch.action.DocWriteRequest;
+import org.elasticsearch.action.admin.indices.flush.FlushRequest;
+import org.elasticsearch.action.admin.indices.refresh.RefreshRequest;
+import org.elasticsearch.action.admin.indices.template.put.TransportPutComposableIndexTemplateAction;
+import org.elasticsearch.action.bulk.BulkRequest;
+import org.elasticsearch.action.get.GetRequest;
+import org.elasticsearch.action.index.IndexRequest;
+import org.elasticsearch.action.search.SearchRequest;
+import org.elasticsearch.cluster.metadata.ComposableIndexTemplate;
+import org.elasticsearch.cluster.metadata.Template;
+import org.elasticsearch.common.compress.CompressedXContent;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.time.DateFormatter;
+import org.elasticsearch.common.time.FormatNames;
+import org.elasticsearch.datastreams.DataStreamsPlugin;
+import org.elasticsearch.index.query.TermQueryBuilder;
+import org.elasticsearch.license.LicenseSettings;
+import org.elasticsearch.plugins.Plugin;
+import org.elasticsearch.search.SearchHit;
+import org.elasticsearch.search.builder.SearchSourceBuilder;
+import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
+import org.elasticsearch.test.ESSingleNodeTestCase;
+import org.elasticsearch.test.InternalSettingsPlugin;
+import org.elasticsearch.xcontent.XContentType;
+import org.elasticsearch.xpack.core.XPackPlugin;
+
+import java.io.IOException;
+import java.time.Instant;
+import java.util.Collection;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.ExecutionException;
+
+import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertResponse;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.notNullValue;
+
+public class LogsIdIT extends ESSingleNodeTestCase {
+
+ public static final String MAPPING_TEMPLATE = """
+ {
+ "_doc":{
+ "properties": {
+ "@timestamp" : {
+ "type": "date"
+ },
+ "message": {
+ "type": "keyword"
+ },
+ "k8s": {
+ "properties": {
+ "pod": {
+ "properties": {
+ "uid": {
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }""";
+
+ private static final String DOC = """
+ {
+ "@timestamp": "$time",
+ "message": "$pod",
+ "k8s": {
+ "pod": {
+ "name": "dog",
+ "uid":"$uuid",
+ "ip": "10.10.55.3",
+ "network": {
+ "tx": 1434595272,
+ "rx": 530605511
+ }
+ }
+ }
+ }
+ """;
+
+ @Override
+ protected Collection> getPlugins() {
+ return List.of(InternalSettingsPlugin.class, XPackPlugin.class, LogsDBPlugin.class, DataStreamsPlugin.class);
+ }
+
+ @Override
+ protected Settings nodeSettings() {
+ return Settings.builder()
+ .put(super.nodeSettings())
+ .put("cluster.logsdb.enabled", "true")
+ .put(LicenseSettings.SELF_GENERATED_LICENSE_TYPE.getKey(), "trial")
+ .build();
+ }
+
+ public void testGetByGeneratedId() throws Exception {
+ String dataStreamName = "k8s";
+ createTemplate(dataStreamName);
+
+ Instant time = Instant.now();
+ BulkRequest bulkRequest = new BulkRequest(dataStreamName);
+ int numDocs = randomIntBetween(16, 256);
+ for (int j = 0; j < numDocs; j++) {
+ var indexRequest = new IndexRequest(dataStreamName).opType(DocWriteRequest.OpType.CREATE);
+ indexRequest.source(
+ DOC.replace("$time", formatInstant(time)).replace("$uuid", UUID.randomUUID().toString()).replace("$pod", "pod-" + j),
+ XContentType.JSON
+ );
+ bulkRequest.add(indexRequest);
+ time = time.plusMillis(1);
+ }
+ var bulkResponse = client().bulk(bulkRequest).actionGet();
+ assertThat(bulkResponse.hasFailures(), equalTo(false));
+ var indexName = bulkResponse.getItems()[0].getIndex();
+ client().admin().indices().refresh(new RefreshRequest(dataStreamName)).actionGet();
+
+ // Check the search api can synthesize _id
+ var searchRequest = new SearchRequest(dataStreamName);
+ searchRequest.source().trackTotalHits(true);
+ assertResponse(client().search(searchRequest), searchResponse -> {
+ assertThat(searchResponse.getHits().getTotalHits().value(), equalTo((long) numDocs));
+
+ for (int i = 0; i < searchResponse.getHits().getHits().length; i++) {
+ SearchHit hit = searchResponse.getHits().getHits()[i];
+ String id = hit.getId();
+ assertThat(id, notNullValue());
+
+ // Check that the _id is gettable:
+ var getRequest = new GetRequest(indexName).id(id);
+ var getResponse = client().get(getRequest).actionGet();
+ assertThat(getResponse.isExists(), equalTo(true));
+ assertThat(getResponse.getId(), equalTo(id));
+ }
+ });
+ }
+
+ public void testGetByProvidedID() throws Exception {
+ var indexName = "test-name";
+ createIndex(indexName, Settings.builder().put("index.mode", "logsdb").build());
+ Instant time = Instant.now();
+ BulkRequest bulkRequest = new BulkRequest(indexName);
+ int numDocs = randomIntBetween(16, 256);
+ for (int j = 0; j < numDocs; j++) {
+ var indexRequest = new IndexRequest(indexName).opType(DocWriteRequest.OpType.INDEX).id("id-" + j);
+ indexRequest.source(
+ DOC.replace("$time", formatInstant(time)).replace("$uuid", UUID.randomUUID().toString()).replace("$pod", "pod-" + j),
+ XContentType.JSON
+ );
+ bulkRequest.add(indexRequest);
+ time = time.plusMillis(1);
+ }
+ var bulkResponse = client().bulk(bulkRequest).actionGet();
+ assertThat(bulkResponse.hasFailures(), equalTo(false));
+ client().admin().indices().refresh(new RefreshRequest(indexName)).actionGet();
+
+ if (randomBoolean()) {
+ flush(indexName, randomBoolean());
+ }
+
+ // Check the search api can synthesize _id
+ var searchRequest = new SearchRequest(indexName);
+ searchRequest.source().trackTotalHits(true);
+ assertResponse(client().search(searchRequest), searchResponse -> {
+ assertThat(searchResponse.getHits().getTotalHits().value(), equalTo((long) numDocs));
+
+ for (int i = 0; i < searchResponse.getHits().getHits().length; i++) {
+ SearchHit hit = searchResponse.getHits().getHits()[i];
+ String id = hit.getId();
+ String numPart = id.split("-")[1];
+ assertThat(id, notNullValue());
+ // check got correct doc in search response
+ var pod = (String) hit.getSourceAsMap().get("message");
+ assertThat(pod, equalTo("pod-" + numPart));
+ }
+ });
+
+ // test get with the provided IDs
+ for (int i = 0; i < numDocs; i++) {
+ String id = "id-" + i;
+ var getRequest = new GetRequest(indexName).id(id).fetchSourceContext(FetchSourceContext.FETCH_SOURCE);
+ var getResponse = client().get(getRequest).actionGet();
+ assertThat(getResponse.isExists(), equalTo(true));
+ assertThat(getResponse.getId(), equalTo(id));
+ assertThat(getResponse.getSourceAsMap().get("message"), equalTo("pod-" + i));
+ }
+ }
+
+ public void testMatchByProvidedID() throws Exception {
+ var indexName = "test-name";
+ createIndex(indexName, Settings.builder().put("index.mode", "logsdb").build());
+ Instant time = Instant.now();
+ BulkRequest bulkRequest = new BulkRequest(indexName);
+ int numDocs = randomIntBetween(16, 256);
+ for (int j = 0; j < numDocs; j++) {
+ var indexRequest = new IndexRequest(indexName).opType(DocWriteRequest.OpType.INDEX).id("id-" + j);
+ indexRequest.source(
+ DOC.replace("$time", formatInstant(time)).replace("$uuid", UUID.randomUUID().toString()).replace("$pod", "pod-" + j),
+ XContentType.JSON
+ );
+ bulkRequest.add(indexRequest);
+ time = time.plusMillis(1);
+ }
+ var bulkResponse = client().bulk(bulkRequest).actionGet();
+ assertThat(bulkResponse.hasFailures(), equalTo(false));
+ client().admin().indices().refresh(new RefreshRequest(indexName)).actionGet();
+
+ for (int i = 0; i < numDocs; i++) {
+ String id = "id-" + i;
+ var searchRequest = new SearchRequest(indexName);
+ searchRequest.source(new SearchSourceBuilder().query(new TermQueryBuilder("_id", id)).size(10));
+ var searchResponse = client().search(searchRequest).actionGet();
+ assertThat(searchResponse.getHits().getTotalHits().value(), equalTo((long) 1));
+ assertThat(searchResponse.getHits().getHits()[0].getSourceAsMap().get("message"), equalTo("pod-" + i));
+ }
+ }
+
+ // public void testDeleteByProvidedID() throws Exception {
+ // var indexName = "test-name";
+ // createIndex(indexName, Settings.builder().put("index.mode", "logsdb").build());
+ // Instant time = Instant.now();
+ // BulkRequest bulkRequest = new BulkRequest(indexName);
+ // int numDocs = randomIntBetween(16, 256);
+ // for (int j = 0; j < numDocs; j++) {
+ // var indexRequest = new IndexRequest(indexName).opType(DocWriteRequest.OpType.INDEX).id("id-" + j);
+ // indexRequest.source(
+ // DOC.replace("$time", formatInstant(time)).replace("$uuid", UUID.randomUUID().toString()).replace("$pod", "pod-" + j),
+ // XContentType.JSON
+ // );
+ // bulkRequest.add(indexRequest);
+ // time = time.plusMillis(1);
+ // }
+ // var bulkResponse = client().bulk(bulkRequest).actionGet();
+ // assertThat(bulkResponse.hasFailures(), is(false));
+ // client().admin().indices().refresh(new RefreshRequest(indexName)).actionGet();
+ //
+ // if (randomBoolean()) {
+ // flush(indexName, randomBoolean());
+ // }
+ //
+ // var searchRequest = new SearchRequest(indexName);
+ // searchRequest.source().trackTotalHits(true);
+ // assertResponse(client().search(searchRequest), searchResponse -> {
+ // assertThat(searchResponse.getHits().getTotalHits().value(), equalTo((long) numDocs));
+ // });
+ //
+ // // test get with the provided IDs
+ // for (int i = 0; i < numDocs; i++) {
+ // DeleteResponse deleteResponse = client().prepareDelete(indexName, "id-" + i).get();
+ // assertThat(deleteResponse.status(), equalTo(RestStatus.OK));
+ // }
+ //
+ // assertThat(client().prepareSearch(indexName).get().getHits().getTotalHits().value(), equalTo(0));
+ // }
+
+ private void createTemplate(String dataStreamName) throws IOException {
+ var putTemplateRequest = new TransportPutComposableIndexTemplateAction.Request("id");
+ putTemplateRequest.indexTemplate(
+ ComposableIndexTemplate.builder()
+ .indexPatterns(List.of(dataStreamName + "*"))
+ .template(
+ new Template(
+ indexSettings(4, 0).put("index.mode", "logsdb").put("index.sort.field", "message,k8s.pod.uid,@timestamp").build(),
+ new CompressedXContent(MAPPING_TEMPLATE),
+ null
+ )
+ )
+ .dataStreamTemplate(new ComposableIndexTemplate.DataStreamTemplate(false, false))
+ .build()
+ );
+ client().execute(TransportPutComposableIndexTemplateAction.TYPE, putTemplateRequest).actionGet();
+ }
+
+ static String formatInstant(Instant instant) {
+ return DateFormatter.forPattern(FormatNames.STRICT_DATE_OPTIONAL_TIME.getName()).format(instant);
+ }
+
+ public void flush(String index, boolean force) throws IOException, ExecutionException, InterruptedException {
+ logger.info("flushing index {} force={}", index, force);
+ FlushRequest flushRequest = new FlushRequest(index).force(force);
+ assertResponse(client().admin().indices().flush(flushRequest), response -> { assertEquals(0, response.getFailedShards()); });
+ }
+
+}
diff --git a/x-pack/plugin/logsdb/src/javaRestTest/java/org/elasticsearch/xpack/logsdb/LogsdbRestIT.java b/x-pack/plugin/logsdb/src/javaRestTest/java/org/elasticsearch/xpack/logsdb/LogsdbRestIT.java
index b4ebab693b591..7d5ae42d1351a 100644
--- a/x-pack/plugin/logsdb/src/javaRestTest/java/org/elasticsearch/xpack/logsdb/LogsdbRestIT.java
+++ b/x-pack/plugin/logsdb/src/javaRestTest/java/org/elasticsearch/xpack/logsdb/LogsdbRestIT.java
@@ -340,4 +340,107 @@ public void testLogsdbDefaultWithRecoveryUseSyntheticSource() throws IOException
assertNull(settings.get("index.mapping.source.mode"));
assertEquals("true", settings.get(IndexSettings.LOGSDB_SORT_ON_HOST_NAME.getKey()));
}
+
+ public void testGetById() throws IOException {
+ String mappings = """
+ {
+ "runtime": {
+ "message_length": {
+ "type": "long"
+ },
+ "log.offset": {
+ "type": "long"
+ }
+ },
+ "dynamic": false,
+ "properties": {
+ "@timestamp": {
+ "type": "date"
+ },
+ "log" : {
+ "properties": {
+ "level": {
+ "type": "keyword"
+ },
+ "file": {
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ }
+ """;
+ String indexName = "test-foo";
+ createIndex(indexName, Settings.builder().put("index.mode", "logsdb").build(), mappings);
+
+ int numDocs = 10;
+ var sb = new StringBuilder();
+ var now = Instant.now();
+
+ var expectedMinTimestamp = now;
+ for (int i = 0; i < numDocs; i++) {
+ String level = randomBoolean() ? "info" : randomBoolean() ? "warning" : randomBoolean() ? "error" : "fatal";
+ String msg = randomAlphaOfLength(20);
+ String path = randomAlphaOfLength(8);
+ String messageLength = Integer.toString(msg.length());
+ String offset = Integer.toString(randomNonNegativeInt());
+ sb.append("{ \"index\": { \"_id\": \"$id\" } }".replace("$id", "some_id_" + i)).append('\n');
+ if (randomBoolean()) {
+ sb.append(
+ """
+ {"@timestamp":"$now","message":"$msg","message_length":$l,"file":{"level":"$level","offset":5,"file":"$path"}}
+ """.replace("$now", formatInstant(now))
+ .replace("$level", level)
+ .replace("$msg", msg)
+ .replace("$path", path)
+ .replace("$l", messageLength)
+ .replace("$o", offset)
+ );
+ } else {
+ sb.append("""
+ {"@timestamp": "$now", "message": "$msg", "message_length": $l}
+ """.replace("$now", formatInstant(now)).replace("$msg", msg).replace("$l", messageLength));
+ }
+ sb.append('\n');
+ if (i != numDocs - 1) {
+ now = now.plusSeconds(1);
+ }
+ }
+ var expectedMaxTimestamp = now;
+
+ var bulkRequest = new Request("POST", "/" + indexName + "/_bulk");
+ bulkRequest.setJsonEntity(sb.toString());
+ bulkRequest.addParameter("refresh", "true");
+ var bulkResponse = client().performRequest(bulkRequest);
+ var bulkResponseBody = responseAsMap(bulkResponse);
+ assertThat(bulkResponseBody, Matchers.hasEntry("errors", false));
+
+ var forceMergeRequest = new Request("POST", "/" + indexName + "/_forcemerge");
+ forceMergeRequest.addParameter("max_num_segments", "1");
+ var forceMergeResponse = client().performRequest(forceMergeRequest);
+ assertOK(forceMergeResponse);
+
+ var getRequest = new Request("GET", "/" + indexName + "/_doc/" + "some_id_4");
+ var getResponse = client().performRequest(getRequest);
+ logger.info(getRequest);
+ assertOK(getResponse);
+
+ // var searchRequest = new Request("POST", "/" + indexName + "/_search");
+ //
+ // searchRequest.setJsonEntity("""
+ // {
+ // "query": {
+ // "match": {
+ // "_id": "some_id_5"
+ // }
+ // }
+ // }
+ // """);
+ // var searchResponse = client().performRequest(searchRequest);
+ // assertOK(searchResponse);
+ // var searchResponseBody = responseAsMap(searchResponse);
+ // int totalHits = (int) XContentMapValues.extractValue("hits.total.value", searchResponseBody);
+ // assertThat(totalHits, equalTo(1));
+ }
+
}