Skip to content

Commit 43cb240

Browse files
authored
SQL: Whitelist SQL utility class for better scripting (#30681)
Add SQL class for reusing code inside SQL functions within Painless Fix #29832
1 parent d4262de commit 43cb240

File tree

10 files changed

+94
-66
lines changed

10 files changed

+94
-66
lines changed

x-pack/plugin/sql/build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ esplugin {
55
name 'x-pack-sql'
66
description 'The Elasticsearch plugin that powers SQL for Elasticsearch'
77
classname 'org.elasticsearch.xpack.sql.plugin.SqlPlugin'
8-
extendedPlugins = ['x-pack-core']
8+
extendedPlugins = ['x-pack-core', 'lang-painless']
99
}
1010

1111
configurations {
@@ -20,6 +20,7 @@ integTest.enabled = false
2020

2121
dependencies {
2222
compileOnly "org.elasticsearch.plugin:x-pack-core:${version}"
23+
compileOnly project(':modules:lang-painless')
2324
compile project('sql-proto')
2425
compile "org.elasticsearch.plugin:aggs-matrix-stats-client:${version}"
2526
compile "org.antlr:antlr4-runtime:4.5.3"

x-pack/plugin/sql/licenses/antlr4-runtime-4.5.3.jar.sha1

Lines changed: 0 additions & 1 deletion
This file was deleted.

x-pack/plugin/sql/licenses/antlr4-runtime-LICENSE.txt

Lines changed: 0 additions & 26 deletions
This file was deleted.

x-pack/plugin/sql/licenses/antlr4-runtime-NOTICE.txt

Whitespace-only changes.

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/datetime/DateTimeFunction.java

Lines changed: 20 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,18 @@ public abstract class DateTimeFunction extends UnaryScalarFunction {
5151
protected final NodeInfo<DateTimeFunction> info() {
5252
return NodeInfo.create(this, ctorForInfo(), field(), timeZone());
5353
}
54+
5455
protected abstract NodeInfo.NodeCtor2<Expression, TimeZone, DateTimeFunction> ctorForInfo();
5556

57+
@Override
58+
protected TypeResolution resolveType() {
59+
if (field().dataType() == DataType.DATE) {
60+
return TypeResolution.TYPE_RESOLVED;
61+
}
62+
return new TypeResolution("Function [" + functionName() + "] cannot be applied on a non-date expression (["
63+
+ Expressions.name(field()) + "] of type [" + field().dataType().esType + "])");
64+
}
65+
5666
public TimeZone timeZone() {
5767
return timeZone;
5868
}
@@ -69,47 +79,24 @@ public Object fold() {
6979
return null;
7080
}
7181

72-
ZonedDateTime time = ZonedDateTime.ofInstant(
73-
Instant.ofEpochMilli(folded.getMillis()), ZoneId.of(timeZone.getID()));
74-
return time.get(chronoField());
82+
return dateTimeChrono(folded.getMillis(), timeZone.getID(), chronoField().name());
7583
}
7684

77-
@Override
78-
protected TypeResolution resolveType() {
79-
if (field().dataType() == DataType.DATE) {
80-
return TypeResolution.TYPE_RESOLVED;
81-
}
82-
return new TypeResolution("Function [" + functionName() + "] cannot be applied on a non-date expression (["
83-
+ Expressions.name(field()) + "] of type [" + field().dataType().esType + "])");
85+
public static Integer dateTimeChrono(long millis, String tzId, String chronoName) {
86+
ZonedDateTime time = ZonedDateTime.ofInstant(Instant.ofEpochMilli(millis), ZoneId.of(tzId));
87+
return Integer.valueOf(time.get(ChronoField.valueOf(chronoName)));
8488
}
8589

8690
@Override
8791
protected ScriptTemplate asScriptFrom(FieldAttribute field) {
8892
ParamsBuilder params = paramsBuilder();
8993

9094
String template = null;
91-
if (TimeZone.getTimeZone("UTC").equals(timeZone)) {
92-
// TODO: it would be nice to be able to externalize the extract function and reuse the script across all extractors
93-
template = formatTemplate("doc[{}].value.get" + extractFunction() + "()");
94-
params.variable(field.name());
95-
} else {
96-
// TODO ewwww
97-
/*
98-
* This uses the Java 8 time API because Painless doesn't whitelist creation of new
99-
* Joda classes.
100-
*
101-
* The actual script is
102-
* ZonedDateTime.ofInstant(Instant.ofEpochMilli(<insert doc field>.value.millis),
103-
* ZoneId.of(<insert user tz>)).get(ChronoField.get(MONTH_OF_YEAR))
104-
*/
105-
106-
template = formatTemplate("ZonedDateTime.ofInstant(Instant.ofEpochMilli(doc[{}].value.millis), "
107-
+ "ZoneId.of({})).get(ChronoField.valueOf({}))");
108-
params.variable(field.name())
109-
.variable(timeZone.getID())
110-
.variable(chronoField().name());
111-
}
112-
95+
template = formatTemplate("{sql}.dateTimeChrono(doc[{}].value.millis, {}, {})");
96+
params.variable(field.name())
97+
.variable(timeZone.getID())
98+
.variable(chronoField().name());
99+
113100
return new ScriptTemplate(template, params.build(), dataType());
114101
}
115102

@@ -119,10 +106,6 @@ protected ScriptTemplate asScriptFrom(AggregateFunctionAttribute aggregate) {
119106
throw new UnsupportedOperationException();
120107
}
121108

122-
protected String extractFunction() {
123-
return getClass().getSimpleName();
124-
}
125-
126109
/**
127110
* Used for generating the painless script version of this function when the time zone is not UTC
128111
*/
@@ -164,4 +147,4 @@ public boolean equals(Object obj) {
164147
public int hashCode() {
165148
return Objects.hash(field(), timeZone);
166149
}
167-
}
150+
}

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/script/ScriptTemplate.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import org.elasticsearch.script.Script;
99
import org.elasticsearch.script.ScriptType;
10+
import org.elasticsearch.xpack.sql.expression.function.scalar.whitelist.InternalSqlScriptUtils;
1011
import org.elasticsearch.xpack.sql.type.DataType;
1112
import org.elasticsearch.xpack.sql.util.StringUtils;
1213

@@ -92,6 +93,6 @@ public String toString() {
9293
}
9394

9495
public static String formatTemplate(String template) {
95-
return template.replace("{}", "params.%s");
96+
return template.replace("{sql}", InternalSqlScriptUtils.class.getSimpleName()).replace("{}", "params.%s");
9697
}
9798
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
package org.elasticsearch.xpack.sql.expression.function.scalar.whitelist;
7+
8+
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateTimeFunction;
9+
10+
/**
11+
* Whitelisted class for SQL scripts.
12+
* Acts as a registry of the various static methods used <b>internally</b> by the scalar functions
13+
* (to simplify the whitelist definition).
14+
*/
15+
public final class InternalSqlScriptUtils {
16+
17+
private InternalSqlScriptUtils() {}
18+
19+
public static Integer dateTimeChrono(long millis, String tzId, String chronoName) {
20+
return DateTimeFunction.dateTimeChrono(millis, tzId, chronoName);
21+
}
22+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
package org.elasticsearch.xpack.sql.plugin;
7+
8+
import org.elasticsearch.painless.spi.PainlessExtension;
9+
import org.elasticsearch.painless.spi.Whitelist;
10+
import org.elasticsearch.painless.spi.WhitelistLoader;
11+
import org.elasticsearch.script.FilterScript;
12+
import org.elasticsearch.script.ScriptContext;
13+
import org.elasticsearch.script.SearchScript;
14+
15+
import java.util.HashMap;
16+
import java.util.List;
17+
import java.util.Map;
18+
19+
import static java.util.Collections.singletonList;
20+
21+
public class SqlPainlessExtension implements PainlessExtension {
22+
23+
private static final Whitelist WHITELIST = WhitelistLoader.loadFromResourceFiles(SqlPainlessExtension.class, "sql_whitelist.txt");
24+
25+
@Override
26+
public Map<ScriptContext<?>, List<Whitelist>> getContextWhitelists() {
27+
Map<ScriptContext<?>, List<Whitelist>> whitelist = new HashMap<>();
28+
List<Whitelist> list = singletonList(WHITELIST);
29+
whitelist.put(FilterScript.CONTEXT, list);
30+
whitelist.put(SearchScript.AGGS_CONTEXT, list);
31+
whitelist.put(SearchScript.CONTEXT, list);
32+
whitelist.put(SearchScript.SCRIPT_SORT_CONTEXT, list);
33+
return whitelist;
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
org.elasticsearch.xpack.sql.plugin.SqlPainlessExtension
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#
2+
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
# or more contributor license agreements. Licensed under the Elastic License;
4+
# you may not use this file except in compliance with the Elastic License.
5+
#
6+
7+
# This file contains a whitelist for SQL specific utilities available inside SQL scripting
8+
9+
class org.elasticsearch.xpack.sql.expression.function.scalar.whitelist.InternalSqlScriptUtils {
10+
11+
Integer dateTimeChrono(long, String, String)
12+
}

0 commit comments

Comments
 (0)