|
32 | 32 | import java.time.format.DateTimeParseException; |
33 | 33 | import java.time.temporal.TemporalAccessor; |
34 | 34 | import java.util.ArrayList; |
| 35 | +import java.util.HashSet; |
35 | 36 | import java.util.List; |
36 | 37 | import java.util.Map; |
37 | 38 | import java.util.Optional; |
| 39 | +import java.util.Set; |
38 | 40 | import java.util.function.BiFunction; |
39 | 41 | import lombok.Getter; |
40 | 42 | import lombok.Setter; |
|
76 | 78 | public class OpenSearchExprValueFactory { |
77 | 79 | private static final Logger LOG = LogManager.getLogger(OpenSearchExprValueFactory.class); |
78 | 80 |
|
| 81 | + /** |
| 82 | + * Collects invalid field names encountered during parsing. Uses ThreadLocal to avoid log spam by |
| 83 | + * logging all invalid fields once per construct() call instead of per field. |
| 84 | + */ |
| 85 | + private static final ThreadLocal<Set<String>> INVALID_FIELDS = |
| 86 | + ThreadLocal.withInitial(HashSet::new); |
| 87 | + |
79 | 88 | /** The Mapping of Field and ExprType. */ |
80 | 89 | private final Map<String, OpenSearchDataType> typeMapping; |
81 | 90 |
|
@@ -168,14 +177,31 @@ public OpenSearchExprValueFactory( |
168 | 177 | * </pre> |
169 | 178 | */ |
170 | 179 | public ExprValue construct(String jsonString, boolean supportArrays) { |
| 180 | + INVALID_FIELDS.get().clear(); |
171 | 181 | try { |
172 | | - return parse( |
173 | | - new OpenSearchJsonContent(OBJECT_MAPPER.readTree(jsonString)), |
174 | | - TOP_PATH, |
175 | | - Optional.of(STRUCT), |
176 | | - fieldTypeTolerance || supportArrays); |
| 182 | + ExprValue result = |
| 183 | + parse( |
| 184 | + new OpenSearchJsonContent(OBJECT_MAPPER.readTree(jsonString)), |
| 185 | + TOP_PATH, |
| 186 | + Optional.of(STRUCT), |
| 187 | + fieldTypeTolerance || supportArrays); |
| 188 | + logInvalidFields(); |
| 189 | + return result; |
177 | 190 | } catch (JsonProcessingException e) { |
178 | 191 | throw new IllegalStateException(String.format("invalid json: %s.", jsonString), e); |
| 192 | + } finally { |
| 193 | + INVALID_FIELDS.get().clear(); |
| 194 | + } |
| 195 | + } |
| 196 | + |
| 197 | + /** Log all invalid fields encountered during parsing (once per construct call). */ |
| 198 | + private void logInvalidFields() { |
| 199 | + Set<String> invalidFields = INVALID_FIELDS.get(); |
| 200 | + if (!invalidFields.isEmpty()) { |
| 201 | + LOG.warn( |
| 202 | + "The following field(s) have invalid names and will return null: {}. " |
| 203 | + + "This can happen with disabled object fields that bypass field name validation.", |
| 204 | + invalidFields); |
179 | 205 | } |
180 | 206 | } |
181 | 207 |
|
@@ -380,25 +406,35 @@ private ExprValue parseStruct(Content content, String prefix, boolean supportArr |
380 | 406 | entry -> { |
381 | 407 | String fieldKey = entry.getKey(); |
382 | 408 | String fullFieldPath = makeField(prefix, fieldKey); |
383 | | - try { |
| 409 | + // Check for invalid field names (e.g., fields consisting only of dots like "." or |
| 410 | + // "..") |
| 411 | + // before creating JsonPath to avoid masking other IllegalArgumentExceptions |
| 412 | + if (isInvalidFieldName(fieldKey)) { |
| 413 | + // Collect invalid fields to log once per construct() call to avoid log spam |
| 414 | + INVALID_FIELDS.get().add(fullFieldPath); |
| 415 | + result.tupleValue().put(fieldKey, ExprNullValue.of()); |
| 416 | + } else { |
384 | 417 | populateValueRecursive( |
385 | 418 | result, |
386 | 419 | new JsonPath(fieldKey), |
387 | 420 | parse(entry.getValue(), fullFieldPath, type(fullFieldPath), supportArrays)); |
388 | | - } catch (IllegalArgumentException e) { |
389 | | - // Return null for invalid field names (e.g., fields consisting only of dots) |
390 | | - // This can happen with disabled object fields that bypass field name validation |
391 | | - // Log warning to hint users about corrupt data |
392 | | - LOG.warn( |
393 | | - "Field '{}' has invalid name and will return null: {}", |
394 | | - fullFieldPath, |
395 | | - e.getMessage()); |
396 | | - result.tupleValue().put(fieldKey, ExprNullValue.of()); |
397 | 421 | } |
398 | 422 | }); |
399 | 423 | return result; |
400 | 424 | } |
401 | 425 |
|
| 426 | + /** |
| 427 | + * Check if a field name is invalid. A field name is invalid if it consists only of dots (e.g., |
| 428 | + * ".", "..", "..."). Such field names cause issues because String.split("\\.") returns an empty |
| 429 | + * array for them. |
| 430 | + * |
| 431 | + * @param fieldName The field name to check. |
| 432 | + * @return true if the field name is invalid, false otherwise. |
| 433 | + */ |
| 434 | + private static boolean isInvalidFieldName(String fieldName) { |
| 435 | + return fieldName.split("\\.").length == 0; |
| 436 | + } |
| 437 | + |
402 | 438 | /** |
403 | 439 | * Populate the current ExprTupleValue recursively. |
404 | 440 | * |
|
0 commit comments