Merge pull request #18262 from rmuir/painless_doc_access

Painless doc access
This commit is contained in:
Robert Muir 2016-05-11 00:58:24 -04:00
commit 2e4f87d2ce
11 changed files with 117 additions and 22 deletions

View File

@ -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);

View File

@ -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;
} }

View File

@ -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);
} }

View File

@ -434,6 +434,12 @@ class Metadata {
*/ */
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.
*/ */

View File

@ -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;
@ -47,6 +48,11 @@ final class ScriptImpl implements ExecutableScript, LeafSearchScript {
*/ */
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
* @see #setScorer(Scorer) * @see #setScorer(Scorer)
@ -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);
} }
/** /**

View File

@ -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);

View File

@ -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());

View File

@ -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"));
}
}

View File

@ -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" }

View File

@ -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"

View File

@ -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