-
Notifications
You must be signed in to change notification settings - Fork 142
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature: PPL - json_set function #3271
base: main
Are you sure you want to change the base?
Feature: PPL - json_set function #3271
Conversation
Signed-off-by: Kenrick Yap <[email protected]>
Signed-off-by: Kenrick Yap <[email protected]>
Signed-off-by: Kenrick Yap <[email protected]>
Signed-off-by: Kenrick Yap <[email protected]>
Signed-off-by: Kenrick Yap <[email protected]>
Signed-off-by: Kenrick Yap <[email protected]>
Signed-off-by: Kenrick Yap <[email protected]>
Signed-off-by: Kenrick Yap <[email protected]>
Signed-off-by: Kenrick Yap <[email protected]>
Signed-off-by: Kenrick Yap <[email protected]>
Signed-off-by: Kenrick Yap <[email protected]>
Co-authored-by: Andrew Carbonetto <[email protected]> Signed-off-by: kenrickyap <[email protected]>
….java Co-authored-by: Andrew Carbonetto <[email protected]> Signed-off-by: kenrickyap <[email protected]>
Signed-off-by: Kenrick Yap <[email protected]>
…nsearch-project-sql into feature/json-valid Signed-off-by: Kenrick Yap <[email protected]>
Signed-off-by: Kenrick Yap <[email protected]>
Signed-off-by: Kenrick Yap <[email protected]>
Signed-off-by: Kenrick Yap <[email protected]>
Signed-off-by: Kenrick Yap <[email protected]>
Signed-off-by: Kenrick Yap <[email protected]>
Signed-off-by: Andrew Carbonetto <[email protected]>
Signed-off-by: Andrew Carbonetto <[email protected]>
Signed-off-by: Andrew Carbonetto <[email protected]>
Signed-off-by: Andrew Carbonetto <[email protected]>
Signed-off-by: Andrew Carbonetto <[email protected]>
Signed-off-by: Andrew Carbonetto <[email protected]>
Signed-off-by: Andrew Carbonetto <[email protected]>
Signed-off-by: Andrew Carbonetto <[email protected]>
Signed-off-by: Andrew Carbonetto <[email protected]>
Hi @andy-k-improving |
Functionality wide, this is ready. |
core/src/main/java/org/opensearch/sql/expression/json/JsonFunctions.java
Outdated
Show resolved
Hide resolved
Signed-off-by: Andy Kwok <[email protected]>
Signed-off-by: Andy Kwok <[email protected]>
@normanj-bitquill Fixed above comments, would you mind to have another look? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reviewed to 2509fc8
.
BuiltinFunctionName.JSON_SET.getName(), | ||
impl(nullMissingHandling(JsonUtils::setJson), UNDEFINED, STRING, STRING, STRING), | ||
impl(nullMissingHandling(JsonUtils::setJson), UNDEFINED, STRING, STRING, LONG), | ||
impl(nullMissingHandling(JsonUtils::setJson), UNDEFINED, STRING, STRING, SHORT), | ||
impl(nullMissingHandling(JsonUtils::setJson), UNDEFINED, STRING, STRING, INTEGER), | ||
impl(nullMissingHandling(JsonUtils::setJson), UNDEFINED, STRING, STRING, DOUBLE), | ||
impl(nullMissingHandling(JsonUtils::setJson), UNDEFINED, STRING, STRING, BOOLEAN), | ||
impl(nullMissingHandling(JsonUtils::setJson), UNDEFINED, STRING, STRING, ARRAY), | ||
impl(nullMissingHandling(JsonUtils::setJson), UNDEFINED, STRING, STRING, STRUCT), | ||
impl(nullMissingHandling(JsonUtils::setJson), UNDEFINED, STRING, STRING, BYTE), | ||
impl(nullMissingHandling(JsonUtils::setJson), UNDEFINED, STRING, STRING, DATE), | ||
impl(nullMissingHandling(JsonUtils::setJson), UNDEFINED, STRING, STRING, IP)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few questions/thoughts:
- Looks like you're missing
FLOAT
? - You're supporting
DATE
. What aboutTIME
,TIMESTAMP
, orINTERVAL
types? - What about
UNDEFINED
? Is there anyway to set a JSON value tonull
?
String jsonUnquoted = StringUtils.unquoteText(json.stringValue()); | ||
String pathUnquoted = StringUtils.unquoteText(path.stringValue()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does this method need to use unquoteText
but the other JSON methods above don't?
Configuration conf = | ||
Configuration.defaultConfiguration().addOptions(Option.SUPPRESS_EXCEPTIONS); | ||
try { | ||
JsonPath jsonPath = JsonPath.compile(pathUnquoted); | ||
DocumentContext docContext = JsonPath.using(conf).parse(jsonUnquoted); | ||
switch (docContext.read(jsonPath)) { | ||
case null -> { | ||
// Insert a new property | ||
recursiveCreate(docContext, pathUnquoted, valueUnquoted); | ||
return new ExprStringValue(docContext.jsonString()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are you using SUPPRESS_EXCEPTIONS
here?
From the documentation:
Suppress all exceptions when evaluating path ... If an exception is thrown and the option ALWAYS_RETURN_LIST is not present null is returned.
Won't this mean that the first switch
case (insert a new property) will be executed whenever an invalid path argument is given (i.e. if it isn't even valid JSON path syntax)? Why are we handling and throwing an exception if the path isn't a valid JSON path?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kenrickyap current PR for json_expand
handles InvalidPathException
. Probably make sense for these two functions (and all other JSON functions) to handle this the same? Would it be possible to extract this logic to a shared method?
private static JsonPath getJsonPath(String jsonPathString) {
try {
return JsonPath.compile(jsonPathString);
} catch () {
final String errorMsgFormat = "JSON path '%s' is not valid. Error details: %s";
throw new SemanticCheckException(String.format(errorMsgFormat, jsonPathString, e.getMessage()), e);
}
}
} | ||
|
||
/** Converts a JSON encoded string to an Expression object. */ | ||
public static ExprValue setJson(ExprValue json, ExprValue path, ExprValue valueToInsert) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about the case where the JSON path points to multiple places? Does this method support inserting multiples values (it doesn't seem like it)? Would it just insert the first one, would it fail? Is this a case we should consider, and have an appropriate error message for it?
case Iterable<?> ignored -> { | ||
// New element in the array. | ||
String updatedJson = docContext.add(pathUnquoted, valueUnquoted).jsonString(); | ||
return new ExprStringValue(updatedJson); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Am I understanding this correctly? If I call jsonSet
with a JSON path that points to an array, this will insert the given value into the array, rather than set it equal to the given value?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about the case where the JSON path points to a nested JSON object? Is a JSON object Iterable
, and so would it fall into this case?
/** | ||
* Helper method to handle recursive scenario. | ||
* | ||
* @param docContext incoming Json in Java object form. | ||
* @param path path in String to perform insertion. | ||
* @param value value to be inserted with given path. | ||
*/ | ||
private static void recursiveCreate(DocumentContext docContext, String path, Object value) { | ||
final int pos = path.lastIndexOf('.'); | ||
final String parent = path.substring(0, pos); | ||
final String current = path.substring(pos + 1); | ||
// Attempt to read the current path as it is, trigger the recursive in case of deep insert. | ||
if (docContext.read(parent) == null) { | ||
recursiveCreate(docContext, parent, new LinkedHashMap<>()); | ||
} | ||
docContext.put(parent, current, value); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned in another comment, have you verified that this (recursive insertion) is not handled by WriteContext.set
?
void json_set_InsertString() { | ||
FunctionExpression functionExpression = | ||
DSL.jsonSet(DSL.literal("[]"), DSL.literal("$"), DSL.literal("test_value")); | ||
assertEquals("[\"test_value\"]", functionExpression.valueOf().stringValue()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be worth extracting a helper function? Could help make this a big easier to read?
void json_set_InsertString() {
assertEquals("[\"test_value\"]", execute_set_json("[]", "$", "test_value"));
}
static String execute_set_json(String jsonString, String jsonPathString, Object value) {
ExprValue jsonExprValue = ExprValueUtils.stringValue(jsonString);
ExprValue jsonPathExprValue = ExprValueUtils.stringValue(jsonPathString);
ExprValue setExprValue = ExprValueUtils.fromObjectValue(value);
FunctionExpression functionExpression = DSL.jsonSet(jsonExprValue, jsonPathExprValue, setExprValue);
return functionExpression.valueOf().stringValue();
}
|
||
Usage: `json_set(json_string, json_path, value)` Perform value insertion or override with provided Json path and value. Returns the updated JSON object if valid, null otherwise. | ||
|
||
Argument type: JSON_STRING, STRING, STRING/BOOLEAN/DOUBLE/INTEGER/NULL/STRUCT/ARRAY |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like you need to update this to include a few more supported types? Also, shouldn't this just be STRING
instead of JSON_STRING
?
|
||
Argument type: JSON_STRING, STRING, STRING/BOOLEAN/DOUBLE/INTEGER/NULL/STRUCT/ARRAY | ||
|
||
Return type: JSON_STRING |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly, should this just be STRING
?
String.format( | ||
"source=%s | eval updated=json_set(json_string, \\\"$.c.innerProperty\\\"," | ||
+ " \\\"test_value\\\") | fields test_name, updated", | ||
TEST_INDEX_JSON_TEST)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I curious: why do we need so many escape characters here? Why doesn't one suffice?
This PR depends on t he
json_valid
,json_object
andjson_extract
:#3262, #3242, #3230
Description
To introduce Json function
json_set
to perform insert || override with provided JsonPath String.Related Issues
Resolves: #3214
Partially resolves: #3028
Check List
--signoff
.By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.