Merge pull request #18262 from rmuir/painless_doc_access
Painless doc access
This commit is contained in:
commit
2e4f87d2ce
|
@ -93,9 +93,22 @@ class Analyzer extends PainlessParserBaseVisitor<Void> {
|
||||||
|
|
||||||
utility.incrementScope();
|
utility.incrementScope();
|
||||||
utility.addVariable(null, "#this", definition.execType);
|
utility.addVariable(null, "#this", definition.execType);
|
||||||
|
//
|
||||||
|
// reserved words parameters.
|
||||||
|
//
|
||||||
|
// input map of variables passed to the script. TODO: rename to 'params' since that will be its use
|
||||||
metadata.inputValueSlot = utility.addVariable(null, "input", definition.smapType).slot;
|
metadata.inputValueSlot = utility.addVariable(null, "input", definition.smapType).slot;
|
||||||
|
// scorer parameter passed to the script. internal use only.
|
||||||
metadata.scorerValueSlot = utility.addVariable(null, "#scorer", definition.objectType).slot;
|
metadata.scorerValueSlot = utility.addVariable(null, "#scorer", definition.objectType).slot;
|
||||||
|
// doc parameter passed to the script.
|
||||||
|
// TODO: currently working as a def type, should be smapType...
|
||||||
|
metadata.docValueSlot = utility.addVariable(null, "doc", definition.defType).slot;
|
||||||
|
//
|
||||||
|
// reserved words implemented as local variables
|
||||||
|
//
|
||||||
|
// loop counter to catch runaway scripts. internal use only.
|
||||||
metadata.loopCounterSlot = utility.addVariable(null, "#loop", definition.intType).slot;
|
metadata.loopCounterSlot = utility.addVariable(null, "#loop", definition.intType).slot;
|
||||||
|
// document's score as a read-only float.
|
||||||
metadata.scoreValueSlot = utility.addVariable(null, "_score", definition.floatType).slot;
|
metadata.scoreValueSlot = utility.addVariable(null, "_score", definition.floatType).slot;
|
||||||
|
|
||||||
metadata.createStatementMetadata(metadata.root);
|
metadata.createStatementMetadata(metadata.root);
|
||||||
|
|
|
@ -448,6 +448,15 @@ class AnalyzerExternal {
|
||||||
throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "Unknown variable [" + id + "].");
|
throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "Unknown variable [" + id + "].");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// special cases: reserved words
|
||||||
|
if ("_score".equals(id) || "doc".equals(id)) {
|
||||||
|
// read-only: don't allow stores
|
||||||
|
if (parentemd.storeExpr != null) {
|
||||||
|
throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "Variable [" + id + "] is read-only.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// track if the _score value is ever used, we will invoke Scorer.score() only once if so.
|
||||||
if ("_score".equals(id)) {
|
if ("_score".equals(id)) {
|
||||||
metadata.scoreValueUsed = true;
|
metadata.scoreValueUsed = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
package org.elasticsearch.painless;
|
package org.elasticsearch.painless;
|
||||||
|
|
||||||
import org.apache.lucene.search.Scorer;
|
import org.apache.lucene.search.Scorer;
|
||||||
|
import org.elasticsearch.search.lookup.LeafDocLookup;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -48,5 +49,5 @@ public abstract class Executable {
|
||||||
return definition;
|
return definition;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract Object execute(Map<String, Object> input, Scorer scorer);
|
public abstract Object execute(Map<String, Object> input, Scorer scorer, LeafDocLookup doc);
|
||||||
}
|
}
|
||||||
|
|
|
@ -433,6 +433,12 @@ class Metadata {
|
||||||
* variable slots at the completion of analysis if _score is not used.
|
* variable slots at the completion of analysis if _score is not used.
|
||||||
*/
|
*/
|
||||||
boolean scoreValueUsed = false;
|
boolean scoreValueUsed = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to determine what slot the doc variable is stored in. This is used in the {@link Writer} whenever
|
||||||
|
* the doc variable is accessed.
|
||||||
|
*/
|
||||||
|
int docValueSlot = -1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps the relevant ANTLR node to its metadata.
|
* Maps the relevant ANTLR node to its metadata.
|
||||||
|
|
|
@ -22,6 +22,7 @@ package org.elasticsearch.painless;
|
||||||
import org.apache.lucene.search.Scorer;
|
import org.apache.lucene.search.Scorer;
|
||||||
import org.elasticsearch.script.ExecutableScript;
|
import org.elasticsearch.script.ExecutableScript;
|
||||||
import org.elasticsearch.script.LeafSearchScript;
|
import org.elasticsearch.script.LeafSearchScript;
|
||||||
|
import org.elasticsearch.search.lookup.LeafDocLookup;
|
||||||
import org.elasticsearch.search.lookup.LeafSearchLookup;
|
import org.elasticsearch.search.lookup.LeafSearchLookup;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -46,6 +47,11 @@ final class ScriptImpl implements ExecutableScript, LeafSearchScript {
|
||||||
* The lookup is used to access search field values at run-time.
|
* The lookup is used to access search field values at run-time.
|
||||||
*/
|
*/
|
||||||
private final LeafSearchLookup lookup;
|
private final LeafSearchLookup lookup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the 'doc' object accessed by the script, if available.
|
||||||
|
*/
|
||||||
|
private final LeafDocLookup doc;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Current scorer being used
|
* Current scorer being used
|
||||||
|
@ -70,6 +76,9 @@ final class ScriptImpl implements ExecutableScript, LeafSearchScript {
|
||||||
|
|
||||||
if (lookup != null) {
|
if (lookup != null) {
|
||||||
variables.putAll(lookup.asMap());
|
variables.putAll(lookup.asMap());
|
||||||
|
doc = lookup.doc();
|
||||||
|
} else {
|
||||||
|
doc = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,7 +98,7 @@ final class ScriptImpl implements ExecutableScript, LeafSearchScript {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Object run() {
|
public Object run() {
|
||||||
return executable.execute(variables, scorer);
|
return executable.execute(variables, scorer, doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
package org.elasticsearch.painless;
|
package org.elasticsearch.painless;
|
||||||
|
|
||||||
import org.apache.lucene.search.Scorer;
|
import org.apache.lucene.search.Scorer;
|
||||||
|
import org.elasticsearch.search.lookup.LeafDocLookup;
|
||||||
import org.objectweb.asm.Handle;
|
import org.objectweb.asm.Handle;
|
||||||
import org.objectweb.asm.Opcodes;
|
import org.objectweb.asm.Opcodes;
|
||||||
import org.objectweb.asm.Type;
|
import org.objectweb.asm.Type;
|
||||||
|
@ -37,7 +38,7 @@ class WriterConstants {
|
||||||
final static Type CLASS_TYPE = Type.getType("L" + CLASS_NAME.replace(".", "/") + ";");
|
final static Type CLASS_TYPE = Type.getType("L" + CLASS_NAME.replace(".", "/") + ";");
|
||||||
|
|
||||||
final static Method CONSTRUCTOR = getAsmMethod(void.class, "<init>", Definition.class, String.class, String.class);
|
final static Method CONSTRUCTOR = getAsmMethod(void.class, "<init>", Definition.class, String.class, String.class);
|
||||||
final static Method EXECUTE = getAsmMethod(Object.class, "execute", Map.class, Scorer.class);
|
final static Method EXECUTE = getAsmMethod(Object.class, "execute", Map.class, Scorer.class, LeafDocLookup.class);
|
||||||
|
|
||||||
final static Type PAINLESS_ERROR_TYPE = Type.getType(PainlessError.class);
|
final static Type PAINLESS_ERROR_TYPE = Type.getType(PainlessError.class);
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ public class NeedsScoreTests extends ESSingleNodeTestCase {
|
||||||
lookup, Collections.<String, Object>emptyMap());
|
lookup, Collections.<String, Object>emptyMap());
|
||||||
assertFalse(ss.needsScores());
|
assertFalse(ss.needsScores());
|
||||||
|
|
||||||
compiled = service.compile("input.doc['d'].value", Collections.emptyMap());
|
compiled = service.compile("doc['d'].value", Collections.emptyMap());
|
||||||
ss = service.search(new CompiledScript(ScriptType.INLINE, "randomName", "painless", compiled),
|
ss = service.search(new CompiledScript(ScriptType.INLINE, "randomName", "painless", compiled),
|
||||||
lookup, Collections.<String, Object>emptyMap());
|
lookup, Collections.<String, Object>emptyMap());
|
||||||
assertFalse(ss.needsScores());
|
assertFalse(ss.needsScores());
|
||||||
|
@ -56,7 +56,7 @@ public class NeedsScoreTests extends ESSingleNodeTestCase {
|
||||||
lookup, Collections.<String, Object>emptyMap());
|
lookup, Collections.<String, Object>emptyMap());
|
||||||
assertTrue(ss.needsScores());
|
assertTrue(ss.needsScores());
|
||||||
|
|
||||||
compiled = service.compile("input.doc['d'].value * _score", Collections.emptyMap());
|
compiled = service.compile("doc['d'].value * _score", Collections.emptyMap());
|
||||||
ss = service.search(new CompiledScript(ScriptType.INLINE, "randomName", "painless", compiled),
|
ss = service.search(new CompiledScript(ScriptType.INLINE, "randomName", "painless", compiled),
|
||||||
lookup, Collections.<String, Object>emptyMap());
|
lookup, Collections.<String, Object>emptyMap());
|
||||||
assertTrue(ss.needsScores());
|
assertTrue(ss.needsScores());
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch under one or more contributor
|
||||||
|
* license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright
|
||||||
|
* ownership. Elasticsearch licenses this file to you under
|
||||||
|
* the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
* not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.elasticsearch.painless;
|
||||||
|
|
||||||
|
/** Tests for special reserved words such as _score */
|
||||||
|
public class ReservedWordTests extends ScriptTestCase {
|
||||||
|
|
||||||
|
/** check that we can't declare a variable of _score, its really reserved! */
|
||||||
|
public void testScoreVar() {
|
||||||
|
IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
|
||||||
|
exec("int _score = 5; return _score;");
|
||||||
|
});
|
||||||
|
assertTrue(expected.getMessage().contains("Variable name [_score] already defined"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** check that we can't write to _score, its read-only! */
|
||||||
|
public void testScoreStore() {
|
||||||
|
IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
|
||||||
|
exec("_score = 5; return _score;");
|
||||||
|
});
|
||||||
|
assertTrue(expected.getMessage().contains("Variable [_score] is read-only"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** check that we can't declare a variable of doc, its really reserved! */
|
||||||
|
public void testDocVar() {
|
||||||
|
IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
|
||||||
|
exec("int doc = 5; return doc;");
|
||||||
|
});
|
||||||
|
assertTrue(expected.getMessage().contains("Variable name [doc] already defined"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** check that we can't write to _score, its read-only! */
|
||||||
|
public void testDocStore() {
|
||||||
|
IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
|
||||||
|
exec("doc = 5; return doc;");
|
||||||
|
});
|
||||||
|
assertTrue(expected.getMessage().contains("Variable [doc] is read-only"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,7 @@
|
||||||
put_script:
|
put_script:
|
||||||
id: "1"
|
id: "1"
|
||||||
lang: "painless"
|
lang: "painless"
|
||||||
body: { "script": "_score * input.doc[\"myParent.weight\"].value" }
|
body: { "script": "_score * doc[\"myParent.weight\"].value" }
|
||||||
- match: { acknowledged: true }
|
- match: { acknowledged: true }
|
||||||
|
|
||||||
- do:
|
- do:
|
||||||
|
@ -15,7 +15,7 @@
|
||||||
- match: { found: true }
|
- match: { found: true }
|
||||||
- match: { lang: painless }
|
- match: { lang: painless }
|
||||||
- match: { _id: "1" }
|
- match: { _id: "1" }
|
||||||
- match: { "script": "_score * input.doc[\"myParent.weight\"].value" }
|
- match: { "script": "_score * doc[\"myParent.weight\"].value" }
|
||||||
|
|
||||||
- do:
|
- do:
|
||||||
catch: missing
|
catch: missing
|
||||||
|
@ -44,11 +44,11 @@
|
||||||
put_script:
|
put_script:
|
||||||
id: "1"
|
id: "1"
|
||||||
lang: "painless"
|
lang: "painless"
|
||||||
body: { "script": "_score * foo bar + input.doc[\"myParent.weight\"].value" }
|
body: { "script": "_score * foo bar + doc[\"myParent.weight\"].value" }
|
||||||
|
|
||||||
- do:
|
- do:
|
||||||
catch: /Unable.to.parse.*/
|
catch: /Unable.to.parse.*/
|
||||||
put_script:
|
put_script:
|
||||||
id: "1"
|
id: "1"
|
||||||
lang: "painless"
|
lang: "painless"
|
||||||
body: { "script": "_score * foo bar + input.doc[\"myParent.weight\"].value" }
|
body: { "script": "_score * foo bar + doc[\"myParent.weight\"].value" }
|
||||||
|
|
|
@ -28,7 +28,7 @@ setup:
|
||||||
script_fields:
|
script_fields:
|
||||||
bar:
|
bar:
|
||||||
script:
|
script:
|
||||||
inline: "input.doc['foo'].value + input.x;"
|
inline: "doc['foo'].value + input.x;"
|
||||||
lang: painless
|
lang: painless
|
||||||
params:
|
params:
|
||||||
x: "bbb"
|
x: "bbb"
|
||||||
|
|
|
@ -29,12 +29,12 @@
|
||||||
query:
|
query:
|
||||||
script:
|
script:
|
||||||
script:
|
script:
|
||||||
inline: "input.doc['num1'].value > 1;"
|
inline: "doc['num1'].value > 1;"
|
||||||
lang: painless
|
lang: painless
|
||||||
script_fields:
|
script_fields:
|
||||||
sNum1:
|
sNum1:
|
||||||
script:
|
script:
|
||||||
inline: "input.doc['num1'].value;"
|
inline: "doc['num1'].value;"
|
||||||
lang: painless
|
lang: painless
|
||||||
sort:
|
sort:
|
||||||
num1:
|
num1:
|
||||||
|
@ -51,7 +51,7 @@
|
||||||
query:
|
query:
|
||||||
script:
|
script:
|
||||||
script:
|
script:
|
||||||
inline: "input.doc['num1'].value > input.param1;"
|
inline: "doc['num1'].value > input.param1;"
|
||||||
lang: painless
|
lang: painless
|
||||||
params:
|
params:
|
||||||
param1: 1
|
param1: 1
|
||||||
|
@ -59,7 +59,7 @@
|
||||||
script_fields:
|
script_fields:
|
||||||
sNum1:
|
sNum1:
|
||||||
script:
|
script:
|
||||||
inline: "return input.doc['num1'].value;"
|
inline: "return doc['num1'].value;"
|
||||||
lang: painless
|
lang: painless
|
||||||
sort:
|
sort:
|
||||||
num1:
|
num1:
|
||||||
|
@ -76,7 +76,7 @@
|
||||||
query:
|
query:
|
||||||
script:
|
script:
|
||||||
script:
|
script:
|
||||||
inline: "input.doc['num1'].value > input.param1;"
|
inline: "doc['num1'].value > input.param1;"
|
||||||
lang: painless
|
lang: painless
|
||||||
params:
|
params:
|
||||||
param1: -1
|
param1: -1
|
||||||
|
@ -84,7 +84,7 @@
|
||||||
script_fields:
|
script_fields:
|
||||||
sNum1:
|
sNum1:
|
||||||
script:
|
script:
|
||||||
inline: "input.doc['num1'].value;"
|
inline: "doc['num1'].value;"
|
||||||
lang: painless
|
lang: painless
|
||||||
sort:
|
sort:
|
||||||
num1:
|
num1:
|
||||||
|
@ -126,7 +126,7 @@
|
||||||
"script_score": {
|
"script_score": {
|
||||||
"script": {
|
"script": {
|
||||||
"lang": "painless",
|
"lang": "painless",
|
||||||
"inline": "input.doc['num1'].value"
|
"inline": "doc['num1'].value"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
|
@ -148,7 +148,7 @@
|
||||||
"script_score": {
|
"script_score": {
|
||||||
"script": {
|
"script": {
|
||||||
"lang": "painless",
|
"lang": "painless",
|
||||||
"inline": "-input.doc['num1'].value"
|
"inline": "-doc['num1'].value"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
|
@ -170,7 +170,7 @@
|
||||||
"script_score": {
|
"script_score": {
|
||||||
"script": {
|
"script": {
|
||||||
"lang": "painless",
|
"lang": "painless",
|
||||||
"inline": "Math.pow(input.doc['num1'].value, 2)"
|
"inline": "Math.pow(doc['num1'].value, 2)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
|
@ -192,7 +192,7 @@
|
||||||
"script_score": {
|
"script_score": {
|
||||||
"script": {
|
"script": {
|
||||||
"lang": "painless",
|
"lang": "painless",
|
||||||
"inline": "Math.max(input.doc['num1'].value, 1)"
|
"inline": "Math.max(doc['num1'].value, 1)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
|
@ -214,7 +214,7 @@
|
||||||
"script_score": {
|
"script_score": {
|
||||||
"script": {
|
"script": {
|
||||||
"lang": "painless",
|
"lang": "painless",
|
||||||
"inline": "input.doc['num1'].value * _score"
|
"inline": "doc['num1'].value * _score"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
|
@ -357,7 +357,7 @@
|
||||||
script_fields:
|
script_fields:
|
||||||
foobar:
|
foobar:
|
||||||
script:
|
script:
|
||||||
inline: "input.doc['f'].values.size()"
|
inline: "doc['f'].values.size()"
|
||||||
lang: painless
|
lang: painless
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue