Skip to content

Commit fea679d

Browse files
authored
Make nested alias type support referring to outer context (opensearch-project#4673)
1 parent 0f45382 commit fea679d

File tree

6 files changed

+98
-29
lines changed

6 files changed

+98
-29
lines changed
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
setup:
2+
- do:
3+
indices.create:
4+
index: test
5+
body:
6+
settings:
7+
number_of_shards: 1
8+
number_of_replicas: 0
9+
mappings:
10+
properties:
11+
aws:
12+
properties:
13+
cloudtrail:
14+
properties:
15+
event_name:
16+
type: alias
17+
path: api.operation
18+
user_identity:
19+
type: text
20+
api:
21+
properties:
22+
operation:
23+
type: keyword
24+
25+
- do:
26+
query.settings:
27+
body:
28+
transient:
29+
plugins.calcite.enabled : true
30+
31+
---
32+
teardown:
33+
- do:
34+
query.settings:
35+
body:
36+
transient:
37+
plugins.calcite.enabled : false
38+
39+
---
40+
"Handle nested alias field referring to the outer context":
41+
- skip:
42+
features:
43+
- headers
44+
- do:
45+
bulk:
46+
index: test
47+
refresh: true
48+
body:
49+
- '{"index": {}}'
50+
- '{
51+
"aws": {
52+
"cloudtrail": {
53+
"user_identity": "test-user"
54+
}
55+
},
56+
"api": {
57+
"operation": "CreateBucket"
58+
}
59+
}'
60+
- do:
61+
headers:
62+
Content-Type: 'application/json'
63+
ppl:
64+
body:
65+
query: 'source=test | fields aws.cloudtrail.event_name'
66+
- match: {"total": 1}
67+
- match: { "schema": [ { "name": "aws.cloudtrail.event_name", "type": "string" } ] }
68+
- match: { "datarows": [ [ "CreateBucket" ] ] }

opensearch/src/main/java/org/opensearch/sql/opensearch/data/type/OpenSearchDataType.java

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ public enum MappingType {
4242
HalfFloat("half_float", ExprCoreType.FLOAT),
4343
ScaledFloat("scaled_float", ExprCoreType.DOUBLE),
4444
Double("double", ExprCoreType.DOUBLE),
45-
Boolean("boolean", ExprCoreType.BOOLEAN);
45+
Boolean("boolean", ExprCoreType.BOOLEAN),
46+
Alias("alias", ExprCoreType.UNKNOWN);
4647
// TODO: ranges, geo shape, point, shape
4748

4849
private final String name;
@@ -117,12 +118,7 @@ public static Map<String, OpenSearchDataType> parseMapping(Map<String, Object> i
117118
// by default, the type is treated as an Object if "type" is not provided
118119
var type = ((String) innerMap.getOrDefault("type", "object")).replace("_", "");
119120
if (!EnumUtils.isValidEnumIgnoreCase(OpenSearchDataType.MappingType.class, type)) {
120-
// unknown type, e.g. `alias`
121-
// Record fields of the alias type and resolve them later in case their references have
122-
// not been resolved.
123-
if (OpenSearchAliasType.typeName.equals(type)) {
124-
aliasMapping.put(k, (String) innerMap.get(OpenSearchAliasType.pathPropertyName));
125-
}
121+
// unknown type, skip it.
126122
return;
127123
}
128124
// create OpenSearchDataType
@@ -133,21 +129,6 @@ public static Map<String, OpenSearchDataType> parseMapping(Map<String, Object> i
133129
innerMap));
134130
});
135131

136-
// Begin to parse alias type fields
137-
if (!aliasMapping.isEmpty()) {
138-
// The path of alias type may point to a nested field, so we need to flatten the result.
139-
Map<String, OpenSearchDataType> flattenResult = traverseAndFlatten(result);
140-
aliasMapping.forEach(
141-
(k, v) -> {
142-
if (flattenResult.containsKey(v)) {
143-
result.put(k, new OpenSearchAliasType(v, flattenResult.get(v)));
144-
} else {
145-
throw new IllegalStateException(
146-
String.format("Cannot find the path [%s] for alias type field [%s]", v, k));
147-
}
148-
});
149-
}
150-
151132
return result;
152133
}
153134

@@ -188,6 +169,10 @@ public static OpenSearchDataType of(MappingType mappingType, Map<String, Object>
188169
// Default date formatter is used when "" is passed as the second parameter
189170
String format = (String) innerMap.getOrDefault("format", "");
190171
return OpenSearchDateType.of(format);
172+
case Alias:
173+
return new OpenSearchAliasType(
174+
(String) innerMap.get(OpenSearchAliasType.pathPropertyName),
175+
OpenSearchDateType.of(MappingType.Invalid));
191176
default:
192177
return res;
193178
}
@@ -302,9 +287,20 @@ public void accept(Map<String, OpenSearchDataType> subtree, String prefix) {
302287
}
303288
};
304289
visitLevel.accept(tree, "");
290+
validateAliasType(result);
305291
return result;
306292
}
307293

294+
private static void validateAliasType(Map<String, OpenSearchDataType> result) {
295+
result.forEach(
296+
(key, value) -> {
297+
if (value instanceof OpenSearchAliasType && value.getOriginalPath().isPresent()) {
298+
String originalPath = value.getOriginalPath().get();
299+
result.put(key, new OpenSearchAliasType(originalPath, result.get(originalPath)));
300+
}
301+
});
302+
}
303+
308304
/**
309305
* Resolve type of identified from parsed mapping tree.
310306
*

opensearch/src/main/java/org/opensearch/sql/opensearch/storage/OpenSearchIndex.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ public Map<String, String> getAliasMapping() {
165165
}
166166
if (aliasMapping == null) {
167167
aliasMapping =
168-
cachedFieldOpenSearchTypes.entrySet().stream()
168+
OpenSearchDataType.traverseAndFlatten(cachedFieldOpenSearchTypes).entrySet().stream()
169169
.filter(entry -> entry.getValue().getOriginalPath().isPresent())
170170
.collect(
171171
Collectors.toUnmodifiableMap(

opensearch/src/test/java/org/opensearch/sql/opensearch/client/OpenSearchNodeClientTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ void get_index_mappings() throws IOException {
187187
() -> assertEquals(OpenSearchTextType.of(MappingType.Text), parsedTypes.get("employer")),
188188
// `employer` is a `text` with `fields`
189189
() -> assertTrue(((OpenSearchTextType) parsedTypes.get("employer")).getFields().size() > 0),
190-
() -> assertEquals("TEXT", mapping.get("employer_alias").legacyTypeName()),
190+
() -> assertEquals("TEXT", parsedTypes.get("employer_alias").legacyTypeName()),
191191
() ->
192192
assertEquals(
193193
new OpenSearchAliasType("employer", OpenSearchTextType.of(MappingType.Text)),

opensearch/src/test/java/org/opensearch/sql/opensearch/client/OpenSearchRestClientTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ void get_index_mappings() throws IOException {
191191
() -> assertEquals(OpenSearchTextType.of(MappingType.Text), parsedTypes.get("employer")),
192192
// `employer` is a `text` with `fields`
193193
() -> assertTrue(((OpenSearchTextType) parsedTypes.get("employer")).getFields().size() > 0),
194-
() -> assertEquals("TEXT", mapping.get("employer_alias").legacyTypeName()),
194+
() -> assertEquals("TEXT", parsedTypes.get("employer_alias").legacyTypeName()),
195195
() ->
196196
assertEquals(
197197
new OpenSearchAliasType("employer", OpenSearchTextType.of(MappingType.Text)),

opensearch/src/test/java/org/opensearch/sql/opensearch/data/type/OpenSearchDataTypeTest.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -505,9 +505,14 @@ public void test_parseMapping_on_AliasType() {
505505
"col0", Map.of("type", "alias", "path", "col1"),
506506
"col1", Map.of("type", "text"),
507507
"col2", Map.of("type", "alias", "path", "col3"));
508-
IllegalStateException exception =
509-
assertThrows(
510-
IllegalStateException.class, () -> OpenSearchDataType.parseMapping(indexMapping2));
511-
assertEquals("Cannot find the path [col3] for alias type field [col2]", exception.getMessage());
508+
assertEquals(
509+
Map.of(
510+
"col0",
511+
new OpenSearchAliasType("col1", textType),
512+
"col1",
513+
textType,
514+
"col2",
515+
new OpenSearchAliasType("col1", OpenSearchDataType.of(MappingType.Invalid))),
516+
OpenSearchDataType.parseMapping(indexMapping2));
512517
}
513518
}

0 commit comments

Comments
 (0)