SQL: generalize the use of ? for STRING (elastic/x-pack-elasticsearch#4359)

Improve grammar to allow use of ? as an alternative to STRING
through-out all commands
Add various parsing tests checking the ? usage for SYS commands

Original commit: elastic/x-pack-elasticsearch@d0d1feeb4c
This commit is contained in:
Costin Leau 2018-04-13 18:16:47 +03:00 committed by GitHub
parent 0d83edbca5
commit fc5e1631f1
20 changed files with 1051 additions and 893 deletions

View File

@ -5,9 +5,9 @@
*/ */
package org.elasticsearch.xpack.sql.jdbc.jdbc; package org.elasticsearch.xpack.sql.jdbc.jdbc;
import org.elasticsearch.xpack.sql.client.shared.Version;
import org.elasticsearch.xpack.sql.jdbc.JdbcSQLException; import org.elasticsearch.xpack.sql.jdbc.JdbcSQLException;
import org.elasticsearch.xpack.sql.jdbc.debug.Debug; import org.elasticsearch.xpack.sql.jdbc.debug.Debug;
import org.elasticsearch.xpack.sql.client.shared.Version;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.sql.Connection; import java.sql.Connection;
@ -16,11 +16,8 @@ import java.sql.DriverPropertyInfo;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException; import java.sql.SQLFeatureNotSupportedException;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger; import java.util.logging.Logger;
import static org.elasticsearch.xpack.sql.client.shared.ConnectionConfiguration.CONNECT_TIMEOUT;
public class JdbcDriver implements java.sql.Driver { public class JdbcDriver implements java.sql.Driver {
private static final JdbcDriver INSTANCE = new JdbcDriver(); private static final JdbcDriver INSTANCE = new JdbcDriver();
@ -67,6 +64,7 @@ public class JdbcDriver implements java.sql.Driver {
// //
// Jdbc 4.0 // Jdbc 4.0
// //
@Override
public Connection connect(String url, Properties props) throws SQLException { public Connection connect(String url, Properties props) throws SQLException {
if (url == null) { if (url == null) {
throw new JdbcSQLException("Non-null url required"); throw new JdbcSQLException("Non-null url required");
@ -116,6 +114,7 @@ public class JdbcDriver implements java.sql.Driver {
// Jdbc 4.1 // Jdbc 4.1
// //
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException { public Logger getParentLogger() throws SQLFeatureNotSupportedException {
throw new SQLFeatureNotSupportedException(); throw new SQLFeatureNotSupportedException();
} }

View File

@ -13,7 +13,6 @@ import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.ObjectParser; import org.elasticsearch.common.xcontent.ObjectParser;
import org.elasticsearch.common.xcontent.ToXContentObject; import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentParserUtils; import org.elasticsearch.common.xcontent.XContentParserUtils;
import org.elasticsearch.xpack.sql.type.DataType; import org.elasticsearch.xpack.sql.type.DataType;
@ -75,8 +74,12 @@ public class SqlTypedParamValue implements ToXContentObject, Writeable {
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) {
if (o == null || getClass() != o.getClass()) return false; return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
SqlTypedParamValue that = (SqlTypedParamValue) o; SqlTypedParamValue that = (SqlTypedParamValue) o;
return Objects.equals(value, that.value) && return Objects.equals(value, that.value) &&
dataType == that.dataType; dataType == that.dataType;
@ -84,7 +87,11 @@ public class SqlTypedParamValue implements ToXContentObject, Writeable {
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(value, dataType); return Objects.hash(value, dataType);
} }
}
@Override
public String toString() {
return String.valueOf(value) + "[" + dataType + "]";
}
}

View File

@ -58,10 +58,10 @@ statement
| SYS CATALOGS #sysCatalogs | SYS CATALOGS #sysCatalogs
| SYS TABLES (CATALOG LIKE? clusterPattern=pattern)? | SYS TABLES (CATALOG LIKE? clusterPattern=pattern)?
(LIKE? tablePattern=pattern)? (LIKE? tablePattern=pattern)?
(TYPE STRING (',' STRING)* )? #sysTables (TYPE string (',' string)* )? #sysTables
| SYS COLUMNS (CATALOG cluster=(STRING | PARAM))? | SYS COLUMNS (CATALOG cluster=string)?
(TABLE LIKE? indexPattern=pattern)? (TABLE LIKE? indexPattern=pattern)?
(LIKE? columnPattern=pattern)? #sysColumns (LIKE? columnPattern=pattern)? #sysColumns
| SYS TYPES #sysTypes | SYS TYPES #sysTypes
| SYS TABLE TYPES #sysTableTypes | SYS TABLE TYPES #sysTableTypes
; ;
@ -158,9 +158,9 @@ expression
booleanExpression booleanExpression
: NOT booleanExpression #logicalNot : NOT booleanExpression #logicalNot
| EXISTS '(' query ')' #exists | EXISTS '(' query ')' #exists
| QUERY '(' queryString=STRING (',' options=STRING)* ')' #stringQuery | QUERY '(' queryString=string (',' options=string)* ')' #stringQuery
| MATCH '(' singleField=qualifiedName ',' queryString=STRING (',' options=STRING)* ')' #matchQuery | MATCH '(' singleField=qualifiedName ',' queryString=string (',' options=string)* ')' #matchQuery
| MATCH '(' multiFields=STRING ',' queryString=STRING (',' options=STRING)* ')' #multiMatchQuery | MATCH '(' multiFields=string ',' queryString=string (',' options=string)* ')' #multiMatchQuery
| predicated #booleanDefault | predicated #booleanDefault
| left=booleanExpression operator=AND right=booleanExpression #logicalBinary | left=booleanExpression operator=AND right=booleanExpression #logicalBinary
| left=booleanExpression operator=OR right=booleanExpression #logicalBinary | left=booleanExpression operator=OR right=booleanExpression #logicalBinary
@ -180,13 +180,12 @@ predicate
| NOT? kind=IN '(' expression (',' expression)* ')' | NOT? kind=IN '(' expression (',' expression)* ')'
| NOT? kind=IN '(' query ')' | NOT? kind=IN '(' query ')'
| NOT? kind=LIKE pattern | NOT? kind=LIKE pattern
| NOT? kind=RLIKE regex=STRING | NOT? kind=RLIKE regex=string
| IS NOT? kind=NULL | IS NOT? kind=NULL
; ;
pattern pattern
: value=STRING (ESCAPE escape=STRING)? : value=string (ESCAPE escape=string)?
| PARAM
; ;
valueExpression valueExpression
@ -216,7 +215,7 @@ constant
| number #numericLiteral | number #numericLiteral
| booleanValue #booleanLiteral | booleanValue #booleanLiteral
| STRING+ #stringLiteral | STRING+ #stringLiteral
| PARAM #param | PARAM #paramLiteral
; ;
comparisonOperator comparisonOperator
@ -261,6 +260,11 @@ number
| INTEGER_VALUE #integerLiteral | INTEGER_VALUE #integerLiteral
; ;
string
: PARAM
| STRING
;
// http://developer.mimer.se/validator/sql-reserved-words.tml // http://developer.mimer.se/validator/sql-reserved-words.tml
nonReserved nonReserved
: ANALYZE | ANALYZED : ANALYZE | ANALYZED

View File

@ -92,13 +92,6 @@ abstract class AbstractBuilder extends SqlBaseBaseVisitor<Object> {
return node == null ? null : node.getText(); return node == null ? null : node.getText();
} }
/**
* Extracts the actual unescaped string (literal) value of a token.
*/
static String string(Token token) {
return token == null ? null : unquoteString(token.getText());
}
/** /**
* Extracts the actual unescaped string (literal) value of a terminal node. * Extracts the actual unescaped string (literal) value of a terminal node.
*/ */
@ -115,4 +108,4 @@ abstract class AbstractBuilder extends SqlBaseBaseVisitor<Object> {
public Object visitTerminal(TerminalNode node) { public Object visitTerminal(TerminalNode node) {
throw new ParsingException(source(node), "Does not know how to handle {}", node.getText()); throw new ParsingException(source(node), "Does not know how to handle {}", node.getText());
} }
} }

View File

@ -6,7 +6,6 @@
package org.elasticsearch.xpack.sql.parser; package org.elasticsearch.xpack.sql.parser;
import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.elasticsearch.common.Booleans; import org.elasticsearch.common.Booleans;
import org.elasticsearch.xpack.sql.analysis.index.IndexResolver.IndexType; import org.elasticsearch.xpack.sql.analysis.index.IndexResolver.IndexType;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.DebugContext; import org.elasticsearch.xpack.sql.parser.SqlBaseParser.DebugContext;
@ -15,6 +14,7 @@ import org.elasticsearch.xpack.sql.parser.SqlBaseParser.ShowColumnsContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.ShowFunctionsContext; import org.elasticsearch.xpack.sql.parser.SqlBaseParser.ShowFunctionsContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.ShowSchemasContext; import org.elasticsearch.xpack.sql.parser.SqlBaseParser.ShowSchemasContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.ShowTablesContext; import org.elasticsearch.xpack.sql.parser.SqlBaseParser.ShowTablesContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.StringContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.SysCatalogsContext; import org.elasticsearch.xpack.sql.parser.SqlBaseParser.SysCatalogsContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.SysColumnsContext; import org.elasticsearch.xpack.sql.parser.SqlBaseParser.SysColumnsContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.SysTableTypesContext; import org.elasticsearch.xpack.sql.parser.SqlBaseParser.SysTableTypesContext;
@ -43,6 +43,7 @@ import java.util.Locale;
import java.util.Map; import java.util.Map;
abstract class CommandBuilder extends LogicalPlanBuilder { abstract class CommandBuilder extends LogicalPlanBuilder {
protected CommandBuilder(Map<Token, SqlTypedParamValue> params) { protected CommandBuilder(Map<Token, SqlTypedParamValue> params) {
super(params); super(params);
} }
@ -146,7 +147,7 @@ abstract class CommandBuilder extends LogicalPlanBuilder {
@Override @Override
public SysTables visitSysTables(SysTablesContext ctx) { public SysTables visitSysTables(SysTablesContext ctx) {
List<IndexType> types = new ArrayList<>(); List<IndexType> types = new ArrayList<>();
for (TerminalNode string : ctx.STRING()) { for (StringContext string : ctx.string()) {
String value = string(string); String value = string(string);
if (value != null) { if (value != null) {
IndexType type = IndexType.from(value); IndexType type = IndexType.from(value);
@ -163,7 +164,7 @@ abstract class CommandBuilder extends LogicalPlanBuilder {
@Override @Override
public Object visitSysColumns(SysColumnsContext ctx) { public Object visitSysColumns(SysColumnsContext ctx) {
Location loc = source(ctx); Location loc = source(ctx);
return new SysColumns(loc, stringOrParam(ctx.cluster, loc), visitPattern(ctx.indexPattern), visitPattern(ctx.columnPattern)); return new SysColumns(loc, string(ctx.cluster), visitPattern(ctx.indexPattern), visitPattern(ctx.columnPattern));
} }
@Override @Override
@ -171,6 +172,7 @@ abstract class CommandBuilder extends LogicalPlanBuilder {
return new SysTypes(source(ctx)); return new SysTypes(source(ctx));
} }
@Override
public Object visitSysTableTypes(SysTableTypesContext ctx) { public Object visitSysTableTypes(SysTableTypesContext ctx) {
return new SysTableTypes(source(ctx)); return new SysTableTypes(source(ctx));
} }

View File

@ -63,6 +63,7 @@ import org.elasticsearch.xpack.sql.parser.SqlBaseParser.MatchQueryContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.MultiMatchQueryContext; import org.elasticsearch.xpack.sql.parser.SqlBaseParser.MultiMatchQueryContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.NullLiteralContext; import org.elasticsearch.xpack.sql.parser.SqlBaseParser.NullLiteralContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.OrderByContext; import org.elasticsearch.xpack.sql.parser.SqlBaseParser.OrderByContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.ParamLiteralContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.ParenthesizedExpressionContext; import org.elasticsearch.xpack.sql.parser.SqlBaseParser.ParenthesizedExpressionContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.PatternContext; import org.elasticsearch.xpack.sql.parser.SqlBaseParser.PatternContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.PredicateContext; import org.elasticsearch.xpack.sql.parser.SqlBaseParser.PredicateContext;
@ -71,6 +72,7 @@ import org.elasticsearch.xpack.sql.parser.SqlBaseParser.PrimitiveDataTypeContext
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.SelectExpressionContext; import org.elasticsearch.xpack.sql.parser.SqlBaseParser.SelectExpressionContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.SingleExpressionContext; import org.elasticsearch.xpack.sql.parser.SqlBaseParser.SingleExpressionContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.StarContext; import org.elasticsearch.xpack.sql.parser.SqlBaseParser.StarContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.StringContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.StringLiteralContext; import org.elasticsearch.xpack.sql.parser.SqlBaseParser.StringLiteralContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.StringQueryContext; import org.elasticsearch.xpack.sql.parser.SqlBaseParser.StringQueryContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.SubqueryExpressionContext; import org.elasticsearch.xpack.sql.parser.SqlBaseParser.SubqueryExpressionContext;
@ -88,14 +90,10 @@ import static java.util.Collections.singletonList;
import static org.elasticsearch.xpack.sql.type.DataTypeConversion.conversionFor; import static org.elasticsearch.xpack.sql.type.DataTypeConversion.conversionFor;
abstract class ExpressionBuilder extends IdentifierBuilder { abstract class ExpressionBuilder extends IdentifierBuilder {
private final Map<Token, SqlTypedParamValue> params; private final Map<Token, SqlTypedParamValue> params;
/** ExpressionBuilder(Map<Token, SqlTypedParamValue> params) {
* ExpressionBuilder constructor
*
* @param params a map between '?' tokens that represent parameters and the actual parameter values
*/
protected ExpressionBuilder(Map<Token, SqlTypedParamValue> params) {
this.params = params; this.params = params;
} }
@ -215,11 +213,6 @@ abstract class ExpressionBuilder extends IdentifierBuilder {
return null; return null;
} }
if (ctx.PARAM() != null) {
Object pattern = paramValue(ctx.PARAM().getSymbol(), source(ctx));
return new LikePattern(source(ctx), pattern.toString(), (char) 0);
}
String pattern = string(ctx.value); String pattern = string(ctx.value);
int pos = pattern.indexOf('*'); int pos = pattern.indexOf('*');
if (pos >= 0) { if (pos >= 0) {
@ -452,16 +445,21 @@ abstract class ExpressionBuilder extends IdentifierBuilder {
} }
@Override @Override
public Object visitParam(SqlBaseParser.ParamContext ctx) { public Object visitDecimalLiteral(DecimalLiteralContext ctx) {
Token token = ctx.PARAM().getSymbol(); return new Literal(source(ctx), new BigDecimal(ctx.getText()).doubleValue(), DataType.DOUBLE);
return paramValue(token, source(ctx));
} }
private Object paramValue(Token token, Location loc) { @Override
if (params.containsKey(token) == false) { public Object visitIntegerLiteral(IntegerLiteralContext ctx) {
throw new ParsingException(loc, "Unexpected parameter"); BigDecimal bigD = new BigDecimal(ctx.getText());
} // TODO: this can be improved to use the smallest type available
SqlTypedParamValue param = params.get(token); return new Literal(source(ctx), bigD.longValueExact(), DataType.INTEGER);
}
@Override
public Object visitParamLiteral(ParamLiteralContext ctx) {
SqlTypedParamValue param = param(ctx.PARAM());
Location loc = source(ctx);
if (param.value == null) { if (param.value == null) {
// no conversion is required for null values // no conversion is required for null values
return new Literal(loc, null, param.dataType); return new Literal(loc, null, param.dataType);
@ -470,8 +468,8 @@ abstract class ExpressionBuilder extends IdentifierBuilder {
try { try {
sourceType = DataTypes.fromJava(param.value); sourceType = DataTypes.fromJava(param.value);
} catch (SqlIllegalArgumentException ex) { } catch (SqlIllegalArgumentException ex) {
throw new ParsingException(ex, loc, "Unexpected actual parameter type [{}] for type [{}]", throw new ParsingException(ex, loc, "Unexpected actual parameter type [{}] for type [{}]", param.value.getClass().getName(),
param.value.getClass().getName(), param.dataType); param.dataType);
} }
if (sourceType == param.dataType) { if (sourceType == param.dataType) {
// no conversion is required if the value is already have correct type // no conversion is required if the value is already have correct type
@ -485,32 +483,37 @@ abstract class ExpressionBuilder extends IdentifierBuilder {
} }
} }
@Override
public String visitString(StringContext ctx) {
return string(ctx);
}
/** /**
* Extracts the actual unescaped string (literal) value of a token or a parameter value * Extracts the string (either as unescaped literal) or parameter.
*/ */
public String stringOrParam(Token token, Location loc) { String string(StringContext ctx) {
if (token == null) { if (ctx == null) {
return null; return null;
} }
if (token.getType() == SqlBaseLexer.PARAM) { SqlTypedParamValue param = param(ctx.PARAM());
if (params.containsKey(token) == false) { if (param != null) {
throw new ParsingException(loc, "Unexpected parameter"); return param.value != null ? param.value.toString() : null;
} } else {
SqlTypedParamValue param = params.get(token); return unquoteString(ctx.getText());
return param.value == null ? null : param.value.toString();
} }
return unquoteString(token.getText());
} }
@Override private SqlTypedParamValue param(TerminalNode node) {
public Object visitDecimalLiteral(DecimalLiteralContext ctx) { if (node == null) {
return new Literal(source(ctx), new BigDecimal(ctx.getText()).doubleValue(), DataType.DOUBLE); return null;
} }
@Override Token token = node.getSymbol();
public Object visitIntegerLiteral(IntegerLiteralContext ctx) {
BigDecimal bigD = new BigDecimal(ctx.getText()); if (params.containsKey(token) == false) {
// TODO: this can be improved to use the smallest type available throw new ParsingException(source(node), "Unexpected parameter");
return new Literal(source(ctx), bigD.longValueExact(), DataType.INTEGER); }
return params.get(token);
} }
} }

View File

@ -53,6 +53,7 @@ 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 {
protected LogicalPlanBuilder(Map<Token, SqlTypedParamValue> params) { protected LogicalPlanBuilder(Map<Token, SqlTypedParamValue> params) {
super(params); super(params);
} }
@ -80,6 +81,7 @@ abstract class LogicalPlanBuilder extends ExpressionBuilder {
return new SubQueryAlias(source(ctx), plan(ctx.queryNoWith()), ctx.name.getText()); return new SubQueryAlias(source(ctx), plan(ctx.queryNoWith()), ctx.name.getText());
} }
@Override
public LogicalPlan visitQueryNoWith(QueryNoWithContext ctx) { public LogicalPlan visitQueryNoWith(QueryNoWithContext ctx) {
LogicalPlan plan = plan(ctx.queryTerm()); LogicalPlan plan = plan(ctx.queryTerm());

View File

@ -765,13 +765,13 @@ class SqlBaseBaseListener implements SqlBaseListener {
* *
* <p>The default implementation does nothing.</p> * <p>The default implementation does nothing.</p>
*/ */
@Override public void enterParam(SqlBaseParser.ParamContext ctx) { } @Override public void enterParamLiteral(SqlBaseParser.ParamLiteralContext ctx) { }
/** /**
* {@inheritDoc} * {@inheritDoc}
* *
* <p>The default implementation does nothing.</p> * <p>The default implementation does nothing.</p>
*/ */
@Override public void exitParam(SqlBaseParser.ParamContext ctx) { } @Override public void exitParamLiteral(SqlBaseParser.ParamLiteralContext ctx) { }
/** /**
* {@inheritDoc} * {@inheritDoc}
* *
@ -916,6 +916,18 @@ class SqlBaseBaseListener implements SqlBaseListener {
* <p>The default implementation does nothing.</p> * <p>The default implementation does nothing.</p>
*/ */
@Override public void exitIntegerLiteral(SqlBaseParser.IntegerLiteralContext ctx) { } @Override public void exitIntegerLiteral(SqlBaseParser.IntegerLiteralContext ctx) { }
/**
* {@inheritDoc}
*
* <p>The default implementation does nothing.</p>
*/
@Override public void enterString(SqlBaseParser.StringContext ctx) { }
/**
* {@inheritDoc}
*
* <p>The default implementation does nothing.</p>
*/
@Override public void exitString(SqlBaseParser.StringContext ctx) { }
/** /**
* {@inheritDoc} * {@inheritDoc}
* *

View File

@ -456,7 +456,7 @@ class SqlBaseBaseVisitor<T> extends AbstractParseTreeVisitor<T> implements SqlBa
* <p>The default implementation returns the result of calling * <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p> * {@link #visitChildren} on {@code ctx}.</p>
*/ */
@Override public T visitParam(SqlBaseParser.ParamContext ctx) { return visitChildren(ctx); } @Override public T visitParamLiteral(SqlBaseParser.ParamLiteralContext ctx) { return visitChildren(ctx); }
/** /**
* {@inheritDoc} * {@inheritDoc}
* *
@ -541,6 +541,13 @@ class SqlBaseBaseVisitor<T> extends AbstractParseTreeVisitor<T> implements SqlBa
* {@link #visitChildren} on {@code ctx}.</p> * {@link #visitChildren} on {@code ctx}.</p>
*/ */
@Override public T visitIntegerLiteral(SqlBaseParser.IntegerLiteralContext ctx) { return visitChildren(ctx); } @Override public T visitIntegerLiteral(SqlBaseParser.IntegerLiteralContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitString(SqlBaseParser.StringContext ctx) { return visitChildren(ctx); }
/** /**
* {@inheritDoc} * {@inheritDoc}
* *

View File

@ -719,17 +719,17 @@ interface SqlBaseListener extends ParseTreeListener {
*/ */
void exitStringLiteral(SqlBaseParser.StringLiteralContext ctx); void exitStringLiteral(SqlBaseParser.StringLiteralContext ctx);
/** /**
* Enter a parse tree produced by the {@code param} * Enter a parse tree produced by the {@code paramLiteral}
* labeled alternative in {@link SqlBaseParser#constant}. * labeled alternative in {@link SqlBaseParser#constant}.
* @param ctx the parse tree * @param ctx the parse tree
*/ */
void enterParam(SqlBaseParser.ParamContext ctx); void enterParamLiteral(SqlBaseParser.ParamLiteralContext ctx);
/** /**
* Exit a parse tree produced by the {@code param} * Exit a parse tree produced by the {@code paramLiteral}
* labeled alternative in {@link SqlBaseParser#constant}. * labeled alternative in {@link SqlBaseParser#constant}.
* @param ctx the parse tree * @param ctx the parse tree
*/ */
void exitParam(SqlBaseParser.ParamContext ctx); void exitParamLiteral(SqlBaseParser.ParamLiteralContext ctx);
/** /**
* Enter a parse tree produced by {@link SqlBaseParser#comparisonOperator}. * Enter a parse tree produced by {@link SqlBaseParser#comparisonOperator}.
* @param ctx the parse tree * @param ctx the parse tree
@ -864,6 +864,16 @@ interface SqlBaseListener extends ParseTreeListener {
* @param ctx the parse tree * @param ctx the parse tree
*/ */
void exitIntegerLiteral(SqlBaseParser.IntegerLiteralContext ctx); void exitIntegerLiteral(SqlBaseParser.IntegerLiteralContext ctx);
/**
* Enter a parse tree produced by {@link SqlBaseParser#string}.
* @param ctx the parse tree
*/
void enterString(SqlBaseParser.StringContext ctx);
/**
* Exit a parse tree produced by {@link SqlBaseParser#string}.
* @param ctx the parse tree
*/
void exitString(SqlBaseParser.StringContext ctx);
/** /**
* Enter a parse tree produced by {@link SqlBaseParser#nonReserved}. * Enter a parse tree produced by {@link SqlBaseParser#nonReserved}.
* @param ctx the parse tree * @param ctx the parse tree

View File

@ -431,12 +431,12 @@ interface SqlBaseVisitor<T> extends ParseTreeVisitor<T> {
*/ */
T visitStringLiteral(SqlBaseParser.StringLiteralContext ctx); T visitStringLiteral(SqlBaseParser.StringLiteralContext ctx);
/** /**
* Visit a parse tree produced by the {@code param} * Visit a parse tree produced by the {@code paramLiteral}
* labeled alternative in {@link SqlBaseParser#constant}. * labeled alternative in {@link SqlBaseParser#constant}.
* @param ctx the parse tree * @param ctx the parse tree
* @return the visitor result * @return the visitor result
*/ */
T visitParam(SqlBaseParser.ParamContext ctx); T visitParamLiteral(SqlBaseParser.ParamLiteralContext ctx);
/** /**
* Visit a parse tree produced by {@link SqlBaseParser#comparisonOperator}. * Visit a parse tree produced by {@link SqlBaseParser#comparisonOperator}.
* @param ctx the parse tree * @param ctx the parse tree
@ -516,6 +516,12 @@ interface SqlBaseVisitor<T> extends ParseTreeVisitor<T> {
* @return the visitor result * @return the visitor result
*/ */
T visitIntegerLiteral(SqlBaseParser.IntegerLiteralContext ctx); T visitIntegerLiteral(SqlBaseParser.IntegerLiteralContext ctx);
/**
* Visit a parse tree produced by {@link SqlBaseParser#string}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitString(SqlBaseParser.StringContext ctx);
/** /**
* Visit a parse tree produced by {@link SqlBaseParser#nonReserved}. * Visit a parse tree produced by {@link SqlBaseParser#nonReserved}.
* @param ctx the parse tree * @param ctx the parse tree

View File

@ -40,10 +40,6 @@ 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);
/**
* Time zone in which the SQL is parsed. This is attached to functions
* that deal with dates and times.
*/
private final boolean DEBUG = false; private final boolean DEBUG = false;
/** /**
@ -119,7 +115,7 @@ public class SqlParser {
parser.addParseListener(parser.new TraceListener()); parser.addParseListener(parser.new TraceListener());
parser.addErrorListener(new DiagnosticErrorListener() { parser.addErrorListener(new DiagnosticErrorListener(false) {
@Override @Override
public void reportAttemptingFullContext(Parser recognizer, DFA dfa, public void reportAttemptingFullContext(Parser recognizer, DFA dfa,
int startIndex, int stopIndex, BitSet conflictingAlts, ATNConfigSet configs) {} int startIndex, int stopIndex, BitSet conflictingAlts, ATNConfigSet configs) {}

View File

@ -46,7 +46,7 @@ public abstract class DataTypes {
if (value instanceof DateTime) { if (value instanceof DateTime) {
return DataType.DATE; return DataType.DATE;
} }
if (value instanceof String) { if (value instanceof String || value instanceof Character) {
return DataType.KEYWORD; return DataType.KEYWORD;
} }
throw new SqlIllegalArgumentException("No idea what's the DataType for {}", value.getClass()); throw new SqlIllegalArgumentException("No idea what's the DataType for {}", value.getClass());

View File

@ -9,9 +9,12 @@ import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.sql.expression.Expression; import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.regex.Like; import org.elasticsearch.xpack.sql.expression.regex.Like;
import org.elasticsearch.xpack.sql.expression.regex.LikePattern; import org.elasticsearch.xpack.sql.expression.regex.LikePattern;
import org.elasticsearch.xpack.sql.plugin.SqlTypedParamValue;
import org.elasticsearch.xpack.sql.type.DataType;
import java.util.Locale; import java.util.Locale;
import static java.util.Collections.singletonList;
import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
@ -27,14 +30,20 @@ public class LikeEscapingParsingTests extends ESTestCase {
} }
private LikePattern like(String pattern) { private LikePattern like(String pattern) {
Expression exp = parser.createExpression(String.format(Locale.ROOT, "exp LIKE %s", pattern)); Expression exp = null;
boolean parameterized = randomBoolean();
if (parameterized) {
exp = parser.createExpression("exp LIKE ?", singletonList(new SqlTypedParamValue(pattern, DataType.KEYWORD)));
} else {
exp = parser.createExpression(String.format(Locale.ROOT, "exp LIKE '%s'", pattern));
}
assertThat(exp, instanceOf(Like.class)); assertThat(exp, instanceOf(Like.class));
Like l = (Like) exp; Like l = (Like) exp;
return l.right(); return l.right();
} }
public void testNoEscaping() { public void testNoEscaping() {
LikePattern like = like("'string'"); LikePattern like = like("string");
assertThat(like.pattern(), is("string")); assertThat(like.pattern(), is("string"));
assertThat(like.asJavaRegex(), is("^string$")); assertThat(like.asJavaRegex(), is("^string$"));
assertThat(like.asLuceneWildcard(), is("string")); assertThat(like.asLuceneWildcard(), is("string"));

View File

@ -19,30 +19,40 @@ import org.elasticsearch.xpack.sql.session.SqlSession;
import org.elasticsearch.xpack.sql.type.TypesTests; import org.elasticsearch.xpack.sql.type.TypesTests;
import org.joda.time.DateTimeZone; import org.joda.time.DateTimeZone;
import static java.util.Collections.singletonList;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class SysCatalogTests extends ESTestCase { public class SysCatalogsTests extends ESTestCase {
private final SqlParser parser = new SqlParser(); private final SqlParser parser = new SqlParser();
@SuppressWarnings({ "rawtypes", "unchecked" })
private Tuple<Command, SqlSession> sql(String sql) { private Tuple<Command, SqlSession> sql(String sql) {
EsIndex test = new EsIndex("test", TypesTests.loadMapping("mapping-multi-field-with-nested.json", true)); EsIndex test = new EsIndex("test", TypesTests.loadMapping("mapping-multi-field-with-nested.json", true));
Analyzer analyzer = new Analyzer(new FunctionRegistry(), IndexResolution.valid(test), DateTimeZone.UTC); Analyzer analyzer = new Analyzer(new FunctionRegistry(), IndexResolution.valid(test), DateTimeZone.UTC);
Command cmd = (Command) analyzer.analyze(parser.createStatement(sql), true); Command cmd = (Command) analyzer.analyze(parser.createStatement(sql), true);
IndexResolver resolver = mock(IndexResolver.class); IndexResolver resolver = mock(IndexResolver.class);
when(resolver.clusterName()).thenReturn("cluster");
doAnswer(invocation -> {
((ActionListener) invocation.getArguments()[2]).onResponse(singletonList(test));
return Void.TYPE;
}).when(resolver).resolveAsSeparateMappings(any(), any(), any());
SqlSession session = new SqlSession(null, null, null, resolver, null, null, null); SqlSession session = new SqlSession(null, null, null, resolver, null, null, null);
return new Tuple<>(cmd, session); return new Tuple<>(cmd, session);
} }
public void testSysCatalogs() throws Exception { public void testSysCatalogs() throws Exception {
Tuple<Command, SqlSession> sql = sql("SYS TABLE TYPES"); Tuple<Command, SqlSession> sql = sql("SYS CATALOGS");
sql.v1().execute(sql.v2(), ActionListener.wrap(r -> { sql.v1().execute(sql.v2(), ActionListener.wrap(r -> {
assertEquals(2, r.size()); assertEquals(1, r.size());
assertEquals("BASE TABLE", r.column(0)); assertEquals("cluster", r.column(0));
r.advanceRow();
assertEquals("ALIAS", r.column(0));
}, ex -> fail(ex.getMessage()))); }, ex -> fail(ex.getMessage())));
} }
} }

View File

@ -19,40 +19,30 @@ import org.elasticsearch.xpack.sql.session.SqlSession;
import org.elasticsearch.xpack.sql.type.TypesTests; import org.elasticsearch.xpack.sql.type.TypesTests;
import org.joda.time.DateTimeZone; import org.joda.time.DateTimeZone;
import static java.util.Collections.singletonList;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class SysTableTypesTests extends ESTestCase { public class SysTableTypesTests extends ESTestCase {
private final SqlParser parser = new SqlParser(); private final SqlParser parser = new SqlParser();
@SuppressWarnings({ "rawtypes", "unchecked" })
private Tuple<Command, SqlSession> sql(String sql) { private Tuple<Command, SqlSession> sql(String sql) {
EsIndex test = new EsIndex("test", TypesTests.loadMapping("mapping-multi-field-with-nested.json", true)); EsIndex test = new EsIndex("test", TypesTests.loadMapping("mapping-multi-field-with-nested.json", true));
Analyzer analyzer = new Analyzer(new FunctionRegistry(), IndexResolution.valid(test), DateTimeZone.UTC); Analyzer analyzer = new Analyzer(new FunctionRegistry(), IndexResolution.valid(test), DateTimeZone.UTC);
Command cmd = (Command) analyzer.analyze(parser.createStatement(sql), true); Command cmd = (Command) analyzer.analyze(parser.createStatement(sql), true);
IndexResolver resolver = mock(IndexResolver.class); IndexResolver resolver = mock(IndexResolver.class);
when(resolver.clusterName()).thenReturn("cluster");
doAnswer(invocation -> {
((ActionListener) invocation.getArguments()[2]).onResponse(singletonList(test));
return Void.TYPE;
}).when(resolver).resolveAsSeparateMappings(any(), any(), any());
SqlSession session = new SqlSession(null, null, null, resolver, null, null, null); SqlSession session = new SqlSession(null, null, null, resolver, null, null, null);
return new Tuple<>(cmd, session); return new Tuple<>(cmd, session);
} }
public void testSysCatalogs() throws Exception { public void testSysCatalogs() throws Exception {
Tuple<Command, SqlSession> sql = sql("SYS CATALOGS"); Tuple<Command, SqlSession> sql = sql("SYS TABLE TYPES");
sql.v1().execute(sql.v2(), ActionListener.wrap(r -> { sql.v1().execute(sql.v2(), ActionListener.wrap(r -> {
assertEquals(1, r.size()); assertEquals(2, r.size());
assertEquals("cluster", r.column(0)); assertEquals("BASE TABLE", r.column(0));
r.advanceRow();
assertEquals("ALIAS", r.column(0));
}, ex -> fail(ex.getMessage()))); }, ex -> fail(ex.getMessage())));
} }
} }

View File

@ -18,17 +18,21 @@ import org.elasticsearch.xpack.sql.expression.function.FunctionRegistry;
import org.elasticsearch.xpack.sql.parser.ParsingException; import org.elasticsearch.xpack.sql.parser.ParsingException;
import org.elasticsearch.xpack.sql.parser.SqlParser; import org.elasticsearch.xpack.sql.parser.SqlParser;
import org.elasticsearch.xpack.sql.plan.logical.command.Command; import org.elasticsearch.xpack.sql.plan.logical.command.Command;
import org.elasticsearch.xpack.sql.plugin.SqlTypedParamValue;
import org.elasticsearch.xpack.sql.session.SchemaRowSet; import org.elasticsearch.xpack.sql.session.SchemaRowSet;
import org.elasticsearch.xpack.sql.session.SqlSession; import org.elasticsearch.xpack.sql.session.SqlSession;
import org.elasticsearch.xpack.sql.type.DataTypes;
import org.elasticsearch.xpack.sql.type.EsField; import org.elasticsearch.xpack.sql.type.EsField;
import org.elasticsearch.xpack.sql.type.TypesTests; import org.elasticsearch.xpack.sql.type.TypesTests;
import org.joda.time.DateTimeZone; import org.joda.time.DateTimeZone;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Consumer; import java.util.function.Consumer;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static org.elasticsearch.action.ActionListener.wrap; import static org.elasticsearch.action.ActionListener.wrap;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doAnswer;
@ -67,6 +71,16 @@ public class SysTablesTests extends ESTestCase {
}, index, alias); }, index, alias);
} }
public void testSysTablesPatternParameterized() throws Exception {
List<SqlTypedParamValue> params = asList(param("%"));
executeCommand("SYS TABLES LIKE ?", params, r -> {
assertEquals(2, r.size());
assertEquals("test", r.column(2));
assertTrue(r.advanceRow());
assertEquals("alias", r.column(2));
}, index, alias);
}
public void testSysTablesOnlyAliases() throws Exception { public void testSysTablesOnlyAliases() throws Exception {
executeCommand("SYS TABLES LIKE 'test' TYPE 'ALIAS'", r -> { executeCommand("SYS TABLES LIKE 'test' TYPE 'ALIAS'", r -> {
assertEquals(1, r.size()); assertEquals(1, r.size());
@ -74,6 +88,14 @@ public class SysTablesTests extends ESTestCase {
}, alias); }, alias);
} }
public void testSysTablesOnlyAliasesParameterized() throws Exception {
List<SqlTypedParamValue> params = asList(param("ALIAS"));
executeCommand("SYS TABLES LIKE 'test' TYPE ?", params, r -> {
assertEquals(1, r.size());
assertEquals("alias", r.column(2));
}, alias);
}
public void testSysTablesOnlyIndices() throws Exception { public void testSysTablesOnlyIndices() throws Exception {
executeCommand("SYS TABLES LIKE 'test' TYPE 'BASE TABLE'", r -> { executeCommand("SYS TABLES LIKE 'test' TYPE 'BASE TABLE'", r -> {
assertEquals(1, r.size()); assertEquals(1, r.size());
@ -81,6 +103,13 @@ public class SysTablesTests extends ESTestCase {
}, index); }, index);
} }
public void testSysTablesOnlyIndicesParameterized() throws Exception {
executeCommand("SYS TABLES LIKE 'test' TYPE ?", asList(param("ALIAS")), r -> {
assertEquals(1, r.size());
assertEquals("test", r.column(2));
}, index);
}
public void testSysTablesOnlyIndicesAndAliases() throws Exception { public void testSysTablesOnlyIndicesAndAliases() throws Exception {
executeCommand("SYS TABLES LIKE 'test' TYPE 'ALIAS', 'BASE TABLE'", r -> { executeCommand("SYS TABLES LIKE 'test' TYPE 'ALIAS', 'BASE TABLE'", r -> {
assertEquals(2, r.size()); assertEquals(2, r.size());
@ -90,15 +119,33 @@ public class SysTablesTests extends ESTestCase {
}, index, alias); }, index, alias);
} }
public void testSysTablesOnlyIndicesAndAliasesParameterized() throws Exception {
List<SqlTypedParamValue> params = asList(param("ALIAS"), param("BASE TABLE"));
executeCommand("SYS TABLES LIKE 'test' TYPE ?, ?", params, r -> {
assertEquals(2, r.size());
assertEquals("test", r.column(2));
assertTrue(r.advanceRow());
assertEquals("alias", r.column(2));
}, index, alias);
}
public void testSysTablesWithInvalidType() throws Exception { public void testSysTablesWithInvalidType() throws Exception {
ParsingException pe = expectThrows(ParsingException.class, () -> sql("SYS TABLES LIKE 'test' TYPE 'QUE HORA ES'")); ParsingException pe = expectThrows(ParsingException.class, () -> sql("SYS TABLES LIKE 'test' TYPE 'QUE HORA ES'"));
assertEquals("line 1:2: Invalid table type [QUE HORA ES]", pe.getMessage()); assertEquals("line 1:2: Invalid table type [QUE HORA ES]", pe.getMessage());
} }
private SqlTypedParamValue param(Object value) {
return new SqlTypedParamValue(value, DataTypes.fromJava(value));
}
private Tuple<Command, SqlSession> sql(String sql) { private Tuple<Command, SqlSession> sql(String sql) {
return sql(sql, emptyList());
}
private Tuple<Command, SqlSession> sql(String sql, List<SqlTypedParamValue> params) {
EsIndex test = new EsIndex("test", mapping); EsIndex test = new EsIndex("test", mapping);
Analyzer analyzer = new Analyzer(new FunctionRegistry(), IndexResolution.valid(test), DateTimeZone.UTC); Analyzer analyzer = new Analyzer(new FunctionRegistry(), IndexResolution.valid(test), DateTimeZone.UTC);
Command cmd = (Command) analyzer.analyze(parser.createStatement(sql), true); Command cmd = (Command) analyzer.analyze(parser.createStatement(sql, params), true);
IndexResolver resolver = mock(IndexResolver.class); IndexResolver resolver = mock(IndexResolver.class);
when(resolver.clusterName()).thenReturn("cluster"); when(resolver.clusterName()).thenReturn("cluster");
@ -107,9 +154,14 @@ public class SysTablesTests extends ESTestCase {
return new Tuple<>(cmd, session); return new Tuple<>(cmd, session);
} }
@SuppressWarnings({ "unchecked", "rawtypes" })
private void executeCommand(String sql, Consumer<SchemaRowSet> consumer, IndexInfo... infos) throws Exception { private void executeCommand(String sql, Consumer<SchemaRowSet> consumer, IndexInfo... infos) throws Exception {
Tuple<Command, SqlSession> tuple = sql(sql); executeCommand(sql, emptyList(), consumer, infos);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private void executeCommand(String sql, List<SqlTypedParamValue> params, Consumer<SchemaRowSet> consumer, IndexInfo... infos)
throws Exception {
Tuple<Command, SqlSession> tuple = sql(sql, params);
IndexResolver resolver = tuple.v2().indexResolver(); IndexResolver resolver = tuple.v2().indexResolver();

View File

@ -87,4 +87,4 @@ public class DatabaseMetaDataTestCase extends JdbcIntegrationTestCase {
assertResultSets(expected, es.getMetaData().getColumns(null, "%", "%", null)); assertResultSets(expected, es.getMetaData().getColumns(null, "%", "%", null));
} }
} }
} }

View File

@ -0,0 +1,15 @@
CREATE TABLE mock (
TABLE_SCHEM VARCHAR,
TABLE_NAME VARCHAR,
TABLE_TYPE VARCHAR,
REMARKS VARCHAR,
TYPE_CAT VARCHAR,
TYPE_SCHEM VARCHAR,
TYPE_NAME VARCHAR,
SELF_REFERENCING_COL_NAME VARCHAR,
REF_GENERATION VARCHAR
) AS
SELECT '', 'test1', 'BASE TABLE', '', null, null, null, null, null FROM DUAL
UNION ALL
SELECT '', 'test2', 'BASE TABLE', '', null, null, null, null, null FROM DUAL
;