Skip to content
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
d0c9a2e
copy patternedtext directory from prototype
parkertimmins Jun 6, 2025
78ef582
Remove timestamp and optimized args
parkertimmins Jun 6, 2025
6bb95ac
copy tests from prototype
parkertimmins Jun 11, 2025
ad728f3
Remove all token type specific test cases
parkertimmins Jun 11, 2025
a97bbc4
Fix bug introduced while moving to all shared args
parkertimmins Jun 11, 2025
3a0e349
index original value
parkertimmins Jun 11, 2025
cda6b0a
Remove type specific handling from tests
parkertimmins Jun 11, 2025
30ae746
[CI] Auto commit changes from spotless
Jun 11, 2025
387757d
A few more changes from prototype
parkertimmins Jun 11, 2025
03b9fde
add messages with arguments to yaml tests
parkertimmins Jun 11, 2025
f9344bf
More yaml test updates
parkertimmins Jun 12, 2025
e4d4830
[CI] Auto commit changes from spotless
Jun 12, 2025
fca0f83
Change to BinaryDocValues
parkertimmins Jun 13, 2025
f9b030b
[CI] Auto commit changes from spotless
Jun 13, 2025
4429474
address some pr feedback
parkertimmins Jun 13, 2025
10147ad
remove unneeded change
parkertimmins Jun 13, 2025
4a3ba41
Merge branch 'main' into parker/pattern-text-initial-version
parkertimmins Jun 18, 2025
b7450c2
Make patterned_text not aggregatable
parkertimmins Jun 18, 2025
8efac46
apply patch to simplify fielddata
parkertimmins Jun 19, 2025
cd2f9aa
Fixes to fielddata and valuefetcher
parkertimmins Jun 19, 2025
906ca11
removed wrong yaml test
parkertimmins Jun 20, 2025
0815760
move patternedtext into logsdb
parkertimmins Jun 21, 2025
6856d2d
more changes to move into logsdb
parkertimmins Jun 21, 2025
4b63ed0
Add patterned_text feature_flag
parkertimmins Jun 24, 2025
fa022e6
Update docs/changelog/129292.yaml
parkertimmins Jun 24, 2025
8cd70fd
Merge branch 'main' into parker/pattern-text-initial-version
parkertimmins Jun 24, 2025
526fef1
Delete docs/changelog/129292.yaml
parkertimmins Jun 24, 2025
39e9d88
Throw exception if multiple messages in document
parkertimmins Jun 24, 2025
eb4212f
[CI] Auto commit changes from spotless
Jun 24, 2025
e026a5c
disable array tests since multiple values disallowed
parkertimmins Jun 24, 2025
a2bc5fa
Add doc values tests
parkertimmins Jun 24, 2025
f0da074
[CI] Auto commit changes from spotless
Jun 24, 2025
4e0c337
add comment
parkertimmins Jun 25, 2025
109afd4
Merge branch 'main' into parker/pattern-text-initial-version
parkertimmins Jun 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions modules/mapper-extras/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@
requires org.apache.lucene.core;
requires org.apache.lucene.memory;
requires org.apache.lucene.queries;

exports org.elasticsearch.index.mapper.extras;
}
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ public MatchOnlyTextFieldType(
super(name, true, false, false, tsi, meta);
this.indexAnalyzer = Objects.requireNonNull(indexAnalyzer);
this.textFieldType = new TextFieldType(name, isSyntheticSource);
this.originalName = isSyntheticSource ? name() + "._original" : null;
this.originalName = isSyntheticSource ? name + "._original" : null;
}

public MatchOnlyTextFieldType(String name) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ private FieldContext contextBuilders(
if (fieldNameContainsWildcards) {
if (fieldType.typeName().equals(TextFieldMapper.CONTENT_TYPE) == false
&& fieldType.typeName().equals(KeywordFieldMapper.CONTENT_TYPE) == false
&& fieldType.typeName().equals("match_only_text") == false) {
&& fieldType.typeName().equals("match_only_text") == false
&& fieldType.typeName().equals("patterned_text") == false) {
continue;
}
if (highlighter.canHighlight(fieldType) == false) {
Expand Down
30 changes: 30 additions & 0 deletions x-pack/plugin/mapper-patterned-text/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* 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.
*/

apply plugin: 'elasticsearch.internal-es-plugin'
apply plugin: 'elasticsearch.internal-yaml-rest-test'

esplugin {
name = 'patterned-text'
description = 'Module for the patterned_text field type.'
classname ='org.elasticsearch.xpack.patternedtext.PatternedTextMapperPlugin'
extendedPlugins = ['x-pack-core']
}
base {
archivesName = 'x-pack-patterned-text'
}

dependencies {
compileOnly project(path: xpackModule('core'))
implementation project(':modules:mapper-extras')
}

if (buildParams.getSnapshotBuild() == false) {
tasks.named("test").configure {
systemProperty 'es.index_mode_feature_flag_registered', 'true'
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* 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.patternedtext;

import org.apache.lucene.index.BinaryDocValues;
import org.apache.lucene.index.DocValues;
import org.apache.lucene.index.LeafReader;
import org.apache.lucene.index.SortedSetDocValues;
import org.apache.lucene.util.BytesRef;

import java.io.IOException;

public class PatternedTextDocValues extends BinaryDocValues {
private final SortedSetDocValues templateDocValues;
private final SortedSetDocValues argsDocValues;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be SortedDocValues. Since there is only args value per document? (all values are concatenated?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, there should only be one template and one concatenated args per doc


PatternedTextDocValues(SortedSetDocValues templateDocValues, SortedSetDocValues argsDocValues) {
this.templateDocValues = templateDocValues;
this.argsDocValues = argsDocValues;
}

static PatternedTextDocValues from(LeafReader leafReader, String templateFieldName, String argsFieldName) throws IOException {
SortedSetDocValues templateDocValues = DocValues.getSortedSet(leafReader, templateFieldName);
if (templateDocValues.getValueCount() == 0) {
return null;
}

SortedSetDocValues argsDocValues = DocValues.getSortedSet(leafReader, argsFieldName);
return new PatternedTextDocValues(templateDocValues, argsDocValues);
}

private String getNextStringValue() throws IOException {
assert templateDocValues.docValueCount() == 1;
String template = templateDocValues.lookupOrd(templateDocValues.nextOrd()).utf8ToString();
int argsCount = PatternedTextValueProcessor.countArgs(template);
if (argsCount > 0) {
assert argsDocValues.docValueCount() == 1;
var mergedArgs = argsDocValues.lookupOrd(argsDocValues.nextOrd());
var args = PatternedTextValueProcessor.decodeRemainingArgs(mergedArgs.utf8ToString());
return PatternedTextValueProcessor.merge(new PatternedTextValueProcessor.Parts(template, args));
} else {
return template;
}
}

@Override
public BytesRef binaryValue() throws IOException {
return new BytesRef(getNextStringValue());
}

@Override
public boolean advanceExact(int i) throws IOException {
argsDocValues.advanceExact(i);
// If template has a value, then message has a value. We don't have to check args here, since there may not be args for the doc
return templateDocValues.advanceExact(i);
}

@Override
public int docID() {
return templateDocValues.docID();
}

@Override
public int nextDoc() throws IOException {
int templateNext = templateDocValues.nextDoc();
int argsNext = argsDocValues.nextDoc();
assert templateNext == argsNext;
return templateNext;
}

@Override
public int advance(int i) throws IOException {
int templateAdvance = templateDocValues.advance(i);
int argAdvance = argsDocValues.advance(i);
assert templateAdvance == argAdvance;
return templateAdvance;
}

@Override
public long cost() {
return templateDocValues.cost() + argsDocValues.cost();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/*
* 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.patternedtext;

import org.apache.lucene.document.Field;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.document.SortedSetDocValuesField;
import org.apache.lucene.index.IndexOptions;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.index.IndexVersion;
import org.elasticsearch.index.analysis.IndexAnalyzers;
import org.elasticsearch.index.analysis.NamedAnalyzer;
import org.elasticsearch.index.mapper.CompositeSyntheticFieldLoader;
import org.elasticsearch.index.mapper.DocumentParserContext;
import org.elasticsearch.index.mapper.FieldMapper;
import org.elasticsearch.index.mapper.MapperBuilderContext;
import org.elasticsearch.index.mapper.TextParams;
import org.elasticsearch.index.mapper.TextSearchInfo;

import java.io.IOException;
import java.util.Map;

/**
* A {@link FieldMapper} that assigns every document the same value.
*/
public class PatternedTextFieldMapper extends FieldMapper {

public static class Defaults {
public static final FieldType FIELD_TYPE;

static {
final FieldType ft = new FieldType();
ft.setTokenized(true);
ft.setStored(false);
ft.setStoreTermVectors(false);
ft.setOmitNorms(true);
ft.setIndexOptions(IndexOptions.DOCS);
FIELD_TYPE = freezeAndDeduplicateFieldType(ft);
}
}

public static class Builder extends FieldMapper.Builder {

private final IndexVersion indexCreatedVersion;

private final Parameter<Map<String, String>> meta = Parameter.metaParam();

private final TextParams.Analyzers analyzers;

public Builder(String name, IndexVersion indexCreatedVersion, IndexAnalyzers indexAnalyzers) {
super(name);
this.indexCreatedVersion = indexCreatedVersion;
this.analyzers = new TextParams.Analyzers(
indexAnalyzers,
m -> ((PatternedTextFieldMapper) m).indexAnalyzer,
m -> ((PatternedTextFieldMapper) m).positionIncrementGap,
indexCreatedVersion
);
}

@Override
protected Parameter<?>[] getParameters() {
return new Parameter<?>[] { meta };
}

private PatternedTextFieldType buildFieldType(MapperBuilderContext context) {
NamedAnalyzer searchAnalyzer = analyzers.getSearchAnalyzer();
NamedAnalyzer searchQuoteAnalyzer = analyzers.getSearchQuoteAnalyzer();
NamedAnalyzer indexAnalyzer = analyzers.getIndexAnalyzer();
TextSearchInfo tsi = new TextSearchInfo(Defaults.FIELD_TYPE, null, searchAnalyzer, searchQuoteAnalyzer);
return new PatternedTextFieldType(
context.buildFullName(leafName()),
tsi,
indexAnalyzer,
context.isSourceSynthetic(),
meta.getValue()
);
}

@Override
public PatternedTextFieldMapper build(MapperBuilderContext context) {
return new PatternedTextFieldMapper(leafName(), buildFieldType(context), builderParams(this, context), this);
}
}

public static final TypeParser PARSER = new TypeParser((n, c) -> new Builder(n, c.indexVersionCreated(), c.getIndexAnalyzers()));

private final IndexVersion indexCreatedVersion;
private final IndexAnalyzers indexAnalyzers;
private final NamedAnalyzer indexAnalyzer;
private final int positionIncrementGap;
private final FieldType fieldType;

private PatternedTextFieldMapper(
String simpleName,
PatternedTextFieldType mappedFieldPatternedTextFieldType,
BuilderParams builderParams,
Builder builder
) {
super(simpleName, mappedFieldPatternedTextFieldType, builderParams);
assert mappedFieldPatternedTextFieldType.getTextSearchInfo().isTokenized();
assert mappedFieldPatternedTextFieldType.hasDocValues() == false;
this.fieldType = Defaults.FIELD_TYPE;
this.indexCreatedVersion = builder.indexCreatedVersion;
this.indexAnalyzers = builder.analyzers.indexAnalyzers;
this.indexAnalyzer = builder.analyzers.getIndexAnalyzer();
this.positionIncrementGap = builder.analyzers.positionIncrementGap.getValue();
}

@Override
public Map<String, NamedAnalyzer> indexAnalyzers() {
return Map.of(mappedFieldType.name(), indexAnalyzer);
}

@Override
public FieldMapper.Builder getMergeBuilder() {
return new Builder(leafName(), indexCreatedVersion, indexAnalyzers).init(this);
}

@Override
protected void parseCreateField(DocumentParserContext context) throws IOException {
final String value = context.parser().textOrNull();
if (value == null) {
return;
}

// Parse template and args.
PatternedTextValueProcessor.Parts parts = PatternedTextValueProcessor.split(value);

// Add index on original value
context.doc().add(new Field(fieldType().name(), value, fieldType));

// Add template doc_values
context.doc().add(new SortedSetDocValuesField(fieldType().templateFieldName(), new BytesRef(parts.template())));

// Add args doc_values
if (parts.args().isEmpty() == false) {
String remainingArgs = PatternedTextValueProcessor.encodeRemainingArgs(parts);
context.doc().add(new SortedSetDocValuesField(fieldType().argsFieldName(), new BytesRef(remainingArgs)));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like an earlier comment, both SortedSetDocValuesField usages can be replaced here with SortedDocValuesField

}
}

@Override
protected String contentType() {
return PatternedTextFieldType.CONTENT_TYPE;
}

@Override
public PatternedTextFieldType fieldType() {
return (PatternedTextFieldType) super.fieldType();
}

@Override
protected SyntheticSourceSupport syntheticSourceSupport() {
return new SyntheticSourceSupport.Native(
() -> new CompositeSyntheticFieldLoader(
leafName(),
fullPath(),
new PatternedTextSyntheticFieldLoaderLayer(fieldType().name(), fieldType().templateFieldName(), fieldType().argsFieldName())
)
);
}
}
Loading