Introduce SqlSettings for handling per-client configuration

To avoid leaking client information across the entire code-base, client
settings like TimeZone or pagination are stored in
SqlSession>SqlSettings which are available as a ThreadLocal (during
analysis) so that components that need them, can pick them up.

Since ES internally uses Joda, the date/time functionality relies on Joda,
whenever possible to match the behavior.

Original commit: elastic/x-pack-elasticsearch@20f41e2bb3
This commit is contained in:
Costin Leau 2017-07-20 19:28:04 +03:00
parent 8acacc4f7d
commit 76b429bfe2
48 changed files with 311 additions and 265 deletions

View File

@ -15,29 +15,29 @@ import java.util.Objects;
import java.util.TimeZone; import java.util.TimeZone;
public class QueryInitRequest extends Request { public class QueryInitRequest extends Request {
public final int fetchSize;
public final String query; public final String query;
public final int fetchSize;
public final TimeZone timeZone; public final TimeZone timeZone;
public final TimeoutInfo timeout; public final TimeoutInfo timeout;
public QueryInitRequest(int fetchSize, String query, TimeZone timeZone, TimeoutInfo timeout) { public QueryInitRequest(String query, int fetchSize, TimeZone timeZone, TimeoutInfo timeout) {
this.fetchSize = fetchSize;
this.query = query; this.query = query;
this.fetchSize = fetchSize;
this.timeZone = timeZone; this.timeZone = timeZone;
this.timeout = timeout; this.timeout = timeout;
} }
QueryInitRequest(int clientVersion, DataInput in) throws IOException { QueryInitRequest(int clientVersion, DataInput in) throws IOException {
fetchSize = in.readInt();
query = in.readUTF(); query = in.readUTF();
fetchSize = in.readInt();
timeZone = TimeZone.getTimeZone(in.readUTF()); timeZone = TimeZone.getTimeZone(in.readUTF());
timeout = new TimeoutInfo(in); timeout = new TimeoutInfo(in);
} }
@Override @Override
public void write(DataOutput out) throws IOException { public void write(DataOutput out) throws IOException {
out.writeInt(fetchSize);
out.writeUTF(query); out.writeUTF(query);
out.writeInt(fetchSize);
out.writeUTF(timeZone.getID()); out.writeUTF(timeZone.getID());
timeout.write(out); timeout.write(out);
} }

View File

@ -16,7 +16,7 @@ import static org.elasticsearch.xpack.sql.jdbc.net.protocol.TimeoutInfoTests.ran
public class QueryInitRequestTests extends ESTestCase { public class QueryInitRequestTests extends ESTestCase {
public static QueryInitRequest randomQueryInitRequest() { public static QueryInitRequest randomQueryInitRequest() {
return new QueryInitRequest(between(0, Integer.MAX_VALUE), randomAlphaOfLength(5), randomTimeZone(random()), randomTimeoutInfo()); return new QueryInitRequest(randomAlphaOfLength(5), between(0, Integer.MAX_VALUE), randomTimeZone(random()), randomTimeoutInfo());
} }
public void testRoundTrip() throws IOException { public void testRoundTrip() throws IOException {
@ -25,8 +25,8 @@ public class QueryInitRequestTests extends ESTestCase {
public void testToString() { public void testToString() {
assertEquals("QueryInitRequest<query=[SELECT * FROM test.doc]>", assertEquals("QueryInitRequest<query=[SELECT * FROM test.doc]>",
new QueryInitRequest(10, "SELECT * FROM test.doc", TimeZone.getTimeZone("UTC"), new TimeoutInfo(1, 1, 1)).toString()); new QueryInitRequest("SELECT * FROM test.doc", 10, TimeZone.getTimeZone("UTC"), new TimeoutInfo(1, 1, 1)).toString());
assertEquals("QueryInitRequest<query=[SELECT * FROM test.doc] timeZone=[GMT-05:00]>", assertEquals("QueryInitRequest<query=[SELECT * FROM test.doc] timeZone=[GMT-05:00]>",
new QueryInitRequest(10, "SELECT * FROM test.doc", TimeZone.getTimeZone("GMT-5"), new TimeoutInfo(1, 1, 1)).toString()); new QueryInitRequest("SELECT * FROM test.doc", 10, TimeZone.getTimeZone("GMT-5"), new TimeoutInfo(1, 1, 1)).toString());
} }
} }

View File

@ -44,8 +44,13 @@ public class JdbcConfiguration extends ConnectionConfiguration {
// can be out/err/url // can be out/err/url
static final String DEBUG_OUTPUT_DEFAULT = "err"; static final String DEBUG_OUTPUT_DEFAULT = "err";
static final String TIME_ZONE = "time_zone"; static final String TIME_ZONE = "timezone";
static final String TIME_ZONE_DEFAULT = "UTC_CALENDAR";
// follow the JDBC spec and use the JVM default...
// to avoid inconsistency, the default is picked up once at startup and reused across connections
// to cater to the principle of least surprise
// really, the way to move forward is to specify a calendar or the timezone manually
static final String TIME_ZONE_DEFAULT = TimeZone.getDefault().getID();
private static final List<String> KNOWN_OPTIONS = Arrays.asList(DEBUG, DEBUG_OUTPUT, TIME_ZONE); private static final List<String> KNOWN_OPTIONS = Arrays.asList(DEBUG, DEBUG_OUTPUT, TIME_ZONE);

View File

@ -5,6 +5,9 @@
*/ */
package org.elasticsearch.xpack.sql.jdbc.jdbc; package org.elasticsearch.xpack.sql.jdbc.jdbc;
import org.elasticsearch.xpack.sql.jdbc.debug.Debug;
import org.elasticsearch.xpack.sql.jdbc.util.Version;
import java.io.Closeable; import java.io.Closeable;
import java.sql.Connection; import java.sql.Connection;
import java.sql.DriverManager; import java.sql.DriverManager;
@ -15,9 +18,6 @@ import java.util.Properties;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.elasticsearch.xpack.sql.jdbc.debug.Debug;
import org.elasticsearch.xpack.sql.jdbc.util.Version;
public class JdbcDriver implements java.sql.Driver, Closeable { public class JdbcDriver implements java.sql.Driver, Closeable {
static { static {
@ -55,7 +55,7 @@ public class JdbcDriver implements java.sql.Driver, Closeable {
private static JdbcConfiguration initInfo(String url, Properties props) { private static JdbcConfiguration initInfo(String url, Properties props) {
JdbcConfiguration ci = new JdbcConfiguration(url, props); JdbcConfiguration ci = new JdbcConfiguration(url, props);
if (DriverManager.getLoginTimeout() > 0) { if (DriverManager.getLoginTimeout() > 0) {
ci.setConnectTimeout(TimeUnit.SECONDS.toMillis(DriverManager.getLoginTimeout())); ci.connectTimeout(TimeUnit.SECONDS.toMillis(DriverManager.getLoginTimeout()));
} }
return ci; return ci;
} }

View File

@ -40,10 +40,10 @@ import static java.lang.String.format;
class JdbcResultSet implements ResultSet, JdbcWrapper { class JdbcResultSet implements ResultSet, JdbcWrapper {
// temporary calendar instance (per connection) used for normalizing the date and time // temporary calendar instance (per connection) used for normalizing the date and time
// even though the info is already in UTC_CALENDAR format, JDBC 3.0 requires java.sql.Time to have its date // even though the cfg is already in UTC format, JDBC 3.0 requires java.sql.Time to have its date
// removed (set to Jan 01 1970) and java.sql.Date to have its HH:mm:ss component removed // removed (set to Jan 01 1970) and java.sql.Date to have its HH:mm:ss component removed
// instead of dealing with longs, a Calendar object is used instead // instead of dealing with longs, a Calendar object is used instead
private final Calendar DEFAULT_CALENDAR = TypeConverter.defaultCalendar(); private final Calendar defaultCalendar;
private final JdbcStatement statement; private final JdbcStatement statement;
private final Cursor cursor; private final Cursor cursor;
@ -57,6 +57,8 @@ class JdbcResultSet implements ResultSet, JdbcWrapper {
JdbcResultSet(JdbcStatement statement, Cursor cursor) { JdbcResultSet(JdbcStatement statement, Cursor cursor) {
this.statement = statement; this.statement = statement;
this.cursor = cursor; this.cursor = cursor;
// TODO: should we consider the locale as well?
this.defaultCalendar = Calendar.getInstance(statement.cfg.timeZone(), Locale.ROOT);
List<ColumnInfo> columns = cursor.columns(); List<ColumnInfo> columns = cursor.columns();
for (int i = 0; i < columns.size(); i++) { for (int i = 0; i < columns.size(); i++) {
@ -239,7 +241,7 @@ class JdbcResultSet implements ResultSet, JdbcWrapper {
} }
private Calendar safeCalendar(Calendar calendar) { private Calendar safeCalendar(Calendar calendar) {
return calendar == null ? DEFAULT_CALENDAR : calendar; return calendar == null ? defaultCalendar : calendar;
} }
@Override @Override

View File

@ -5,6 +5,9 @@
*/ */
package org.elasticsearch.xpack.sql.jdbc.jdbc; package org.elasticsearch.xpack.sql.jdbc.jdbc;
import org.elasticsearch.xpack.sql.jdbc.net.client.Cursor;
import org.elasticsearch.xpack.sql.jdbc.net.client.RequestMeta;
import java.sql.Connection; import java.sql.Connection;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
@ -13,13 +16,10 @@ import java.sql.SQLWarning;
import java.sql.Statement; import java.sql.Statement;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.elasticsearch.xpack.sql.jdbc.net.client.Cursor;
import org.elasticsearch.xpack.sql.jdbc.net.client.RequestMeta;
class JdbcStatement implements Statement, JdbcWrapper { class JdbcStatement implements Statement, JdbcWrapper {
final JdbcConnection con; final JdbcConnection con;
final JdbcConfiguration info; final JdbcConfiguration cfg;
private boolean closed = false; private boolean closed = false;
private boolean closeOnCompletion = false; private boolean closeOnCompletion = false;
@ -30,7 +30,7 @@ class JdbcStatement implements Statement, JdbcWrapper {
JdbcStatement(JdbcConnection jdbcConnection, JdbcConfiguration info) { JdbcStatement(JdbcConnection jdbcConnection, JdbcConfiguration info) {
this.con = jdbcConnection; this.con = jdbcConnection;
this.info = info; this.cfg = info;
} }
@Override @Override
@ -155,7 +155,7 @@ class JdbcStatement implements Statement, JdbcWrapper {
// close previous result set // close previous result set
closeResultSet(); closeResultSet();
Cursor cursor = con.client.query(sql, info.timeZone(), requestMeta); Cursor cursor = con.client.query(sql, requestMeta);
rs = new JdbcResultSet(this, cursor); rs = new JdbcResultSet(this, cursor);
} }

View File

@ -18,7 +18,6 @@ import java.time.OffsetTime;
import java.util.Calendar; import java.util.Calendar;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
import java.util.Locale; import java.util.Locale;
import java.util.TimeZone;
import java.util.function.Function; import java.util.function.Function;
import static java.lang.String.format; import static java.lang.String.format;
@ -33,14 +32,8 @@ import static java.util.Calendar.YEAR;
abstract class TypeConverter { abstract class TypeConverter {
static final Calendar UTC_CALENDAR = Calendar.getInstance(TimeZone.getTimeZone("UTC_CALENDAR"), Locale.ROOT);
private static final long DAY_IN_MILLIS = 60 * 60 * 24; private static final long DAY_IN_MILLIS = 60 * 60 * 24;
static Calendar defaultCalendar() {
return (Calendar) UTC_CALENDAR.clone();
}
static Date convertDate(Long millis, Calendar cal) { static Date convertDate(Long millis, Calendar cal) {
return dateTimeConvert(millis, cal, c -> { return dateTimeConvert(millis, cal, c -> {
c.set(HOUR_OF_DAY, 0); c.set(HOUR_OF_DAY, 0);

View File

@ -5,6 +5,10 @@
*/ */
package org.elasticsearch.xpack.sql.jdbc.jdbcx; package org.elasticsearch.xpack.sql.jdbc.jdbcx;
import org.elasticsearch.xpack.sql.jdbc.debug.Debug;
import org.elasticsearch.xpack.sql.jdbc.jdbc.JdbcConfiguration;
import org.elasticsearch.xpack.sql.jdbc.jdbc.JdbcConnection;
import java.io.Closeable; import java.io.Closeable;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.sql.Connection; import java.sql.Connection;
@ -17,10 +21,6 @@ import java.util.logging.Logger;
import javax.sql.DataSource; import javax.sql.DataSource;
import org.elasticsearch.xpack.sql.jdbc.debug.Debug;
import org.elasticsearch.xpack.sql.jdbc.jdbc.JdbcConfiguration;
import org.elasticsearch.xpack.sql.jdbc.jdbc.JdbcConnection;
public class JdbcDataSource implements DataSource, Wrapper, Closeable { public class JdbcDataSource implements DataSource, Wrapper, Closeable {
private String url; private String url;
@ -86,7 +86,7 @@ public class JdbcDataSource implements DataSource, Wrapper, Closeable {
private Connection doGetConnection(Properties p) { private Connection doGetConnection(Properties p) {
JdbcConfiguration ci = new JdbcConfiguration(url, p); JdbcConfiguration ci = new JdbcConfiguration(url, p);
if (loginTimeout > 0) { if (loginTimeout > 0) {
ci.setConnectTimeout(TimeUnit.SECONDS.toMillis(loginTimeout)); ci.connectTimeout(TimeUnit.SECONDS.toMillis(loginTimeout));
} }
return new JdbcConnection(ci); return new JdbcConnection(ci);
} }

View File

@ -33,11 +33,11 @@ class HttpClient {
} }
void setNetworkTimeout(long millis) { void setNetworkTimeout(long millis) {
cfg.setNetworkTimeout(millis); cfg.networkTimeout(millis);
} }
long getNetworkTimeout() { long getNetworkTimeout() {
return cfg.getNetworkTimeout(); return cfg.networkTimeout();
} }
private URL url(String subPath) { private URL url(String subPath) {

View File

@ -37,7 +37,6 @@ import java.io.IOException;
import java.sql.SQLException; import java.sql.SQLException;
import java.time.Instant; import java.time.Instant;
import java.util.List; import java.util.List;
import java.util.TimeZone;
public class JdbcHttpClient implements Closeable { public class JdbcHttpClient implements Closeable {
@FunctionalInterface @FunctionalInterface
@ -65,9 +64,9 @@ public class JdbcHttpClient implements Closeable {
} }
} }
public Cursor query(String sql, TimeZone timeZone, RequestMeta meta) throws SQLException { public Cursor query(String sql, RequestMeta meta) throws SQLException {
int fetch = meta.fetchSize() >= 0 ? meta.fetchSize() : conCfg.pageSize(); int fetch = meta.fetchSize() >= 0 ? meta.fetchSize() : conCfg.pageSize();
QueryInitRequest request = new QueryInitRequest(fetch, sql, timeZone, timeout(meta)); QueryInitRequest request = new QueryInitRequest(sql, fetch, conCfg.timeZone(), timeout(meta));
BytesArray ba = http.put(out -> Proto.INSTANCE.writeRequest(request, out)); BytesArray ba = http.put(out -> Proto.INSTANCE.writeRequest(request, out));
QueryInitResponse response = doIO(ba, in -> (QueryInitResponse) readResponse(request, in)); QueryInitResponse response = doIO(ba, in -> (QueryInitResponse) readResponse(request, in));
return new DefaultCursor(this, response.requestId, (Page) response.data, meta); return new DefaultCursor(this, response.requestId, (Page) response.data, meta);
@ -149,8 +148,8 @@ public class JdbcHttpClient implements Closeable {
// timeout (in ms) // timeout (in ms)
long timeout = meta.timeoutInMs(); long timeout = meta.timeoutInMs();
if (timeout == 0) { if (timeout == 0) {
timeout = conCfg.getQueryTimeout(); timeout = conCfg.queryTimeout();
} }
return new TimeoutInfo(clientTime, timeout, conCfg.getPageTimeout()); return new TimeoutInfo(clientTime, timeout, conCfg.pageTimeout());
} }
} }

View File

@ -108,31 +108,31 @@ public class ConnectionConfiguration {
return ssl; return ssl;
} }
public void setConnectTimeout(long millis) { public void connectTimeout(long millis) {
connectTimeout = millis; connectTimeout = millis;
} }
public long getConnectTimeout() { public long connectTimeout() {
return connectTimeout; return connectTimeout;
} }
public void setNetworkTimeout(long millis) { public void networkTimeout(long millis) {
networkTimeout = millis; networkTimeout = millis;
} }
public long getNetworkTimeout() { public long networkTimeout() {
return networkTimeout; return networkTimeout;
} }
public void setQueryTimeout(long millis) { public void queryTimeout(long millis) {
queryTimeout = millis; queryTimeout = millis;
} }
public long getQueryTimeout() { public long queryTimeout() {
return queryTimeout; return queryTimeout;
} }
public long getPageTimeout() { public long pageTimeout() {
return pageTimeout; return pageTimeout;
} }

View File

@ -40,8 +40,8 @@ public class JreHttpUrlConnection implements Closeable {
throw new ClientException(ex, "Cannot setup connection to %s (%s)", url, ex.getMessage()); throw new ClientException(ex, "Cannot setup connection to %s (%s)", url, ex.getMessage());
} }
con.setConnectTimeout((int) cfg.getConnectTimeout()); con.setConnectTimeout((int) cfg.connectTimeout());
con.setReadTimeout((int) cfg.getNetworkTimeout()); con.setReadTimeout((int) cfg.networkTimeout());
con.setAllowUserInteraction(false); con.setAllowUserInteraction(false);
con.setUseCaches(false); con.setUseCaches(false);

View File

@ -47,6 +47,7 @@ import org.elasticsearch.xpack.sql.plan.logical.UnresolvedRelation;
import org.elasticsearch.xpack.sql.plan.logical.With; import org.elasticsearch.xpack.sql.plan.logical.With;
import org.elasticsearch.xpack.sql.rule.Rule; import org.elasticsearch.xpack.sql.rule.Rule;
import org.elasticsearch.xpack.sql.rule.RuleExecutor; import org.elasticsearch.xpack.sql.rule.RuleExecutor;
import org.elasticsearch.xpack.sql.session.SqlSession;
import org.elasticsearch.xpack.sql.tree.Node; import org.elasticsearch.xpack.sql.tree.Node;
import org.elasticsearch.xpack.sql.type.CompoundDataType; import org.elasticsearch.xpack.sql.type.CompoundDataType;
import org.elasticsearch.xpack.sql.util.StringUtils; import org.elasticsearch.xpack.sql.util.StringUtils;
@ -641,8 +642,7 @@ public class Analyzer extends RuleExecutor<LogicalPlan> {
// TODO: might be removed // TODO: might be removed
// dedicated count optimization // dedicated count optimization
if (name.toUpperCase(Locale.ROOT).equals("COUNT")) { if (name.toUpperCase(Locale.ROOT).equals("COUNT")) {
uf = new UnresolvedFunction(uf.location(), uf.name(), uf.distinct(), uf.timeZone(), uf = new UnresolvedFunction(uf.location(), uf.name(), uf.distinct(), singletonList(Literal.of(uf.arguments().get(0).location(), Integer.valueOf(1))));
singletonList(Literal.of(uf.arguments().get(0).location(), Integer.valueOf(1))));
} }
} }
@ -666,7 +666,7 @@ public class Analyzer extends RuleExecutor<LogicalPlan> {
throw new UnknownFunctionException(name, uf); throw new UnknownFunctionException(name, uf);
} }
// TODO: look into Generator for significant terms, etc.. // TODO: look into Generator for significant terms, etc..
Function f = functionRegistry.resolveFunction(uf); Function f = functionRegistry.resolveFunction(uf, SqlSession.CURRENT.get());
list.add(f); list.add(f);
return f; return f;

View File

@ -22,12 +22,10 @@ import org.elasticsearch.xpack.sql.session.SqlSession;
import org.elasticsearch.xpack.sql.session.SqlSettings; import org.elasticsearch.xpack.sql.session.SqlSettings;
import java.io.IOException; import java.io.IOException;
import java.util.TimeZone;
import java.util.function.Supplier; import java.util.function.Supplier;
public class PlanExecutor extends AbstractLifecycleComponent { public class PlanExecutor extends AbstractLifecycleComponent {
// NOCOMMIT prefer not to use AbstractLifecycleComponent because the reasons for its tradeoffs is lost to the mists of time // NOCOMMIT prefer not to use AbstractLifecycleComponent because the reasons for its tradeoffs is lost to the mists of time
private static final SqlSettings DEFAULTS = SqlSettings.EMPTY;
private final Client client; private final Client client;
@ -55,13 +53,17 @@ public class PlanExecutor extends AbstractLifecycleComponent {
return catalog; return catalog;
} }
public SqlSession newSession() { public SqlSession newSession(SqlSettings settings) {
return new SqlSession(DEFAULTS, client, parser, catalog, functionRegistry, analyzer, optimizer, planner); return new SqlSession(settings, client, parser, catalog, functionRegistry, analyzer, optimizer, planner);
} }
public void sql(String sql, TimeZone timeZone, ActionListener<RowSetCursor> listener) { public void sql(String sql, ActionListener<RowSetCursor> listener) {
SqlSession session = newSession(); sql(SqlSettings.EMPTY, sql, listener);
session.executable(sql, timeZone).execute(session, listener); }
public void sql(SqlSettings sqlSettings, String sql, ActionListener<RowSetCursor> listener) {
SqlSession session = newSession(sqlSettings);
session.executable(sql).execute(session, listener);
} }
@Override @Override

View File

@ -8,21 +8,25 @@ package org.elasticsearch.xpack.sql.expression.function;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException; import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
import org.elasticsearch.xpack.sql.expression.Expression; import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.function.aware.DistinctAware;
import org.elasticsearch.xpack.sql.expression.function.aware.TimeZoneAware;
import org.elasticsearch.xpack.sql.parser.ParsingException; import org.elasticsearch.xpack.sql.parser.ParsingException;
import org.elasticsearch.xpack.sql.session.SqlSettings;
import org.elasticsearch.xpack.sql.tree.Location; import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.tree.Node; import org.elasticsearch.xpack.sql.tree.Node;
import org.elasticsearch.xpack.sql.tree.NodeUtils; import org.elasticsearch.xpack.sql.tree.NodeUtils;
import org.elasticsearch.xpack.sql.tree.NodeUtils.NodeInfo; import org.elasticsearch.xpack.sql.tree.NodeUtils.NodeInfo;
import org.elasticsearch.xpack.sql.util.Assert; import org.elasticsearch.xpack.sql.util.Assert;
import org.elasticsearch.xpack.sql.util.StringUtils; import org.elasticsearch.xpack.sql.util.StringUtils;
import org.joda.time.DateTimeZone;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.TimeZone;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
@ -50,12 +54,12 @@ abstract class AbstractFunctionRegistry implements FunctionRegistry {
@Override @Override
public Function resolveFunction(UnresolvedFunction ur) { public Function resolveFunction(UnresolvedFunction ur, SqlSettings settings) {
FunctionDefinition def = defs.get(normalize(ur.name())); FunctionDefinition def = defs.get(normalize(ur.name()));
if (def == null) { if (def == null) {
throw new SqlIllegalArgumentException("Cannot find function %s; this should have been caught during analysis", ur.name()); throw new SqlIllegalArgumentException("Cannot find function %s; this should have been caught during analysis", ur.name());
} }
return createInstance(def.clazz(), ur); return createInstance(def.clazz(), ur, settings);
} }
@Override @Override
@ -95,42 +99,62 @@ abstract class AbstractFunctionRegistry implements FunctionRegistry {
} }
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
private static Function createInstance(Class<? extends Function> clazz, UnresolvedFunction ur) { private static Function createInstance(Class<? extends Function> clazz, UnresolvedFunction ur, SqlSettings settings) {
NodeInfo info = NodeUtils.info((Class<? extends Node>) clazz); NodeInfo info = NodeUtils.info((Class<? extends Node>) clazz);
Class<?> exp = ur.children().size() == 1 ? Expression.class : List.class; Class<?> exp = ur.children().size() == 1 ? Expression.class : List.class;
Object expVal = exp == Expression.class ? ur.children().get(0) : ur.children(); Object expVal = exp == Expression.class ? ur.children().get(0) : ur.children();
boolean distinctAware = true; boolean noExpression = false;
boolean noArgument = false; boolean distinctAware = DistinctAware.class.isAssignableFrom(clazz);
boolean tzAware = false; boolean timezoneAware = TimeZoneAware.class.isAssignableFrom(clazz);
// distinct ctor
if (!Arrays.equals(new Class[] { Location.class, exp, boolean.class }, info.ctr.getParameterTypes())) {
if (ur.distinct()) {
throw new ParsingException(ur.location(), "Function [%s] does not support DISTINCT yet it was specified", ur.name());
}
distinctAware = false;
// might be a constant function // check constructor signature
if (expVal instanceof List && ((List) expVal).isEmpty()) {
noArgument = Arrays.equals(new Class[] { Location.class }, info.ctr.getParameterTypes());
} // validate distinct ctor
else if (Arrays.equals(new Class[] { Location.class, exp, TimeZone.class }, info.ctr.getParameterTypes())) { if (!distinctAware && ur.distinct()) {
tzAware = true; throw new ParsingException(ur.location(), "Function [%s] does not support DISTINCT yet it was specified", ur.name());
}
// distinctless
else if (!Arrays.equals(new Class[] { Location.class, exp }, info.ctr.getParameterTypes())) {
throw new SqlIllegalArgumentException("No constructor with signature [%s, %s (,%s)?] found for [%s]",
Location.class, exp, boolean.class, clazz.getTypeName());
}
} }
List<Class> ctorSignature = new ArrayList<>();
ctorSignature.add(Location.class);
// might be a constant function
if (expVal instanceof List && ((List) expVal).isEmpty()) {
noExpression = Arrays.equals(new Class[] { Location.class }, info.ctr.getParameterTypes());
}
else {
ctorSignature.add(exp);
}
// aware stuff
if (distinctAware) {
ctorSignature.add(boolean.class);
}
if (timezoneAware) {
ctorSignature.add(DateTimeZone.class);
}
// validate
Assert.isTrue(Arrays.equals(ctorSignature.toArray(new Class[ctorSignature.size()]), info.ctr.getParameterTypes()),
"No constructor with signature %s found for [%s]", ctorSignature, clazz.getTypeName());
// now add the actual values
try { try {
// NOCOMMIT reflection here feels icky List<Object> args = new ArrayList<>();
Object[] args;
if (tzAware) { // always add location first
args = new Object[] { ur.location(), expVal, ur.timeZone() }; args.add(ur.location());
} else {
args = noArgument ? new Object[] { ur.location() } : (distinctAware ? new Object[] { ur.location(), expVal, ur.distinct() } : new Object[] { ur.location(), expVal }); // has multiple arguments
if (!noExpression) {
args.add(expVal);
if (distinctAware) {
args.add(ur.distinct());
}
if (timezoneAware) {
args.add(settings.timeZone());
}
} }
return (Function) info.ctr.newInstance(args); return (Function) info.ctr.newInstance(args);
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {

View File

@ -5,11 +5,13 @@
*/ */
package org.elasticsearch.xpack.sql.expression.function; package org.elasticsearch.xpack.sql.expression.function;
import org.elasticsearch.xpack.sql.session.SqlSettings;
import java.util.Collection; import java.util.Collection;
public interface FunctionRegistry { public interface FunctionRegistry {
Function resolveFunction(UnresolvedFunction ur); Function resolveFunction(UnresolvedFunction ur, SqlSettings settings);
boolean functionExists(String name); boolean functionExists(String name);

View File

@ -13,19 +13,16 @@ import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.type.DataType; import org.elasticsearch.xpack.sql.type.DataType;
import java.util.List; import java.util.List;
import java.util.TimeZone;
public class UnresolvedFunction extends Function implements Unresolvable { public class UnresolvedFunction extends Function implements Unresolvable {
private final String name; private final String name;
private final boolean distinct; private final boolean distinct;
private final TimeZone timeZone;
public UnresolvedFunction(Location location, String name, boolean distinct, TimeZone timeZone, List<Expression> children) { public UnresolvedFunction(Location location, String name, boolean distinct, List<Expression> children) {
super(location, children); super(location, children);
this.name = name; this.name = name;
this.distinct = distinct; this.distinct = distinct;
this.timeZone = timeZone;
} }
@Override @Override
@ -47,10 +44,6 @@ public class UnresolvedFunction extends Function implements Unresolvable {
return distinct; return distinct;
} }
public TimeZone timeZone() {
return timeZone;
}
@Override @Override
public DataType dataType() { public DataType dataType() {
throw new UnresolvedException("dataType", this); throw new UnresolvedException("dataType", this);

View File

@ -7,11 +7,12 @@ package org.elasticsearch.xpack.sql.expression.function.aggregate;
import org.elasticsearch.xpack.sql.expression.Expression; import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.NamedExpression; import org.elasticsearch.xpack.sql.expression.NamedExpression;
import org.elasticsearch.xpack.sql.expression.function.aware.DistinctAware;
import org.elasticsearch.xpack.sql.tree.Location; import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.type.DataType; import org.elasticsearch.xpack.sql.type.DataType;
import org.elasticsearch.xpack.sql.type.DataTypes; import org.elasticsearch.xpack.sql.type.DataTypes;
public class Count extends AggregateFunction { public class Count extends AggregateFunction implements DistinctAware {
private final boolean distinct; private final boolean distinct;

View File

@ -0,0 +1,10 @@
/*
* 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.aware;
public interface DistinctAware {
}

View File

@ -0,0 +1,10 @@
/*
* 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.aware;
public interface TimeZoneAware {
}

View File

@ -9,6 +9,7 @@ import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.Expressions; import org.elasticsearch.xpack.sql.expression.Expressions;
import org.elasticsearch.xpack.sql.expression.FieldAttribute; import org.elasticsearch.xpack.sql.expression.FieldAttribute;
import org.elasticsearch.xpack.sql.expression.function.aggregate.AggregateFunctionAttribute; import org.elasticsearch.xpack.sql.expression.function.aggregate.AggregateFunctionAttribute;
import org.elasticsearch.xpack.sql.expression.function.aware.TimeZoneAware;
import org.elasticsearch.xpack.sql.expression.function.scalar.ColumnProcessor; import org.elasticsearch.xpack.sql.expression.function.scalar.ColumnProcessor;
import org.elasticsearch.xpack.sql.expression.function.scalar.ScalarFunction; import org.elasticsearch.xpack.sql.expression.function.scalar.ScalarFunction;
import org.elasticsearch.xpack.sql.expression.function.scalar.script.ScriptTemplate; import org.elasticsearch.xpack.sql.expression.function.scalar.script.ScriptTemplate;
@ -20,20 +21,25 @@ import org.joda.time.DateTimeZone;
import org.joda.time.ReadableDateTime; import org.joda.time.ReadableDateTime;
import java.time.temporal.ChronoField; import java.time.temporal.ChronoField;
import java.util.TimeZone; import java.util.Locale;
import static java.lang.String.format;
import static org.elasticsearch.xpack.sql.expression.function.scalar.script.ParamsBuilder.paramsBuilder; import static org.elasticsearch.xpack.sql.expression.function.scalar.script.ParamsBuilder.paramsBuilder;
import static org.elasticsearch.xpack.sql.expression.function.scalar.script.ScriptTemplate.formatTemplate; import static org.elasticsearch.xpack.sql.expression.function.scalar.script.ScriptTemplate.formatTemplate;
public abstract class DateTimeFunction extends ScalarFunction { public abstract class DateTimeFunction extends ScalarFunction implements TimeZoneAware {
private final TimeZone timeZone;
// NOCOMMIT I feel like our lives could be made a lot simpler with composition instead of inheritance here private final DateTimeZone timeZone;
public DateTimeFunction(Location location, Expression argument, TimeZone timeZone) {
public DateTimeFunction(Location location, Expression argument, DateTimeZone timeZone) {
super(location, argument); super(location, argument);
this.timeZone = timeZone; this.timeZone = timeZone;
} }
public DateTimeZone timeZone() {
return timeZone;
}
@Override @Override
protected TypeResolution resolveType() { protected TypeResolution resolveType() {
return argument().dataType().same(DataTypes.DATE) ? return argument().dataType().same(DataTypes.DATE) ?
@ -62,16 +68,16 @@ public abstract class DateTimeFunction extends ScalarFunction {
} }
private String createTemplate() { private String createTemplate() {
if (timeZone.getID().equals("UTC")) { if (DateTimeZone.UTC.equals(timeZone)) {
return formatTemplate("doc[{}].value.get" + extractFunction() + "()"); return formatTemplate("doc[{}].value.get" + extractFunction() + "()");
} else { } else {
// NOCOMMIT ewwww // NOCOMMIT ewwww
/* This uses the Java 9 time API because Painless doesn't whitelist creation of new /* This uses the Java 8 time API because Painless doesn't whitelist creation of new
* Joda classes. */ * Joda classes. */
// ideally JodaTime should be used since that's internally used and there are subtle differences between that and the JDK API
String asInstant = formatTemplate("Instant.ofEpochMilli(doc[{}].value.millis)"); String asInstant = formatTemplate("Instant.ofEpochMilli(doc[{}].value.millis)");
String zoneId = "ZoneId.of(\"" + timeZone.toZoneId().getId() + "\""; return format(Locale.ROOT, "ZonedDateTime.ofInstant(%s, ZoneId.of(\"%s\")).get(ChronoField.%s)", asInstant, timeZone.getID(), chronoField().name());
String asZonedDateTime = "ZonedDateTime.ofInstant(" + asInstant + ", " + zoneId + "))";
return asZonedDateTime + ".get(ChronoField." + chronoField().name() + ")";
} }
} }
@ -87,14 +93,9 @@ public abstract class DateTimeFunction extends ScalarFunction {
if (l instanceof Long) { if (l instanceof Long) {
dt = new DateTime((Long) l, DateTimeZone.UTC); dt = new DateTime((Long) l, DateTimeZone.UTC);
} }
// but date histogram returns the keys already as DateTime on UTC
else { else {
dt = (ReadableDateTime) l; dt = (ReadableDateTime) l;
} }
if (false == timeZone.getID().equals("UTC")) {
// TODO probably faster to use `null` for UTC like core does
dt = dt.toDateTime().withZone(DateTimeZone.forTimeZone(timeZone));
}
return Integer.valueOf(extract(dt)); return Integer.valueOf(extract(dt));
}; };
} }
@ -104,10 +105,6 @@ public abstract class DateTimeFunction extends ScalarFunction {
return DataTypes.INTEGER; return DataTypes.INTEGER;
} }
public TimeZone timeZone() {
return timeZone;
}
protected abstract int extract(ReadableDateTime dt); protected abstract int extract(ReadableDateTime dt);
// used for aggregration (date histogram) // used for aggregration (date histogram)

View File

@ -7,14 +7,14 @@ package org.elasticsearch.xpack.sql.expression.function.scalar.datetime;
import org.elasticsearch.xpack.sql.expression.Expression; import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.tree.Location; import org.elasticsearch.xpack.sql.tree.Location;
import org.joda.time.DateTimeZone;
import org.joda.time.ReadableDateTime; import org.joda.time.ReadableDateTime;
import java.time.temporal.ChronoField; import java.time.temporal.ChronoField;
import java.util.TimeZone;
public class DayOfMonth extends DateTimeFunction { public class DayOfMonth extends DateTimeFunction {
public DayOfMonth(Location location, Expression argument, TimeZone timeZone) { public DayOfMonth(Location location, Expression argument, DateTimeZone timeZone) {
super(location, argument, timeZone); super(location, argument, timeZone);
} }

View File

@ -7,14 +7,14 @@ package org.elasticsearch.xpack.sql.expression.function.scalar.datetime;
import org.elasticsearch.xpack.sql.expression.Expression; import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.tree.Location; import org.elasticsearch.xpack.sql.tree.Location;
import org.joda.time.DateTimeZone;
import org.joda.time.ReadableDateTime; import org.joda.time.ReadableDateTime;
import java.time.temporal.ChronoField; import java.time.temporal.ChronoField;
import java.util.TimeZone;
public class DayOfWeek extends DateTimeFunction { public class DayOfWeek extends DateTimeFunction {
public DayOfWeek(Location location, Expression argument, TimeZone timeZone) { public DayOfWeek(Location location, Expression argument, DateTimeZone timeZone) {
super(location, argument, timeZone); super(location, argument, timeZone);
} }

View File

@ -7,14 +7,14 @@ package org.elasticsearch.xpack.sql.expression.function.scalar.datetime;
import org.elasticsearch.xpack.sql.expression.Expression; import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.tree.Location; import org.elasticsearch.xpack.sql.tree.Location;
import org.joda.time.DateTimeZone;
import org.joda.time.ReadableDateTime; import org.joda.time.ReadableDateTime;
import java.time.temporal.ChronoField; import java.time.temporal.ChronoField;
import java.util.TimeZone;
public class DayOfYear extends DateTimeFunction { public class DayOfYear extends DateTimeFunction {
public DayOfYear(Location location, Expression argument, TimeZone timeZone) { public DayOfYear(Location location, Expression argument, DateTimeZone timeZone) {
super(location, argument, timeZone); super(location, argument, timeZone);
} }

View File

@ -6,108 +6,112 @@
package org.elasticsearch.xpack.sql.expression.function.scalar.datetime; package org.elasticsearch.xpack.sql.expression.function.scalar.datetime;
import org.elasticsearch.xpack.sql.expression.Expression; import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.session.SqlSession;
import org.elasticsearch.xpack.sql.tree.Location; import org.elasticsearch.xpack.sql.tree.Location;
import org.joda.time.DateTimeZone;
import java.util.TimeZone;
public enum Extract { public enum Extract {
YEAR { YEAR {
@Override @Override
public DateTimeFunction toFunction(Location source, Expression argument, TimeZone timeZone) { public DateTimeFunction toFunction(Location source, Expression argument, DateTimeZone timeZone) {
return new Year(source, argument, timeZone); return new Year(source, argument, timeZone);
} }
}, },
MONTH { MONTH {
@Override @Override
public DateTimeFunction toFunction(Location source, Expression argument, TimeZone timeZone) { public DateTimeFunction toFunction(Location source, Expression argument, DateTimeZone timeZone) {
return new MonthOfYear(source, argument, timeZone); return new MonthOfYear(source, argument, timeZone);
} }
}, },
WEEK { WEEK {
@Override @Override
public DateTimeFunction toFunction(Location source, Expression argument, TimeZone timeZone) { public DateTimeFunction toFunction(Location source, Expression argument, DateTimeZone timeZone) {
return new WeekOfWeekYear(source, argument, timeZone); return new WeekOfWeekYear(source, argument, timeZone);
} }
}, },
DAY { DAY {
@Override @Override
public DateTimeFunction toFunction(Location source, Expression argument, TimeZone timeZone) { public DateTimeFunction toFunction(Location source, Expression argument, DateTimeZone timeZone) {
return new DayOfMonth(source, argument, timeZone); return new DayOfMonth(source, argument, timeZone);
} }
}, },
DAY_OF_MONTH { DAY_OF_MONTH {
@Override @Override
public DateTimeFunction toFunction(Location source, Expression argument, TimeZone timeZone) { public DateTimeFunction toFunction(Location source, Expression argument, DateTimeZone timeZone) {
return DAY.toFunction(source, argument, timeZone); return DAY.toFunction(source, argument, timeZone);
} }
}, },
DOM { DOM {
@Override @Override
public DateTimeFunction toFunction(Location source, Expression argument, TimeZone timeZone) { public DateTimeFunction toFunction(Location source, Expression argument, DateTimeZone timeZone) {
return DAY.toFunction(source, argument, timeZone); return DAY.toFunction(source, argument, timeZone);
} }
}, },
DAY_OF_WEEK { DAY_OF_WEEK {
@Override @Override
public DateTimeFunction toFunction(Location source, Expression argument, TimeZone timeZone) { public DateTimeFunction toFunction(Location source, Expression argument, DateTimeZone timeZone) {
return new DayOfWeek(source, argument, timeZone); return new DayOfWeek(source, argument, timeZone);
} }
}, },
DOW { DOW {
@Override @Override
public DateTimeFunction toFunction(Location source, Expression argument, TimeZone timeZone) { public DateTimeFunction toFunction(Location source, Expression argument, DateTimeZone timeZone) {
return DAY_OF_WEEK.toFunction(source, argument, timeZone); return DAY_OF_WEEK.toFunction(source, argument, timeZone);
} }
}, },
DAY_OF_YEAR { DAY_OF_YEAR {
@Override @Override
public DateTimeFunction toFunction(Location source, Expression argument, TimeZone timeZone) { public DateTimeFunction toFunction(Location source, Expression argument, DateTimeZone timeZone) {
return new DayOfYear(source, argument, timeZone); return new DayOfYear(source, argument, timeZone);
} }
}, },
DOY { DOY {
@Override @Override
public DateTimeFunction toFunction(Location source, Expression argument, TimeZone timeZone) { public DateTimeFunction toFunction(Location source, Expression argument, DateTimeZone timeZone) {
return DAY_OF_YEAR.toFunction(source, argument, timeZone); return DAY_OF_YEAR.toFunction(source, argument, timeZone);
} }
}, },
HOUR { HOUR {
@Override @Override
public DateTimeFunction toFunction(Location source, Expression argument, TimeZone timeZone) { public DateTimeFunction toFunction(Location source, Expression argument, DateTimeZone timeZone) {
return new HourOfDay(source, argument, timeZone); return new HourOfDay(source, argument, timeZone);
} }
}, },
MINUTE { MINUTE {
@Override @Override
public DateTimeFunction toFunction(Location source, Expression argument, TimeZone timeZone) { public DateTimeFunction toFunction(Location source, Expression argument, DateTimeZone timeZone) {
return new MinuteOfHour(source, argument, timeZone); return new MinuteOfHour(source, argument, timeZone);
} }
}, },
MINUTE_OF_HOUR { MINUTE_OF_HOUR {
@Override @Override
public DateTimeFunction toFunction(Location source, Expression argument, TimeZone timeZone) { public DateTimeFunction toFunction(Location source, Expression argument, DateTimeZone timeZone) {
return MINUTE.toFunction(source, argument, timeZone); return MINUTE.toFunction(source, argument, timeZone);
} }
}, },
MINUTE_OF_DAY { MINUTE_OF_DAY {
@Override @Override
public DateTimeFunction toFunction(Location source, Expression argument, TimeZone timeZone) { public DateTimeFunction toFunction(Location source, Expression argument, DateTimeZone timeZone) {
return new MinuteOfDay(source, argument, timeZone); return new MinuteOfDay(source, argument, timeZone);
} }
}, },
SECOND { SECOND {
@Override @Override
public DateTimeFunction toFunction(Location source, Expression argument, TimeZone timeZone) { public DateTimeFunction toFunction(Location source, Expression argument, DateTimeZone timeZone) {
return new SecondOfMinute(source, argument, timeZone); return new SecondOfMinute(source, argument, timeZone);
} }
}, },
SECOND_OF_MINUTE { SECOND_OF_MINUTE {
@Override @Override
public DateTimeFunction toFunction(Location source, Expression argument, TimeZone timeZone) { public DateTimeFunction toFunction(Location source, Expression argument, DateTimeZone timeZone) {
return SECOND.toFunction(source, argument, timeZone); return SECOND.toFunction(source, argument, timeZone);
} }
}; };
public abstract DateTimeFunction toFunction(Location source, Expression argument, TimeZone timeZone); public DateTimeFunction toFunction(Location source, Expression argument) {
return toFunction(source, argument, SqlSession.CURRENT.get().timeZone());
}
public abstract DateTimeFunction toFunction(Location source, Expression argument, DateTimeZone timeZone);
} }

View File

@ -7,14 +7,14 @@ package org.elasticsearch.xpack.sql.expression.function.scalar.datetime;
import org.elasticsearch.xpack.sql.expression.Expression; import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.tree.Location; import org.elasticsearch.xpack.sql.tree.Location;
import org.joda.time.DateTimeZone;
import org.joda.time.ReadableDateTime; import org.joda.time.ReadableDateTime;
import java.time.temporal.ChronoField; import java.time.temporal.ChronoField;
import java.util.TimeZone;
public class HourOfDay extends DateTimeFunction { public class HourOfDay extends DateTimeFunction {
public HourOfDay(Location location, Expression argument, TimeZone timeZone) { public HourOfDay(Location location, Expression argument, DateTimeZone timeZone) {
super(location, argument, timeZone); super(location, argument, timeZone);
} }

View File

@ -7,14 +7,14 @@ package org.elasticsearch.xpack.sql.expression.function.scalar.datetime;
import org.elasticsearch.xpack.sql.expression.Expression; import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.tree.Location; import org.elasticsearch.xpack.sql.tree.Location;
import org.joda.time.DateTimeZone;
import org.joda.time.ReadableDateTime; import org.joda.time.ReadableDateTime;
import java.time.temporal.ChronoField; import java.time.temporal.ChronoField;
import java.util.TimeZone;
public class MinuteOfDay extends DateTimeFunction { public class MinuteOfDay extends DateTimeFunction {
public MinuteOfDay(Location location, Expression argument, TimeZone timeZone) { public MinuteOfDay(Location location, Expression argument, DateTimeZone timeZone) {
super(location, argument, timeZone); super(location, argument, timeZone);
} }

View File

@ -7,14 +7,14 @@ package org.elasticsearch.xpack.sql.expression.function.scalar.datetime;
import org.elasticsearch.xpack.sql.expression.Expression; import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.tree.Location; import org.elasticsearch.xpack.sql.tree.Location;
import org.joda.time.DateTimeZone;
import org.joda.time.ReadableDateTime; import org.joda.time.ReadableDateTime;
import java.time.temporal.ChronoField; import java.time.temporal.ChronoField;
import java.util.TimeZone;
public class MinuteOfHour extends DateTimeFunction { public class MinuteOfHour extends DateTimeFunction {
public MinuteOfHour(Location location, Expression argument, TimeZone timeZone) { public MinuteOfHour(Location location, Expression argument, DateTimeZone timeZone) {
super(location, argument, timeZone); super(location, argument, timeZone);
} }

View File

@ -7,14 +7,14 @@ package org.elasticsearch.xpack.sql.expression.function.scalar.datetime;
import org.elasticsearch.xpack.sql.expression.Expression; import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.tree.Location; import org.elasticsearch.xpack.sql.tree.Location;
import org.joda.time.DateTimeZone;
import org.joda.time.ReadableDateTime; import org.joda.time.ReadableDateTime;
import java.time.temporal.ChronoField; import java.time.temporal.ChronoField;
import java.util.TimeZone;
public class MonthOfYear extends DateTimeFunction { public class MonthOfYear extends DateTimeFunction {
public MonthOfYear(Location location, Expression argument, TimeZone timeZone) { public MonthOfYear(Location location, Expression argument, DateTimeZone timeZone) {
super(location, argument, timeZone); super(location, argument, timeZone);
} }

View File

@ -7,14 +7,14 @@ package org.elasticsearch.xpack.sql.expression.function.scalar.datetime;
import org.elasticsearch.xpack.sql.expression.Expression; import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.tree.Location; import org.elasticsearch.xpack.sql.tree.Location;
import org.joda.time.DateTimeZone;
import org.joda.time.ReadableDateTime; import org.joda.time.ReadableDateTime;
import java.time.temporal.ChronoField; import java.time.temporal.ChronoField;
import java.util.TimeZone;
public class SecondOfMinute extends DateTimeFunction { public class SecondOfMinute extends DateTimeFunction {
public SecondOfMinute(Location location, Expression argument, TimeZone timeZone) { public SecondOfMinute(Location location, Expression argument, DateTimeZone timeZone) {
super(location, argument, timeZone); super(location, argument, timeZone);
} }

View File

@ -7,14 +7,14 @@ package org.elasticsearch.xpack.sql.expression.function.scalar.datetime;
import org.elasticsearch.xpack.sql.expression.Expression; import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.tree.Location; import org.elasticsearch.xpack.sql.tree.Location;
import org.joda.time.DateTimeZone;
import org.joda.time.ReadableDateTime; import org.joda.time.ReadableDateTime;
import java.time.temporal.ChronoField; import java.time.temporal.ChronoField;
import java.util.TimeZone;
public class WeekOfWeekYear extends DateTimeFunction { public class WeekOfWeekYear extends DateTimeFunction {
public WeekOfWeekYear(Location location, Expression argument, TimeZone timeZone) { public WeekOfWeekYear(Location location, Expression argument, DateTimeZone timeZone) {
super(location, argument, timeZone); super(location, argument, timeZone);
} }

View File

@ -7,14 +7,14 @@ package org.elasticsearch.xpack.sql.expression.function.scalar.datetime;
import org.elasticsearch.xpack.sql.expression.Expression; import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.tree.Location; import org.elasticsearch.xpack.sql.tree.Location;
import org.joda.time.DateTimeZone;
import org.joda.time.ReadableDateTime; import org.joda.time.ReadableDateTime;
import java.time.temporal.ChronoField; import java.time.temporal.ChronoField;
import java.util.TimeZone;
public class Year extends DateTimeFunction { public class Year extends DateTimeFunction {
public Year(Location location, Expression argument, TimeZone timeZone) { public Year(Location location, Expression argument, DateTimeZone timeZone) {
super(location, argument, timeZone); super(location, argument, timeZone);
} }

View File

@ -8,12 +8,7 @@ package org.elasticsearch.xpack.sql.parser;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.SingleStatementContext; import org.elasticsearch.xpack.sql.parser.SqlBaseParser.SingleStatementContext;
import org.elasticsearch.xpack.sql.plan.logical.LogicalPlan; import org.elasticsearch.xpack.sql.plan.logical.LogicalPlan;
import java.util.TimeZone;
class AstBuilder extends CommandBuilder { class AstBuilder extends CommandBuilder {
AstBuilder(TimeZone timeZone) {
super(timeZone);
}
@Override @Override
public LogicalPlan visitSingleStatement(SingleStatementContext ctx) { public LogicalPlan visitSingleStatement(SingleStatementContext ctx) {

View File

@ -5,9 +5,6 @@
*/ */
package org.elasticsearch.xpack.sql.parser; package org.elasticsearch.xpack.sql.parser;
import java.util.Locale;
import java.util.TimeZone;
import org.elasticsearch.common.Booleans; import org.elasticsearch.common.Booleans;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.DebugContext; import org.elasticsearch.xpack.sql.parser.SqlBaseParser.DebugContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.ExplainContext; import org.elasticsearch.xpack.sql.parser.SqlBaseParser.ExplainContext;
@ -31,10 +28,9 @@ import org.elasticsearch.xpack.sql.plan.logical.command.ShowSession;
import org.elasticsearch.xpack.sql.plan.logical.command.ShowTables; import org.elasticsearch.xpack.sql.plan.logical.command.ShowTables;
import org.elasticsearch.xpack.sql.tree.Location; import org.elasticsearch.xpack.sql.tree.Location;
import java.util.Locale;
abstract class CommandBuilder extends LogicalPlanBuilder { abstract class CommandBuilder extends LogicalPlanBuilder {
CommandBuilder(TimeZone timeZone) {
super(timeZone);
}
@Override @Override
public Command visitDebug(DebugContext ctx) { public Command visitDebug(DebugContext ctx) {

View File

@ -71,29 +71,11 @@ import org.elasticsearch.xpack.sql.type.DataTypes;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.TimeZone;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static java.lang.String.format; import static java.lang.String.format;
abstract class ExpressionBuilder extends IdentifierBuilder { abstract class ExpressionBuilder extends IdentifierBuilder {
/**
* Time zone in which to execute the query. Used by date time
* functions and the rounding in date histograms.
*/
private final TimeZone timeZone;
ExpressionBuilder(TimeZone timeZone) {
this.timeZone = timeZone;
}
/**
* Time zone in which to execute the query. Used by date time
* functions and the rounding in date histograms.
*/
protected TimeZone timeZone() {
return timeZone;
}
protected Expression expression(ParseTree ctx) { protected Expression expression(ParseTree ctx) {
return typedParsing(ctx, Expression.class); return typedParsing(ctx, Expression.class);
@ -304,7 +286,7 @@ abstract class ExpressionBuilder extends IdentifierBuilder {
if (ctx.setQuantifier() != null) { if (ctx.setQuantifier() != null) {
isDistinct = (ctx.setQuantifier().DISTINCT() != null); isDistinct = (ctx.setQuantifier().DISTINCT() != null);
} }
return new UnresolvedFunction(source(ctx), name, isDistinct, timeZone, expressions(ctx.expression())); return new UnresolvedFunction(source(ctx), name, isDistinct, expressions(ctx.expression()));
} }
@Override @Override
@ -317,7 +299,7 @@ abstract class ExpressionBuilder extends IdentifierBuilder {
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
throw new ParsingException(source, format(Locale.ROOT, "Invalid EXTRACT field %s", fieldString)); throw new ParsingException(source, format(Locale.ROOT, "Invalid EXTRACT field %s", fieldString));
} }
return extract.toFunction(source, expression(ctx.valueExpression()), timeZone); return extract.toFunction(source, expression(ctx.valueExpression()));
} }
@Override @Override

View File

@ -5,11 +5,11 @@
*/ */
package org.elasticsearch.xpack.sql.parser; package org.elasticsearch.xpack.sql.parser;
import java.util.LinkedHashMap; import org.elasticsearch.xpack.sql.expression.Expression;
import java.util.List; import org.elasticsearch.xpack.sql.expression.Literal;
import java.util.Map; import org.elasticsearch.xpack.sql.expression.NamedExpression;
import java.util.TimeZone; import org.elasticsearch.xpack.sql.expression.Order;
import org.elasticsearch.xpack.sql.expression.UnresolvedAlias;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.AliasedQueryContext; import org.elasticsearch.xpack.sql.parser.SqlBaseParser.AliasedQueryContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.AliasedRelationContext; import org.elasticsearch.xpack.sql.parser.SqlBaseParser.AliasedRelationContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.FromClauseContext; import org.elasticsearch.xpack.sql.parser.SqlBaseParser.FromClauseContext;
@ -23,17 +23,13 @@ import org.elasticsearch.xpack.sql.parser.SqlBaseParser.QuerySpecificationContex
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.RelationContext; import org.elasticsearch.xpack.sql.parser.SqlBaseParser.RelationContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.SubqueryContext; import org.elasticsearch.xpack.sql.parser.SqlBaseParser.SubqueryContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.TableNameContext; import org.elasticsearch.xpack.sql.parser.SqlBaseParser.TableNameContext;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.Literal;
import org.elasticsearch.xpack.sql.expression.NamedExpression;
import org.elasticsearch.xpack.sql.expression.Order;
import org.elasticsearch.xpack.sql.expression.UnresolvedAlias;
import org.elasticsearch.xpack.sql.plan.TableIdentifier; import org.elasticsearch.xpack.sql.plan.TableIdentifier;
import org.elasticsearch.xpack.sql.plan.logical.Aggregate; import org.elasticsearch.xpack.sql.plan.logical.Aggregate;
import org.elasticsearch.xpack.sql.plan.logical.Distinct; import org.elasticsearch.xpack.sql.plan.logical.Distinct;
import org.elasticsearch.xpack.sql.plan.logical.Filter; import org.elasticsearch.xpack.sql.plan.logical.Filter;
import org.elasticsearch.xpack.sql.plan.logical.FromlessSelect; import org.elasticsearch.xpack.sql.plan.logical.FromlessSelect;
import org.elasticsearch.xpack.sql.plan.logical.Join; import org.elasticsearch.xpack.sql.plan.logical.Join;
import org.elasticsearch.xpack.sql.plan.logical.Join.JoinType;
import org.elasticsearch.xpack.sql.plan.logical.Limit; import org.elasticsearch.xpack.sql.plan.logical.Limit;
import org.elasticsearch.xpack.sql.plan.logical.LogicalPlan; import org.elasticsearch.xpack.sql.plan.logical.LogicalPlan;
import org.elasticsearch.xpack.sql.plan.logical.OrderBy; import org.elasticsearch.xpack.sql.plan.logical.OrderBy;
@ -41,16 +37,16 @@ import org.elasticsearch.xpack.sql.plan.logical.Project;
import org.elasticsearch.xpack.sql.plan.logical.SubQueryAlias; import org.elasticsearch.xpack.sql.plan.logical.SubQueryAlias;
import org.elasticsearch.xpack.sql.plan.logical.UnresolvedRelation; import org.elasticsearch.xpack.sql.plan.logical.UnresolvedRelation;
import org.elasticsearch.xpack.sql.plan.logical.With; import org.elasticsearch.xpack.sql.plan.logical.With;
import org.elasticsearch.xpack.sql.plan.logical.Join.JoinType;
import org.elasticsearch.xpack.sql.type.DataTypes; import org.elasticsearch.xpack.sql.type.DataTypes;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toList;
abstract class LogicalPlanBuilder extends ExpressionBuilder { abstract class LogicalPlanBuilder extends ExpressionBuilder {
LogicalPlanBuilder(TimeZone timeZone) {
super(timeZone);
}
@Override @Override
public LogicalPlan visitQuery(QueryContext ctx) { public LogicalPlan visitQuery(QueryContext ctx) {

View File

@ -19,30 +19,29 @@ import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.xpack.sql.expression.Expression; import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.plan.logical.LogicalPlan; import org.elasticsearch.xpack.sql.plan.logical.LogicalPlan;
import java.util.TimeZone;
import java.util.function.Function; import java.util.function.Function;
public class SqlParser { public class SqlParser {
private static final Logger log = Loggers.getLogger(SqlParser.class); private static final Logger log = Loggers.getLogger(SqlParser.class);
public LogicalPlan createStatement(String sql, TimeZone timeZone) { public LogicalPlan createStatement(String sql) {
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
log.debug("Parsing as statement: {}", sql); log.debug("Parsing as statement: {}", sql);
} }
return invokeParser("statement", sql, timeZone, SqlBaseParser::singleStatement); return invokeParser("statement", sql, SqlBaseParser::singleStatement);
} }
public Expression createExpression(String expression, TimeZone timeZone) { public Expression createExpression(String expression) {
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
log.debug("Parsing as expression: {}", expression); log.debug("Parsing as expression: {}", expression);
} }
return invokeParser("expression", expression, timeZone, SqlBaseParser::singleExpression); return invokeParser("expression", expression, SqlBaseParser::singleExpression);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private <T> T invokeParser(String name, String sql, TimeZone timeZone, Function<SqlBaseParser, ParserRuleContext> parseFunction) { private <T> T invokeParser(String name, String sql, Function<SqlBaseParser, ParserRuleContext> parseFunction) {
try { try {
SqlBaseLexer lexer = new SqlBaseLexer(new CaseInsensitiveStream(sql)); SqlBaseLexer lexer = new SqlBaseLexer(new CaseInsensitiveStream(sql));
@ -73,7 +72,7 @@ public class SqlParser {
postProcess(lexer, parser, tree); postProcess(lexer, parser, tree);
return (T) new AstBuilder(timeZone).visit(tree); return (T) new AstBuilder().visit(tree);
} }
catch (StackOverflowError e) { catch (StackOverflowError e) {

View File

@ -23,7 +23,6 @@ import org.elasticsearch.xpack.sql.protocol.shared.Request;
import org.elasticsearch.xpack.sql.protocol.shared.Response; import org.elasticsearch.xpack.sql.protocol.shared.Response;
import org.elasticsearch.xpack.sql.util.StringUtils; import org.elasticsearch.xpack.sql.util.StringUtils;
import java.util.TimeZone;
import java.util.function.Supplier; import java.util.function.Supplier;
import static org.elasticsearch.action.ActionListener.wrap; import static org.elasticsearch.action.ActionListener.wrap;
@ -78,8 +77,9 @@ public class CliServer extends AbstractSqlServer {
public void command(CommandRequest req, ActionListener<Response> listener) { public void command(CommandRequest req, ActionListener<Response> listener) {
final long start = System.currentTimeMillis(); // NOCOMMIT should be nanoTime or else clock skew will skew us final long start = System.currentTimeMillis(); // NOCOMMIT should be nanoTime or else clock skew will skew us
// NOCOMMIT: need to add settings for CLI
// TODO support non-utc for cli server // TODO support non-utc for cli server
executor.sql(req.command, TimeZone.getTimeZone("UTC"), wrap( executor.sql(req.command, wrap(
c -> { c -> {
long stop = System.currentTimeMillis(); long stop = System.currentTimeMillis();
String requestId = EMPTY; String requestId = EMPTY;

View File

@ -9,6 +9,7 @@ import org.elasticsearch.Build;
import org.elasticsearch.Version; import org.elasticsearch.Version;
import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionListener;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.xpack.sql.analysis.catalog.EsType; import org.elasticsearch.xpack.sql.analysis.catalog.EsType;
import org.elasticsearch.xpack.sql.execution.PlanExecutor; import org.elasticsearch.xpack.sql.execution.PlanExecutor;
import org.elasticsearch.xpack.sql.execution.search.SearchHitRowSetCursor; import org.elasticsearch.xpack.sql.execution.search.SearchHitRowSetCursor;
@ -30,6 +31,7 @@ import org.elasticsearch.xpack.sql.plugin.AbstractSqlServer;
import org.elasticsearch.xpack.sql.protocol.shared.AbstractProto.SqlExceptionType; import org.elasticsearch.xpack.sql.protocol.shared.AbstractProto.SqlExceptionType;
import org.elasticsearch.xpack.sql.protocol.shared.Request; import org.elasticsearch.xpack.sql.protocol.shared.Request;
import org.elasticsearch.xpack.sql.protocol.shared.Response; import org.elasticsearch.xpack.sql.protocol.shared.Response;
import org.elasticsearch.xpack.sql.session.SqlSettings;
import org.elasticsearch.xpack.sql.type.DataType; import org.elasticsearch.xpack.sql.type.DataType;
import org.elasticsearch.xpack.sql.util.StringUtils; import org.elasticsearch.xpack.sql.util.StringUtils;
@ -141,7 +143,13 @@ public class JdbcServer extends AbstractSqlServer {
public void queryInit(QueryInitRequest req, ActionListener<Response> listener) { public void queryInit(QueryInitRequest req, ActionListener<Response> listener) {
final long start = System.currentTimeMillis(); final long start = System.currentTimeMillis();
executor.sql(req.query, req.timeZone, wrap(c -> { SqlSettings sqlCfg = new SqlSettings(Settings.builder()
.put(SqlSettings.PAGE_SIZE, req.fetchSize)
.put(SqlSettings.TIMEZONE_ID, req.timeZone.getID())
.build()
);
executor.sql(sqlCfg, req.query, wrap(c -> {
long stop = System.currentTimeMillis(); long stop = System.currentTimeMillis();
String requestId = EMPTY; String requestId = EMPTY;
if (c.hasNextSet() && c instanceof SearchHitRowSetCursor) { if (c.hasNextSet() && c instanceof SearchHitRowSetCursor) {

View File

@ -36,8 +36,7 @@ public class TransportJdbcAction extends HandledTransportAction<JdbcRequest, Jdb
// lazy init of the resolver // lazy init of the resolver
((EsCatalog) planExecutor.catalog()).setIndexNameExpressionResolver(indexNameExpressionResolver); ((EsCatalog) planExecutor.catalog()).setIndexNameExpressionResolver(indexNameExpressionResolver);
// NOCOMMIT indexNameExpressionResolver should be available some other way // NOCOMMIT indexNameExpressionResolver should be available some other way
this.jdbcServer = new JdbcServer(planExecutor, clusterService.getClusterName().value(), () -> clusterService.localNode().getName(), this.jdbcServer = new JdbcServer(planExecutor, clusterService.getClusterName().value(), () -> clusterService.localNode().getName(), Version.CURRENT, Build.CURRENT);
Version.CURRENT, Build.CURRENT);
} }
@Override @Override

View File

@ -5,16 +5,16 @@
*/ */
package org.elasticsearch.xpack.sql.plugin.sql.action; package org.elasticsearch.xpack.sql.plugin.sql.action;
import java.io.IOException;
import java.util.Objects;
import java.util.TimeZone;
import org.elasticsearch.action.ActionRequest; import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.action.CompositeIndicesRequest; import org.elasticsearch.action.CompositeIndicesRequest;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.StreamOutput;
import org.joda.time.DateTimeZone;
import java.io.IOException;
import java.util.Objects;
import static org.elasticsearch.action.ValidateActions.addValidationError; import static org.elasticsearch.action.ValidateActions.addValidationError;
@ -22,13 +22,13 @@ public class SqlRequest extends ActionRequest implements CompositeIndicesRequest
// initialized on the first request // initialized on the first request
private String query; private String query;
private TimeZone timeZone; private DateTimeZone timeZone;
// initialized after the plan has been translated // initialized after the plan has been translated
private String sessionId; private String sessionId;
public SqlRequest() {} public SqlRequest() {}
public SqlRequest(String query, TimeZone timeZone, String sessionId) { public SqlRequest(String query, DateTimeZone timeZone, String sessionId) {
this.query = query; this.query = query;
this.timeZone = timeZone; this.timeZone = timeZone;
this.sessionId = sessionId; this.sessionId = sessionId;
@ -51,7 +51,7 @@ public class SqlRequest extends ActionRequest implements CompositeIndicesRequest
return sessionId; return sessionId;
} }
public TimeZone timeZone() { public DateTimeZone timeZone() {
return timeZone; return timeZone;
} }
@ -69,7 +69,7 @@ public class SqlRequest extends ActionRequest implements CompositeIndicesRequest
public void readFrom(StreamInput in) throws IOException { public void readFrom(StreamInput in) throws IOException {
super.readFrom(in); super.readFrom(in);
query = in.readString(); query = in.readString();
timeZone = TimeZone.getTimeZone(in.readString()); timeZone = DateTimeZone.forID(in.readString());
sessionId = in.readOptionalString(); sessionId = in.readOptionalString();
} }

View File

@ -7,8 +7,7 @@ package org.elasticsearch.xpack.sql.plugin.sql.action;
import org.elasticsearch.action.ActionRequestBuilder; import org.elasticsearch.action.ActionRequestBuilder;
import org.elasticsearch.client.ElasticsearchClient; import org.elasticsearch.client.ElasticsearchClient;
import org.joda.time.DateTimeZone;
import java.util.TimeZone;
public class SqlRequestBuilder extends ActionRequestBuilder<SqlRequest, SqlResponse, SqlRequestBuilder> { public class SqlRequestBuilder extends ActionRequestBuilder<SqlRequest, SqlResponse, SqlRequestBuilder> {
@ -16,7 +15,7 @@ public class SqlRequestBuilder extends ActionRequestBuilder<SqlRequest, SqlRespo
this(client, action, null, null, null); this(client, action, null, null, null);
} }
public SqlRequestBuilder(ElasticsearchClient client, SqlAction action, String query, TimeZone timeZone, String sessionId) { public SqlRequestBuilder(ElasticsearchClient client, SqlAction action, String query, DateTimeZone timeZone, String sessionId) {
super(client, action, new SqlRequest(query, timeZone, sessionId)); super(client, action, new SqlRequest(query, timeZone, sessionId));
} }

View File

@ -23,8 +23,9 @@ import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
import org.elasticsearch.xpack.sql.analysis.catalog.EsCatalog; import org.elasticsearch.xpack.sql.analysis.catalog.EsCatalog;
import org.elasticsearch.xpack.sql.execution.PlanExecutor; import org.elasticsearch.xpack.sql.execution.PlanExecutor;
import org.elasticsearch.xpack.sql.session.RowSetCursor; import org.elasticsearch.xpack.sql.session.RowSetCursor;
import org.elasticsearch.xpack.sql.session.SqlSettings;
import org.joda.time.DateTimeZone;
import java.util.TimeZone;
import java.util.function.Supplier; import java.util.function.Supplier;
import static org.elasticsearch.xpack.sql.util.ActionUtils.chain; import static org.elasticsearch.xpack.sql.util.ActionUtils.chain;
@ -60,7 +61,13 @@ public class TransportSqlAction extends HandledTransportAction<SqlRequest, SqlRe
protected void doExecute(SqlRequest request, ActionListener<SqlResponse> listener) { protected void doExecute(SqlRequest request, ActionListener<SqlResponse> listener) {
String sessionId = request.sessionId(); String sessionId = request.sessionId();
String query = request.query(); String query = request.query();
TimeZone timeZone = request.timeZone(); DateTimeZone timeZone = request.timeZone();
SqlSettings sqlCfg = new SqlSettings(
Settings.builder()
// .put(SqlSettings.PAGE_SIZE, req.fetchSize)
.put(SqlSettings.TIMEZONE_ID, request.timeZone().getID())
.build());
try { try {
if (sessionId == null) { if (sessionId == null) {
@ -69,7 +76,7 @@ public class TransportSqlAction extends HandledTransportAction<SqlRequest, SqlRe
return; return;
} }
planExecutor.sql(query, timeZone, chain(listener, c -> { planExecutor.sql(query, chain(listener, c -> {
String id = generateId(); String id = generateId();
SESSIONS.put(id, c); SESSIONS.put(id, c);
return new SqlResponse(id, c); return new SqlResponse(id, c);

View File

@ -5,9 +5,6 @@
*/ */
package org.elasticsearch.xpack.sql.plugin.sql.rest; package org.elasticsearch.xpack.sql.plugin.sql.rest;
import java.io.IOException;
import java.util.TimeZone;
import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.ExceptionsHelper;
import org.elasticsearch.client.node.NodeClient; import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParseField;
@ -21,6 +18,9 @@ import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestRequest; import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.xpack.sql.plugin.sql.action.SqlAction; import org.elasticsearch.xpack.sql.plugin.sql.action.SqlAction;
import org.elasticsearch.xpack.sql.plugin.sql.action.SqlRequest; import org.elasticsearch.xpack.sql.plugin.sql.action.SqlRequest;
import org.joda.time.DateTimeZone;
import java.io.IOException;
import static org.elasticsearch.rest.RestRequest.Method.GET; import static org.elasticsearch.rest.RestRequest.Method.GET;
import static org.elasticsearch.rest.RestRequest.Method.POST; import static org.elasticsearch.rest.RestRequest.Method.POST;
@ -73,7 +73,7 @@ public class RestSqlAction extends BaseRestHandler {
} }
String query; String query;
TimeZone timeZone; DateTimeZone timeZone;
static Payload from(RestRequest request) throws IOException { static Payload from(RestRequest request) throws IOException {
Payload payload = new Payload(); Payload payload = new Payload();
@ -89,7 +89,7 @@ public class RestSqlAction extends BaseRestHandler {
} }
public void setTimeZone(String timeZone) { public void setTimeZone(String timeZone) {
this.timeZone = TimeZone.getTimeZone(timeZone); this.timeZone = DateTimeZone.forID(timeZone);
} }
} }
} }

View File

@ -16,7 +16,6 @@ import org.joda.time.DateTimeZone;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.TimeZone;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap; import static java.util.Collections.emptyMap;
@ -25,14 +24,14 @@ import static org.elasticsearch.search.aggregations.AggregationBuilders.dateHist
public class GroupByDateAgg extends GroupingAgg { public class GroupByDateAgg extends GroupingAgg {
private final String interval; private final String interval;
private final TimeZone timeZone; private final DateTimeZone timeZone;
public GroupByDateAgg(String id, String propertyPath, String fieldName, String interval, TimeZone timeZone) { public GroupByDateAgg(String id, String propertyPath, String fieldName, String interval, DateTimeZone timeZone) {
this(id, propertyPath, fieldName, interval, timeZone, emptyList(), emptyList(), emptyMap()); this(id, propertyPath, fieldName, interval, timeZone, emptyList(), emptyList(), emptyMap());
} }
public GroupByDateAgg(String id, String propertyPath, String fieldName, String interval, TimeZone timeZone, List<LeafAgg> subAggs, public GroupByDateAgg(String id, String propertyPath, String fieldName, String interval, DateTimeZone timeZone,
List<PipelineAgg> subPipelines, Map<String, Direction> order) { List<LeafAgg> subAggs, List<PipelineAgg> subPipelines, Map<String, Direction> order) {
super(id, propertyPath, fieldName, subAggs, subPipelines, order); super(id, propertyPath, fieldName, subAggs, subPipelines, order);
this.interval = interval; this.interval = interval;
this.timeZone = timeZone; this.timeZone = timeZone;
@ -46,7 +45,7 @@ public class GroupByDateAgg extends GroupingAgg {
protected AggregationBuilder toGroupingAgg() { protected AggregationBuilder toGroupingAgg() {
DateHistogramAggregationBuilder dhab = dateHistogram(id()) DateHistogramAggregationBuilder dhab = dateHistogram(id())
.field(fieldName()) .field(fieldName())
.timeZone(DateTimeZone.forTimeZone(timeZone)) .timeZone(timeZone)
.dateHistogramInterval(new DateHistogramInterval(interval)); .dateHistogramInterval(new DateHistogramInterval(interval));
if (!order().isEmpty()) { if (!order().isEmpty()) {
for (Entry<String, Sort.Direction> entry : order().entrySet()) { for (Entry<String, Sort.Direction> entry : order().entrySet()) {

View File

@ -18,7 +18,6 @@ import org.elasticsearch.xpack.sql.plan.logical.LogicalPlan;
import org.elasticsearch.xpack.sql.plan.physical.PhysicalPlan; import org.elasticsearch.xpack.sql.plan.physical.PhysicalPlan;
import org.elasticsearch.xpack.sql.planner.Planner; import org.elasticsearch.xpack.sql.planner.Planner;
import java.util.TimeZone;
import java.util.function.Function; import java.util.function.Function;
public class SqlSession { public class SqlSession {
@ -35,6 +34,15 @@ public class SqlSession {
private final SqlSettings defaults; private final SqlSettings defaults;
private SqlSettings settings; private SqlSettings settings;
// thread-local used for sharing settings across the plan compilation
public static final ThreadLocal<SqlSettings> CURRENT = new ThreadLocal<SqlSettings>() {
@Override
public String toString() {
return "SQL Session";
}
};
public SqlSession(SqlSession other) { public SqlSession(SqlSession other) {
this(other.defaults(), other.client(), other.parser, other.catalog(), other.functionRegistry(), other.analyzer(), other.optimizer(), other.planner()); this(other.defaults(), other.client(), other.parser, other.catalog(), other.functionRegistry(), other.analyzer(), other.optimizer(), other.planner());
} }
@ -79,12 +87,12 @@ public class SqlSession {
return optimizer; return optimizer;
} }
public LogicalPlan parse(String sql, TimeZone timeZone) { public LogicalPlan parse(String sql) {
return parser.createStatement(sql, timeZone); return parser.createStatement(sql);
} }
public Expression expression(String expression, TimeZone timeZone) { public Expression expression(String expression) {
return parser.createExpression(expression, timeZone); return parser.createExpression(expression);
} }
public LogicalPlan analyzedPlan(LogicalPlan plan, boolean verify) { public LogicalPlan analyzedPlan(LogicalPlan plan, boolean verify) {
@ -99,8 +107,17 @@ public class SqlSession {
return planner.plan(optimizedPlan(optimized), verify); return planner.plan(optimizedPlan(optimized), verify);
} }
public PhysicalPlan executable(String sql, TimeZone timeZone) { public PhysicalPlan executable(String sql) {
return physicalPlan(parse(sql, timeZone), true); CURRENT.set(settings);
try {
return physicalPlan(parse(sql), true);
} finally {
CURRENT.remove();
}
}
public void sql(String sql, ActionListener<RowSetCursor> listener) {
executable(sql).execute(this, listener);
} }
public SqlSettings defaults() { public SqlSettings defaults() {

View File

@ -6,12 +6,19 @@
package org.elasticsearch.xpack.sql.session; package org.elasticsearch.xpack.sql.session;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.joda.time.DateTimeZone;
// Typed object holding properties for a given // Typed object holding properties for a given
public class SqlSettings { public class SqlSettings {
public static final SqlSettings EMPTY = new SqlSettings(Settings.EMPTY); public static final SqlSettings EMPTY = new SqlSettings(Settings.EMPTY);
public static final String TIMEZONE_ID = "sql.timeZoneId";
public static final String TIMEZONE_ID_DEFAULT = null;
public static final String PAGE_SIZE = "sql.fetch.size";
public static final int PAGE_SIZE_DEFAULT = 100;
private final Settings cfg; private final Settings cfg;
public SqlSettings(Settings cfg) { public SqlSettings(Settings cfg) {
@ -27,14 +34,15 @@ public class SqlSettings {
return cfg.toDelimitedString(','); return cfg.toDelimitedString(',');
} }
public boolean dateAsString() { public String timeZoneId() {
return cfg.getAsBoolean(DATE_AS_STRING, false); return cfg.get(TIMEZONE_ID, TIMEZONE_ID_DEFAULT);
} }
public SqlSettings dateAsString(boolean value) { public DateTimeZone timeZone() {
return new SqlSettings(Settings.builder().put(cfg).put(DATE_AS_STRING, value).build()); return DateTimeZone.forID(TIMEZONE_ID);
} }
public int pageSize() {
private static final String DATE_AS_STRING = "sql.date_as_string"; return cfg.getAsInt(PAGE_SIZE, 100);
}
} }

View File

@ -11,25 +11,24 @@ import org.elasticsearch.xpack.sql.type.DateType;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.joda.time.DateTimeZone; import org.joda.time.DateTimeZone;
import java.util.TimeZone;
public class DayOfYearTests extends ESTestCase { public class DayOfYearTests extends ESTestCase {
private static final TimeZone UTC = TimeZone.getTimeZone("UTC"); private static final DateTimeZone UTC = DateTimeZone.UTC;
public void testAsColumnProcessor() { public void testAsColumnProcessor() {
assertEquals(1, extract(dateTime(0), UTC)); assertEquals(1, extract(dateTime(0), UTC));
assertEquals(1, extract(dateTime(0), TimeZone.getTimeZone("GMT+1"))); assertEquals(1, extract(dateTime(0), DateTimeZone.forID("GMT+1")));
assertEquals(365, extract(dateTime(0), TimeZone.getTimeZone("GMT-1"))); assertEquals(365, extract(dateTime(0), DateTimeZone.forID("GMT-1")));
} }
private DateTime dateTime(long millisSinceEpoch) { private DateTime dateTime(long millisSinceEpoch) {
return new DateTime(millisSinceEpoch, DateTimeZone.forTimeZone(UTC)); return new DateTime(millisSinceEpoch, UTC);
} }
private Object extract(Object value, TimeZone timeZone) { private Object extract(Object value, DateTimeZone timeZone) {
return build(value, timeZone).asProcessor().apply(value); return build(value, timeZone).asProcessor().apply(value);
} }
private DayOfYear build(Object value, TimeZone timeZone) { private DayOfYear build(Object value, DateTimeZone timeZone) {
return new DayOfYear(null, new Literal(null, value, DateType.DEFAULT), timeZone); return new DayOfYear(null, new Literal(null, value, DateType.DEFAULT), timeZone);
} }
} }