SQL: Whitelist SQL utility class for better scripting (#30681)
Add SQL class for reusing code inside SQL functions within Painless Fix #29832
This commit is contained in:
parent
d4262de83a
commit
43cb24035e
|
@ -5,7 +5,7 @@ esplugin {
|
|||
name 'x-pack-sql'
|
||||
description 'The Elasticsearch plugin that powers SQL for Elasticsearch'
|
||||
classname 'org.elasticsearch.xpack.sql.plugin.SqlPlugin'
|
||||
extendedPlugins = ['x-pack-core']
|
||||
extendedPlugins = ['x-pack-core', 'lang-painless']
|
||||
}
|
||||
|
||||
configurations {
|
||||
|
@ -20,6 +20,7 @@ integTest.enabled = false
|
|||
|
||||
dependencies {
|
||||
compileOnly "org.elasticsearch.plugin:x-pack-core:${version}"
|
||||
compileOnly project(':modules:lang-painless')
|
||||
compile project('sql-proto')
|
||||
compile "org.elasticsearch.plugin:aggs-matrix-stats-client:${version}"
|
||||
compile "org.antlr:antlr4-runtime:4.5.3"
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
2609e36f18f7e8d593cc1cddfb2ac776dc96b8e0
|
|
@ -1,26 +0,0 @@
|
|||
[The "BSD license"]
|
||||
Copyright (c) 2015 Terence Parr, Sam Harwell
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
3. The name of the author may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -51,8 +51,18 @@ public abstract class DateTimeFunction extends UnaryScalarFunction {
|
|||
protected final NodeInfo<DateTimeFunction> info() {
|
||||
return NodeInfo.create(this, ctorForInfo(), field(), timeZone());
|
||||
}
|
||||
|
||||
protected abstract NodeInfo.NodeCtor2<Expression, TimeZone, DateTimeFunction> ctorForInfo();
|
||||
|
||||
@Override
|
||||
protected TypeResolution resolveType() {
|
||||
if (field().dataType() == DataType.DATE) {
|
||||
return TypeResolution.TYPE_RESOLVED;
|
||||
}
|
||||
return new TypeResolution("Function [" + functionName() + "] cannot be applied on a non-date expression (["
|
||||
+ Expressions.name(field()) + "] of type [" + field().dataType().esType + "])");
|
||||
}
|
||||
|
||||
public TimeZone timeZone() {
|
||||
return timeZone;
|
||||
}
|
||||
|
@ -69,18 +79,12 @@ public abstract class DateTimeFunction extends UnaryScalarFunction {
|
|||
return null;
|
||||
}
|
||||
|
||||
ZonedDateTime time = ZonedDateTime.ofInstant(
|
||||
Instant.ofEpochMilli(folded.getMillis()), ZoneId.of(timeZone.getID()));
|
||||
return time.get(chronoField());
|
||||
return dateTimeChrono(folded.getMillis(), timeZone.getID(), chronoField().name());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TypeResolution resolveType() {
|
||||
if (field().dataType() == DataType.DATE) {
|
||||
return TypeResolution.TYPE_RESOLVED;
|
||||
}
|
||||
return new TypeResolution("Function [" + functionName() + "] cannot be applied on a non-date expression (["
|
||||
+ Expressions.name(field()) + "] of type [" + field().dataType().esType + "])");
|
||||
public static Integer dateTimeChrono(long millis, String tzId, String chronoName) {
|
||||
ZonedDateTime time = ZonedDateTime.ofInstant(Instant.ofEpochMilli(millis), ZoneId.of(tzId));
|
||||
return Integer.valueOf(time.get(ChronoField.valueOf(chronoName)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -88,27 +92,10 @@ public abstract class DateTimeFunction extends UnaryScalarFunction {
|
|||
ParamsBuilder params = paramsBuilder();
|
||||
|
||||
String template = null;
|
||||
if (TimeZone.getTimeZone("UTC").equals(timeZone)) {
|
||||
// TODO: it would be nice to be able to externalize the extract function and reuse the script across all extractors
|
||||
template = formatTemplate("doc[{}].value.get" + extractFunction() + "()");
|
||||
params.variable(field.name());
|
||||
} else {
|
||||
// TODO ewwww
|
||||
/*
|
||||
* This uses the Java 8 time API because Painless doesn't whitelist creation of new
|
||||
* Joda classes.
|
||||
*
|
||||
* The actual script is
|
||||
* ZonedDateTime.ofInstant(Instant.ofEpochMilli(<insert doc field>.value.millis),
|
||||
* ZoneId.of(<insert user tz>)).get(ChronoField.get(MONTH_OF_YEAR))
|
||||
*/
|
||||
|
||||
template = formatTemplate("ZonedDateTime.ofInstant(Instant.ofEpochMilli(doc[{}].value.millis), "
|
||||
+ "ZoneId.of({})).get(ChronoField.valueOf({}))");
|
||||
template = formatTemplate("{sql}.dateTimeChrono(doc[{}].value.millis, {}, {})");
|
||||
params.variable(field.name())
|
||||
.variable(timeZone.getID())
|
||||
.variable(chronoField().name());
|
||||
}
|
||||
|
||||
return new ScriptTemplate(template, params.build(), dataType());
|
||||
}
|
||||
|
@ -119,10 +106,6 @@ public abstract class DateTimeFunction extends UnaryScalarFunction {
|
|||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
protected String extractFunction() {
|
||||
return getClass().getSimpleName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for generating the painless script version of this function when the time zone is not UTC
|
||||
*/
|
||||
|
|
|
@ -7,6 +7,7 @@ package org.elasticsearch.xpack.sql.expression.function.scalar.script;
|
|||
|
||||
import org.elasticsearch.script.Script;
|
||||
import org.elasticsearch.script.ScriptType;
|
||||
import org.elasticsearch.xpack.sql.expression.function.scalar.whitelist.InternalSqlScriptUtils;
|
||||
import org.elasticsearch.xpack.sql.type.DataType;
|
||||
import org.elasticsearch.xpack.sql.util.StringUtils;
|
||||
|
||||
|
@ -92,6 +93,6 @@ public class ScriptTemplate {
|
|||
}
|
||||
|
||||
public static String formatTemplate(String template) {
|
||||
return template.replace("{}", "params.%s");
|
||||
return template.replace("{sql}", InternalSqlScriptUtils.class.getSimpleName()).replace("{}", "params.%s");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.sql.expression.function.scalar.whitelist;
|
||||
|
||||
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateTimeFunction;
|
||||
|
||||
/**
|
||||
* Whitelisted class for SQL scripts.
|
||||
* Acts as a registry of the various static methods used <b>internally</b> by the scalar functions
|
||||
* (to simplify the whitelist definition).
|
||||
*/
|
||||
public final class InternalSqlScriptUtils {
|
||||
|
||||
private InternalSqlScriptUtils() {}
|
||||
|
||||
public static Integer dateTimeChrono(long millis, String tzId, String chronoName) {
|
||||
return DateTimeFunction.dateTimeChrono(millis, tzId, chronoName);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.sql.plugin;
|
||||
|
||||
import org.elasticsearch.painless.spi.PainlessExtension;
|
||||
import org.elasticsearch.painless.spi.Whitelist;
|
||||
import org.elasticsearch.painless.spi.WhitelistLoader;
|
||||
import org.elasticsearch.script.FilterScript;
|
||||
import org.elasticsearch.script.ScriptContext;
|
||||
import org.elasticsearch.script.SearchScript;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
|
||||
public class SqlPainlessExtension implements PainlessExtension {
|
||||
|
||||
private static final Whitelist WHITELIST = WhitelistLoader.loadFromResourceFiles(SqlPainlessExtension.class, "sql_whitelist.txt");
|
||||
|
||||
@Override
|
||||
public Map<ScriptContext<?>, List<Whitelist>> getContextWhitelists() {
|
||||
Map<ScriptContext<?>, List<Whitelist>> whitelist = new HashMap<>();
|
||||
List<Whitelist> list = singletonList(WHITELIST);
|
||||
whitelist.put(FilterScript.CONTEXT, list);
|
||||
whitelist.put(SearchScript.AGGS_CONTEXT, list);
|
||||
whitelist.put(SearchScript.CONTEXT, list);
|
||||
whitelist.put(SearchScript.SCRIPT_SORT_CONTEXT, list);
|
||||
return whitelist;
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
org.elasticsearch.xpack.sql.plugin.SqlPainlessExtension
|
|
@ -0,0 +1,12 @@
|
|||
#
|
||||
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
# or more contributor license agreements. Licensed under the Elastic License;
|
||||
# you may not use this file except in compliance with the Elastic License.
|
||||
#
|
||||
|
||||
# This file contains a whitelist for SQL specific utilities available inside SQL scripting
|
||||
|
||||
class org.elasticsearch.xpack.sql.expression.function.scalar.whitelist.InternalSqlScriptUtils {
|
||||
|
||||
Integer dateTimeChrono(long, String, String)
|
||||
}
|
Loading…
Reference in New Issue