Skip to content

Commit 11b7194

Browse files
committed
Initial set of tests for index time mappings - both standard and runtime; no query time runtime mapping tests
1 parent d08a61d commit 11b7194

File tree

3 files changed

+270
-3
lines changed

3 files changed

+270
-3
lines changed

server/src/main/java/org/elasticsearch/index/mapper/ServerlessRootObjectMapperNamespaceValidator.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ public class ServerlessRootObjectMapperNamespaceValidator implements RootObjectM
2121
private static final String SERVERLESS_RESERVED_NAMESPACE = "_project";
2222

2323
/**
24-
*
24+
* Throws an error if a top level field with {@code SERVERLESS_RESERVED_NAMESPACE} is found.
25+
* If subobjects = false, then it also checks for field names starting with "_project."
2526
* @param subobjects if null, it will be interpreted as subobjects != ObjectMapper.Subobjects.ENABLED
26-
* @param name
27+
* @param name field name to evaluation
2728
*/
2829
@Override
2930
public void validateNamespace(@Nullable ObjectMapper.Subobjects subobjects, String name) {

server/src/test/java/org/elasticsearch/index/mapper/RootObjectMapperTests.java

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
package org.elasticsearch.index.mapper;
1111

12+
import org.elasticsearch.ExceptionsHelper;
1213
import org.elasticsearch.common.Strings;
1314
import org.elasticsearch.common.xcontent.XContentHelper;
1415
import org.elasticsearch.core.CheckedConsumer;
@@ -370,6 +371,250 @@ public void testEmptyType() throws Exception {
370371
assertThat(e.getMessage(), containsString("type cannot be an empty string"));
371372
}
372373

374+
public void testWithRootObjectMapperNamespaceValidator() throws Exception {
375+
final String errorMessage = "error 1234";
376+
377+
RootObjectMapperNamespaceValidator validatorx = new RootObjectMapperNamespaceValidator() {
378+
@Override
379+
public void validateNamespace(ObjectMapper.Subobjects subobjects, String name) {
380+
System.err.println(">>> XXX subobjects: " + subobjects.toString());
381+
String disallowed = "_project";
382+
if (name.equals(disallowed)) {
383+
throw new IllegalArgumentException(errorMessage);
384+
} else if (subobjects != ObjectMapper.Subobjects.ENABLED) {
385+
System.err.println(">>> YYYYYYYYYYYYYYYYY subobjects: " + subobjects.toString());
386+
// name here will be something like _project.my_field, rather than just _project
387+
if (name.startsWith(disallowed + ".")) {
388+
throw new IllegalArgumentException(errorMessage);
389+
}
390+
}
391+
}
392+
};
393+
394+
String notNested = """
395+
{
396+
"_doc": {
397+
"properties": {
398+
"<FIELD_NAME>": {
399+
"type": "<TYPE>"
400+
}
401+
}
402+
}
403+
}""";
404+
405+
// _project should fail, regardless of type
406+
{
407+
String json = notNested.replace("<FIELD_NAME>", "_project");
408+
409+
String keyword = json.replace("<TYPE>", "keyword");
410+
Exception e = expectThrows(IllegalArgumentException.class, () -> createMapperServiceWithNamespaceValidator(keyword, validator));
411+
assertThat(e.getMessage(), equalTo(errorMessage));
412+
413+
String text = json.replace("<TYPE>", "text");
414+
e = expectThrows(IllegalArgumentException.class, () -> createMapperServiceWithNamespaceValidator(text, validator));
415+
assertThat(e.getMessage(), equalTo(errorMessage));
416+
417+
String object = json.replace("<TYPE>", "object");
418+
e = expectThrows(IllegalArgumentException.class, () -> createMapperServiceWithNamespaceValidator(object, validator));
419+
assertThat(e.getMessage(), equalTo(errorMessage));
420+
}
421+
422+
// _project.subfield should fail
423+
{
424+
String json = notNested.replace("<FIELD_NAME>", "_project.subfield").replace("<TYPE>", randomFrom("text", "keyword", "object"));
425+
Exception e = expectThrows(IllegalArgumentException.class, () -> createMapperServiceWithNamespaceValidator(json, validator));
426+
assertThat(e.getMessage(), equalTo(errorMessage));
427+
}
428+
429+
// _projectx should pass
430+
{
431+
String json = notNested.replace("<FIELD_NAME>", "_projectx").replace("<TYPE>", randomFrom("text", "keyword", "object"));
432+
MapperService mapperService = createMapperServiceWithNamespaceValidator(json, validator);
433+
assertNotNull(mapperService);
434+
}
435+
436+
// _project_subfield should pass
437+
{
438+
String json = notNested.replace("<FIELD_NAME>", "_project_subfield");
439+
json = json.replace("<TYPE>", randomFrom("text", "keyword", "object"));
440+
MapperService mapperService = createMapperServiceWithNamespaceValidator(json, validator);
441+
assertNotNull(mapperService);
442+
}
443+
444+
// _projectx.subfield should pass
445+
{
446+
String json = notNested.replace("<FIELD_NAME>", "_projectx.subfield");
447+
json = json.replace("<TYPE>", randomFrom("text", "keyword", "object"));
448+
MapperService mapperService = createMapperServiceWithNamespaceValidator(json, validator);
449+
assertNotNull(mapperService);
450+
}
451+
452+
String nested = """
453+
{
454+
"_doc": {
455+
"properties": {
456+
"<FIELD_NAME1>": {
457+
"type": "object",
458+
"properties": {
459+
"<FIELD_NAME1>": {
460+
"type": "keyword"
461+
}
462+
}
463+
}
464+
}
465+
}
466+
}""";
467+
468+
// TODO: this also works - what is the difference?
469+
// String nested = """
470+
// {
471+
// "mappings": {
472+
// "properties": {
473+
// "<FIELD_NAME1>": {
474+
// "type": "object",
475+
// "properties": {
476+
// "<FIELD_NAME1>": {
477+
// "type": "keyword"
478+
// }
479+
// }
480+
// }
481+
// }
482+
// }
483+
// }""";
484+
485+
// nested _project { my_field } should fail
486+
{
487+
String json = nested.replace("<FIELD_NAME1>", "_project").replace("<FIELD_NAME2>", "my_field");
488+
Exception e = expectThrows(IllegalArgumentException.class, () -> createMapperServiceWithNamespaceValidator(json, validator));
489+
assertThat(e.getMessage(), equalTo(errorMessage));
490+
}
491+
492+
// nested my_field { _project } should succeed
493+
{
494+
String json = nested.replace("<FIELD_NAME1>", "my_field").replace("<FIELD_NAME2>", "_project");
495+
MapperService mapperService = createMapperServiceWithNamespaceValidator(json, validator);
496+
assertNotNull(mapperService);
497+
}
498+
499+
// nested _projectx { _project } should succeed
500+
{
501+
String json = nested.replace("<FIELD_NAME1>", "_projectx").replace("<FIELD_NAME2>", "_project");
502+
MapperService mapperService = createMapperServiceWithNamespaceValidator(json, validator);
503+
assertNotNull(mapperService);
504+
}
505+
506+
// TODO: I'm not allowed to change the subobjects setting for some reason
507+
// test with subobjects setting
508+
String withSubobjects = """
509+
{
510+
"_doc": {
511+
"subobjects": "<SUBOBJECTS_SETTING>",
512+
"properties": {
513+
"<FIELD_NAME>": {
514+
"type": "object",
515+
"properties": {
516+
"my_field": {
517+
"type": "keyword"
518+
}
519+
}
520+
}
521+
}
522+
}
523+
}""";
524+
525+
// String withSubobjects = """
526+
// {
527+
// "mappings": {
528+
// "subobjects": "<SUBOBJECTS_SETTING>",
529+
// "properties": {
530+
// "<FIELD_NAME>": {
531+
// "type": "object",
532+
// "properties": {
533+
// "my_field": {
534+
// "type": "keyword"
535+
// }
536+
// }
537+
// }
538+
// }
539+
// }
540+
// }""";
541+
//
542+
// {
543+
// String json = withSubobjects.replace("<SUBOBJECTS_SETTING>", "false")//randomFrom("true", "false", "auto"))
544+
// .replace("<FIELD_NAME>", "abc");
545+
// MapperService mapperService = createMapperServiceWithNamespaceValidator(json, validator);
546+
// assertNotNull(mapperService);
547+
// }
548+
549+
// {
550+
// String json = withSubobjects.replace("<SUBOBJECTS_SETTING>", "false")
551+
// .replace("<FIELD_NAME>", "_project");
552+
// // TODO: fails with org.elasticsearch.index.mapper.MapperException: the [subobjects] parameter can't be updated for the object
553+
// mapping [_doc]
554+
// MapperService mapperService = createMapperServiceWithNamespaceValidator(json, validator);
555+
// assertNotNull(mapperService);
556+
// }
557+
}
558+
559+
public void testRuntimeFieldInMappingWithNamespaceValidator() throws IOException {
560+
final String errorMessage = "error 1234";
561+
562+
RootObjectMapperNamespaceValidator validator = new RootObjectMapperNamespaceValidator() {
563+
@Override
564+
public void validateNamespace(ObjectMapper.Subobjects subobjects, String name) {
565+
System.err.println(">>> XXX subobjects: " + subobjects);
566+
String disallowed = "_project";
567+
if (name.equals(disallowed)) {
568+
throw new IllegalArgumentException(errorMessage);
569+
} else if (subobjects != ObjectMapper.Subobjects.ENABLED) {
570+
System.err.println(">>> YYYYYYYYYYYYYYYYY subobjects: " + subobjects);
571+
// name here will be something like _project.my_field, rather than just _project
572+
if (name.startsWith(disallowed + ".")) {
573+
throw new IllegalArgumentException(errorMessage);
574+
}
575+
}
576+
}
577+
};
578+
579+
// ensure that things close to the disallowed fields that are allowed
580+
{
581+
String mapping = Strings.toString(runtimeMapping(builder -> {
582+
builder.startObject("_project_x").field("type", "ip").endObject();
583+
builder.startObject("_projectx").field("type", "date").endObject();
584+
builder.startObject("field1._project").field("type", "double").endObject();
585+
}));
586+
MapperService mapperService = createMapperServiceWithNamespaceValidator(mapping, validator);
587+
assertEquals(mapping, mapperService.documentMapper().mappingSource().toString());
588+
assertEquals(3, mapperService.documentMapper().mapping().getRoot().getTotalFieldsCount());
589+
}
590+
591+
// _project is rejected
592+
{
593+
String mapping = Strings.toString(runtimeMapping(builder -> {
594+
builder.startObject("field1").field("type", "double").endObject();
595+
builder.startObject("_project").field("type", "date").endObject();
596+
builder.startObject("field3").field("type", "ip").endObject();
597+
}));
598+
Exception e = expectThrows(MapperParsingException.class, () -> createMapperServiceWithNamespaceValidator(mapping, validator));
599+
Throwable cause = ExceptionsHelper.unwrap(e, IllegalArgumentException.class);
600+
assertNotNull(cause);
601+
assertThat(cause.getMessage(), equalTo(errorMessage));
602+
}
603+
604+
// _project.my_sub_field is rejected
605+
{
606+
String mapping = Strings.toString(runtimeMapping(builder -> {
607+
builder.startObject("field1").field("type", "double").endObject();
608+
builder.startObject("_project.my_sub_field").field("type", "keyword").endObject();
609+
builder.startObject("field3").field("type", "ip").endObject();
610+
}));
611+
Exception e = expectThrows(MapperParsingException.class, () -> createMapperServiceWithNamespaceValidator(mapping, validator));
612+
Throwable cause = ExceptionsHelper.unwrap(e, IllegalArgumentException.class);
613+
assertNotNull(cause);
614+
assertThat(cause.getMessage(), equalTo(errorMessage));
615+
}
616+
}
617+
373618
public void testSyntheticSourceKeepAllThrows() throws IOException {
374619
String mapping = Strings.toString(
375620
XContentFactory.jsonBuilder()

test/framework/src/main/java/org/elasticsearch/index/mapper/MapperServiceTestCase.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,20 @@ protected final MapperService createMapperService(IndexVersion version, Settings
234234
return new TestMapperServiceBuilder().indexVersion(version).settings(settings).idFieldDataEnabled(idFieldDataEnabled).build();
235235
}
236236

237+
public MapperService createMapperServiceWithNamespaceValidator(String mappings, RootObjectMapperNamespaceValidator validatorx)
238+
throws IOException {
239+
MapperService mapperService = new TestMapperServiceBuilder().indexVersion(getVersion())
240+
.settings(getIndexSettings())
241+
.idFieldDataEnabled(() -> true)
242+
.namespaceValidator(validator)
243+
.build();
244+
245+
// TODO: is this step necessary?
246+
mapperService = withMapping(mapperService, mapping(b -> {}));
247+
merge(mapperService, mappings);
248+
return mapperService;
249+
}
250+
237251
protected final MapperService withMapping(MapperService mapperService, XContentBuilder mapping) throws IOException {
238252
merge(mapperService, mapping);
239253
return mapperService;
@@ -246,6 +260,7 @@ protected class TestMapperServiceBuilder {
246260
private ScriptCompiler scriptCompiler;
247261
private MapperMetrics mapperMetrics;
248262
private boolean applyDefaultMapping;
263+
private RootObjectMapperNamespaceValidator namespaceValidator;
249264

250265
public TestMapperServiceBuilder() {
251266
indexVersion = getVersion();
@@ -281,11 +296,17 @@ public TestMapperServiceBuilder applyDefaultMapping(boolean applyDefaultMapping)
281296
return this;
282297
}
283298

299+
public TestMapperServiceBuilder namespaceValidator(RootObjectMapperNamespaceValidator validator) {
300+
this.namespaceValidator = validator;
301+
return this;
302+
}
303+
284304
public MapperService build() {
285305
IndexSettings indexSettings = createIndexSettings(indexVersion, settings);
286306
SimilarityService similarityService = new SimilarityService(indexSettings, null, Map.of());
287307
MapperRegistry mapperRegistry = new IndicesModule(
288-
getPlugins().stream().filter(p -> p instanceof MapperPlugin).map(p -> (MapperPlugin) p).collect(toList())
308+
getPlugins().stream().filter(p -> p instanceof MapperPlugin).map(p -> (MapperPlugin) p).collect(toList()),
309+
namespaceValidator
289310
).getMapperRegistry();
290311

291312
BitsetFilterCache bitsetFilterCache = new BitsetFilterCache(indexSettings, BitsetFilterCache.Listener.NOOP);

0 commit comments

Comments
 (0)