-
Notifications
You must be signed in to change notification settings - Fork 25k
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
Script: Metadata for update context #88333
Conversation
Adds the metadata() API call and a Metadata class for the Update context There are different metadata available in the update context depending on whether it is an update or an insert (via upsert). For update, scripts can read index, id, routing, version and timestamp. For insert, scripts can read index, id and timestamp. Scripts can always read and write the op but the available ops are different. Updates allow 'noop', 'index' and 'delete'. Inserts allow 'noop' and 'create'. Refs: elastic#86472
…inancy for UpdateRequestTests.testUpdateScript
Pinging @elastic/es-core-infra (Team:Core/Infra) |
Hi @stu-elastic, I've created a changelog YAML for you. |
Pinging @elastic/clients-team (Team:Clients) |
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 realized we have talked about this, but after viewing the code I was wondering again what if each context did have its own Metadata and the base class for this took in the Map of validators? The numerous comments in the Metadata class about how something is needed only for one context, but not another seems like it'll be hard to maintain. We also wouldn't have to have methods on the class that don't make sense for a specific context. You still wouldn't have to alias the name because it's part of the allow list specific to each context.
I can see there's a lot of refactoring work as well as you mentioned to me. Since I'm just coming back into this project it's tricky for me to differentiate what's refactoring versus what's added here that's new. Would you be able to split the refactoring into a first PR please?
…asticsearch into 220712-update-metadata-0
…sticsearch into 220712-update-metadata-0
…0712-update-metadata-0
…sticsearch into 220712-update-metadata-0
…to Metadata base class
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.
Thanks for the update Stu! After the other PRs this is much more straightforward and easier to understand. I think there is a backcompat issue to address, but otherwise I just have a few minor comments.
@SuppressWarnings("unchecked") | ||
public Map<String, Object> getSource() { | ||
Map<String, Object> wrapped = super.getSource(); | ||
Object rawSource = wrapped.get(SOURCE); |
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.
Since ctx._source
seems to be the more common pattern (only ingest differs?), wouldn't it make more sense for IngestCtxMap to be the special case, and CtxMap has the implementation from here?
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.
Moved.
@Override | ||
public void setOp(String op) { | ||
if (LEGACY_NOOP_STRING.equals(op)) { | ||
throw new IllegalArgumentException(LEGACY_NOOP_STRING + " is deprecated, use 'noop' instead"); |
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.
This doesn't seem like a deprecation, an exception would mean an error. I think we need to support the existing case without error for now.
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.
It is supported in the map, so there's no bwc issue, what do you think about removing the " is deprecated"
language, disallow it in set but translate it in getOp
?
That's asymmetric, but the addition of this class seems like the time and place to make that change.
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.
Ok, I see now. I think your suggestion is good.
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.
Ok, keeping it.
String getId() | ||
String getRouting() | ||
long getVersion() | ||
String getOp() |
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.
We discussed having get and set op outside of the metadata on each script type that needs it, as it isn't really metadata about the document. What happened with that?
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.
If it's on the script, then it conflicts with any variables named op
, so it would be a backwards compatibility issue.
After thinking about it some more, it seems like a high cost to introduce another concept in addition to field
and metadata
for this one-off. Maybe there's a better way to handle it?
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 don't feel strongly about it being outside of metadata. The potential conflict with variable names is both unfortunate, and something I think we need to look at fixing (via shadowing), because it's not just with class getters, but also any additional arguments added to the execute method.
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.
The potential conflict with variable names is both unfortunate, and something I think we need to look at fixing (via shadowing), because it's not just with class getters, but also any additional arguments added to the execute method.
Agreed. It's a tricky change but worth doing because it allows us to make changes without backward compatibility issues. It's come up a few times now.
SET_ONCE_LONG | ||
); | ||
|
||
protected static BiConsumer<String, String> setValidator(Set<String> valid) { |
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.
nit: the naming here makes it look like a setter. perhaps stringSetValidator
?
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.
Renamed.
@Override | ||
public String getOp() { | ||
String op = super.getOp(); | ||
if (LEGACY_NOOP_STRING.equals(op) || op == null) { |
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 don't think we can do this translation for the "legacy" value here, it would be breaking. We should look at how/when to deprecate the two noop values outside of this PR.
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 don't think it's breaking since the Map interface still allows it, it's just if the script wants to use the metadata interface. I can allow it here but still reject it in setOp(String)
, what do you think?
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.
The legacy case makes sense, but why is null converted to noop?
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.
op
is nullable for backwards compatibility.
Before this change, if op
were null
, lenientFromString
would translate it to UpdateOp.NONE
.
UpdateOp.NONE
is the same as 'noop'
now.
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 suppose we should also restrict null
in setOp
like we're already doing with "none".
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.
In fact, there shouldn't be validation for op
at all in the map, as it all gets translated to UpdateOp.NONE
.
|
||
@Override | ||
public String getRouting() { | ||
throw new IllegalArgumentException("routing is unavailable for insert"); |
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.
UnsupportedOperationException would be more typical. We should use the same anywhere else we are failing a get/set method (a user should never see it since we woulnd't have allowed the method on scripting to begin with).
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.
Changed to UnsupportedOperationException
.
It is still whitelisted because an Update script may run in "Update mode" or "Insert-via-upsert mode" and this allows a user to write a script that will work in either case. If we split the Update context then a script that works for "Update mode" would fail to compile for "Insert-via-upsert mode".
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.
If we split the Update context then a script
I thought we were already planning to do that?
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.
We discussed it a while ago, but it'd be weird to have an script sent to the update endpoint fail to compile based on the whether or not the document exists.
if (rawSource instanceof Map<?, ?> map) { | ||
return (Map<String, Object>) map; | ||
} | ||
throw new IllegalArgumentException( |
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.
How could this occur? Since we control the ctx map, wouldn't this be a coding error? Should this be an assert instead?
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.
An assert
brings down the node. Do you think we'd get the same feedback with an Exception without the customer pain? Perhaps IllegalStateException
.
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.
An assert brings down the node
It won't for a couple reasons. The uncaught exception handler does not consider AssertionError to be fatal. But also it would only be tripped if running with asserts enabled, which doesn't happen in production. We run tests with asserts enabled, and use asserts for things like this in many other parts of the code specifically so that we can catch coding errors in tests.
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.
Changed to assert
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.
The new changes look fine to me. Please wait for @rjernst to have another look before committing.
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.
LGTM.
Adds the
metadata()
API call and a Metadata class for the Update context.There are different metadata available in the update context depending
on whether it is an update or an insert (via upsert).
For update, scripts can read
index
,id
,routing
,version
andtimestamp
.For insert, scripts can read
index
,id
andtimestamp
.Scripts can always read and write the
op
but the available ops are different.Updates allow 'noop', 'index' and 'delete'.
Inserts allow 'noop' and 'create'.
Refs: #86472