diff --git a/ksql-engine/src/test/java/io/confluent/ksql/engine/rewrite/AstSanitizerTest.java b/ksql-engine/src/test/java/io/confluent/ksql/engine/rewrite/AstSanitizerTest.java index fd8a3af120e..50d30465f01 100644 --- a/ksql-engine/src/test/java/io/confluent/ksql/engine/rewrite/AstSanitizerTest.java +++ b/ksql-engine/src/test/java/io/confluent/ksql/engine/rewrite/AstSanitizerTest.java @@ -296,10 +296,10 @@ public void shouldPreserveAliasIfPresent() { private static Statement givenQuery(final String sql) { final List statements = new DefaultKsqlParser().parse(sql); assertThat(statements, hasSize(1)); - return new AstBuilder(META_STORE).build(statements.get(0).getStatement()); + return new AstBuilder(META_STORE).buildStatement(statements.get(0).getStatement()); } private static ColumnReferenceExp column(final SourceName source, final String fieldName) { return new ColumnReferenceExp(ColumnRef.of(source, ColumnName.of(fieldName))); } -} \ No newline at end of file +} diff --git a/ksql-engine/src/test/java/io/confluent/ksql/engine/rewrite/DataSourceExtractorTest.java b/ksql-engine/src/test/java/io/confluent/ksql/engine/rewrite/DataSourceExtractorTest.java index 6436a569b35..4e1e69b7e50 100644 --- a/ksql-engine/src/test/java/io/confluent/ksql/engine/rewrite/DataSourceExtractorTest.java +++ b/ksql-engine/src/test/java/io/confluent/ksql/engine/rewrite/DataSourceExtractorTest.java @@ -186,6 +186,6 @@ public void shouldThrowIfRightJoinSourceDoesNotExist() { private static AstNode givenQuery(final String sql) { final List statements = new DefaultKsqlParser().parse(sql); assertThat(statements, hasSize(1)); - return new AstBuilder(META_STORE).build(statements.get(0).getStatement()); + return new AstBuilder(META_STORE).buildStatement(statements.get(0).getStatement()); } } \ No newline at end of file diff --git a/ksql-execution/src/main/java/io/confluent/ksql/execution/plan/SelectExpression.java b/ksql-execution/src/main/java/io/confluent/ksql/execution/plan/SelectExpression.java index ef1f8c26f83..b06486d3fba 100644 --- a/ksql-execution/src/main/java/io/confluent/ksql/execution/plan/SelectExpression.java +++ b/ksql-execution/src/main/java/io/confluent/ksql/execution/plan/SelectExpression.java @@ -15,8 +15,10 @@ package io.confluent.ksql.execution.plan; +import io.confluent.ksql.execution.expression.formatter.ExpressionFormatter; import io.confluent.ksql.execution.expression.tree.Expression; import io.confluent.ksql.name.ColumnName; +import io.confluent.ksql.schema.ksql.FormatOptions; import java.util.Objects; import javax.annotation.concurrent.Immutable; @@ -25,6 +27,7 @@ */ @Immutable public final class SelectExpression { + private static final String FMT = "%s AS %s"; private final ColumnName alias; private final Expression expression; @@ -66,9 +69,14 @@ public int hashCode() { @Override public String toString() { - return "SelectExpression{" - + "name='" + alias + '\'' - + ", expression=" + expression - + '}'; + return format(FormatOptions.none()); + } + + public String format(final FormatOptions formatOptions) { + return String.format( + FMT, + ExpressionFormatter.formatExpression(expression, formatOptions), + alias.toString(formatOptions) + ); } } diff --git a/ksql-parser/src/main/java/io/confluent/ksql/parser/AstBuilder.java b/ksql-parser/src/main/java/io/confluent/ksql/parser/AstBuilder.java index bb561ff49d1..2891811892f 100644 --- a/ksql-parser/src/main/java/io/confluent/ksql/parser/AstBuilder.java +++ b/ksql-parser/src/main/java/io/confluent/ksql/parser/AstBuilder.java @@ -69,7 +69,6 @@ import io.confluent.ksql.parser.SqlBaseParser.ListConnectorsContext; import io.confluent.ksql.parser.SqlBaseParser.ListTypesContext; import io.confluent.ksql.parser.SqlBaseParser.RegisterTypeContext; -import io.confluent.ksql.parser.SqlBaseParser.SingleStatementContext; import io.confluent.ksql.parser.SqlBaseParser.SourceNameContext; import io.confluent.ksql.parser.SqlBaseParser.TablePropertiesContext; import io.confluent.ksql.parser.SqlBaseParser.TablePropertyContext; @@ -158,12 +157,25 @@ public AstBuilder(final TypeRegistry typeRegistry) { this.typeRegistry = requireNonNull(typeRegistry, "typeRegistry"); } - public Statement build(final SingleStatementContext statement) { - final Node result = new Visitor( - getSources(statement), - typeRegistry - ).visit(statement); - return (Statement) result; + @SuppressWarnings("unchecked") + public Statement buildStatement(final ParserRuleContext parseTree) { + return build(Optional.of(getSources(parseTree)), typeRegistry, parseTree); + } + + public Expression buildExpression(final ParserRuleContext parseTree) { + return build(Optional.empty(), typeRegistry, parseTree); + } + + public WindowExpression buildWindowExpression(final ParserRuleContext parseTree) { + return build(Optional.empty(), typeRegistry, parseTree); + } + + @SuppressWarnings("unchecked") + private T build( + final Optional> sources, + final TypeRegistry typeRegistry, + final ParserRuleContext parseTree) { + return (T) new Visitor(sources, typeRegistry).visit(parseTree); } // CHECKSTYLE_RULES.OFF: ClassDataAbstractionCoupling @@ -172,13 +184,13 @@ private static final class Visitor extends SqlBaseBaseVisitor { private static final String DEFAULT_WINDOW_NAME = "StreamWindow"; - private final Set sources; + private final Optional> sources; private final SqlTypeParser typeParser; private boolean buildingPersistentQuery = false; - Visitor(final Set sources, final TypeRegistry typeRegistry) { - this.sources = ImmutableSet.copyOf(Objects.requireNonNull(sources, "sources")); + Visitor(final Optional> sources, final TypeRegistry typeRegistry) { + this.sources = Objects.requireNonNull(sources, "sources").map(ImmutableSet::copyOf); this.typeParser = SqlTypeParser.create(typeRegistry); } @@ -1088,7 +1100,7 @@ public Node visitRegisterType(final RegisterTypeContext context) { } private void throwOnUnknownNameOrAlias(final SourceName name) { - if (!sources.contains(name)) { + if (sources.isPresent() && !sources.get().contains(name)) { throw new KsqlException("'" + name.name() + "' is not a valid stream/table name or alias."); } } diff --git a/ksql-parser/src/main/java/io/confluent/ksql/parser/DefaultKsqlParser.java b/ksql-parser/src/main/java/io/confluent/ksql/parser/DefaultKsqlParser.java index ec088dc6abf..316ee1171df 100644 --- a/ksql-parser/src/main/java/io/confluent/ksql/parser/DefaultKsqlParser.java +++ b/ksql-parser/src/main/java/io/confluent/ksql/parser/DefaultKsqlParser.java @@ -72,7 +72,7 @@ public PreparedStatement prepare( ) { try { final AstBuilder astBuilder = new AstBuilder(typeRegistry); - final Statement root = astBuilder.build(stmt.getStatement()); + final Statement root = astBuilder.buildStatement(stmt.getStatement()); return PreparedStatement.of(stmt.getStatementText(), root); } catch (final ParseFailedException e) { diff --git a/ksql-parser/src/main/java/io/confluent/ksql/parser/ExpressionParser.java b/ksql-parser/src/main/java/io/confluent/ksql/parser/ExpressionParser.java new file mode 100644 index 00000000000..d7604952bce --- /dev/null +++ b/ksql-parser/src/main/java/io/confluent/ksql/parser/ExpressionParser.java @@ -0,0 +1,67 @@ +/* + * Copyright 2019 Confluent Inc. + * + * Licensed under the Confluent Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * http://www.confluent.io/confluent-community-license + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package io.confluent.ksql.parser; + +import io.confluent.ksql.execution.expression.tree.Expression; +import io.confluent.ksql.execution.plan.SelectExpression; +import io.confluent.ksql.execution.windows.KsqlWindowExpression; +import io.confluent.ksql.metastore.TypeRegistry; +import io.confluent.ksql.name.ColumnName; +import io.confluent.ksql.parser.tree.WindowExpression; +import io.confluent.ksql.util.ParserUtil; +import org.antlr.v4.runtime.ParserRuleContext; + +public final class ExpressionParser { + private ExpressionParser() { + } + + public static SelectExpression parseSelectExpression(final String expressionText) { + final SqlBaseParser.SelectItemContext parseCtx = GrammarParseUtil.getParseTree( + expressionText, + SqlBaseParser::selectItem + ); + if (!(parseCtx instanceof SqlBaseParser.SelectSingleContext)) { + throw new IllegalArgumentException("Illegal select item type in: " + expressionText); + } + final SqlBaseParser.SelectSingleContext selectSingleContext = + (SqlBaseParser.SelectSingleContext) parseCtx; + if (selectSingleContext.identifier() == null) { + throw new IllegalArgumentException("Select item must have identifier in: " + expressionText); + } + return SelectExpression.of( + ColumnName.of(ParserUtil.getIdentifierText(selectSingleContext.identifier())), + new AstBuilder(TypeRegistry.EMPTY).buildExpression(selectSingleContext.expression()) + ); + } + + public static Expression parseExpression(final String expressionText) { + final ParserRuleContext parseTree = GrammarParseUtil.getParseTree( + expressionText, + SqlBaseParser::singleExpression + ); + return new AstBuilder(TypeRegistry.EMPTY).buildExpression(parseTree); + } + + public static KsqlWindowExpression parseWindowExpression(final String expressionText) { + final ParserRuleContext parseTree = GrammarParseUtil.getParseTree( + expressionText, + SqlBaseParser::windowExpression + ); + final WindowExpression windowExpression = + new AstBuilder(TypeRegistry.EMPTY).buildWindowExpression(parseTree); + return windowExpression.getKsqlWindowExpression(); + } +} diff --git a/ksql-parser/src/main/java/io/confluent/ksql/parser/GrammarParseUtil.java b/ksql-parser/src/main/java/io/confluent/ksql/parser/GrammarParseUtil.java new file mode 100644 index 00000000000..688a636f60a --- /dev/null +++ b/ksql-parser/src/main/java/io/confluent/ksql/parser/GrammarParseUtil.java @@ -0,0 +1,78 @@ +/* + * Copyright 2019 Confluent Inc. + * + * Licensed under the Confluent Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * http://www.confluent.io/confluent-community-license + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package io.confluent.ksql.parser; + +import java.util.function.Function; +import org.antlr.v4.runtime.BaseErrorListener; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.RecognitionException; +import org.antlr.v4.runtime.Recognizer; +import org.antlr.v4.runtime.atn.PredictionMode; +import org.antlr.v4.runtime.misc.ParseCancellationException; + +final class GrammarParseUtil { + private static final BaseErrorListener ERROR_LISTENER = new BaseErrorListener() { + @Override + public void syntaxError( + final Recognizer recognizer, + final Object offendingSymbol, + final int line, + final int charPositionInLine, + final String message, + final RecognitionException e + ) { + throw new ParsingException(message, e, line, charPositionInLine); + } + }; + + private GrammarParseUtil() { + } + + static T getParseTree( + final String text, + final Function parseFunction) { + final SqlBaseLexer sqlBaseLexer = new SqlBaseLexer( + new CaseInsensitiveStream(CharStreams.fromString(text))); + final CommonTokenStream tokenStream = new CommonTokenStream(sqlBaseLexer); + final SqlBaseParser sqlBaseParser = new SqlBaseParser(tokenStream); + + sqlBaseLexer.removeErrorListeners(); + sqlBaseLexer.addErrorListener(ERROR_LISTENER); + + sqlBaseParser.removeErrorListeners(); + sqlBaseParser.addErrorListener(ERROR_LISTENER); + + try { + // first, try parsing w/ potentially faster SLL mode + sqlBaseParser.getInterpreter().setPredictionMode(PredictionMode.SLL); + return castContext(parseFunction.apply(sqlBaseParser)); + } catch (final ParseCancellationException ex) { + // if we fail, parse with LL mode + tokenStream.seek(0); // rewind input stream + sqlBaseParser.reset(); + + sqlBaseParser.getInterpreter().setPredictionMode(PredictionMode.LL); + return castContext(parseFunction.apply(sqlBaseParser)); + } + } + + @SuppressWarnings("unchecked") + private static T castContext(final ParserRuleContext ctx) { + return (T) ctx; + } +} diff --git a/ksql-parser/src/main/java/io/confluent/ksql/parser/ParsingException.java b/ksql-parser/src/main/java/io/confluent/ksql/parser/ParsingException.java index 0530484fe2d..d0c278c16a0 100644 --- a/ksql-parser/src/main/java/io/confluent/ksql/parser/ParsingException.java +++ b/ksql-parser/src/main/java/io/confluent/ksql/parser/ParsingException.java @@ -35,7 +35,7 @@ public ParsingException(final String message, final Optional nodeL ); } - ParsingException( + public ParsingException( final String message, final RecognitionException cause, final int line, diff --git a/ksql-parser/src/main/java/io/confluent/ksql/parser/json/ColumnRefDeserializer.java b/ksql-parser/src/main/java/io/confluent/ksql/parser/json/ColumnRefDeserializer.java new file mode 100644 index 00000000000..0a9ce126f89 --- /dev/null +++ b/ksql-parser/src/main/java/io/confluent/ksql/parser/json/ColumnRefDeserializer.java @@ -0,0 +1,38 @@ +/* + * Copyright 2019 Confluent Inc. + * + * Licensed under the Confluent Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * http://www.confluent.io/confluent-community-license + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package io.confluent.ksql.parser.json; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import io.confluent.ksql.execution.expression.tree.ColumnReferenceExp; +import io.confluent.ksql.execution.expression.tree.Expression; +import io.confluent.ksql.parser.ExpressionParser; +import io.confluent.ksql.schema.ksql.ColumnRef; +import java.io.IOException; + +class ColumnRefDeserializer extends JsonDeserializer { + @Override + public ColumnRef deserialize(final JsonParser parser, final DeserializationContext ctx) + throws IOException { + final Expression expression + = ExpressionParser.parseExpression(parser.readValueAs(String.class)); + if (expression instanceof ColumnReferenceExp) { + return ((ColumnReferenceExp) expression).getReference(); + } + throw new IllegalArgumentException("Passed JSON is not a column reference: " + expression); + } +} diff --git a/ksql-parser/src/main/java/io/confluent/ksql/parser/json/ColumnRefSerializer.java b/ksql-parser/src/main/java/io/confluent/ksql/parser/json/ColumnRefSerializer.java new file mode 100644 index 00000000000..f34168163a2 --- /dev/null +++ b/ksql-parser/src/main/java/io/confluent/ksql/parser/json/ColumnRefSerializer.java @@ -0,0 +1,34 @@ +/* + * Copyright 2019 Confluent Inc. + * + * Licensed under the Confluent Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * http://www.confluent.io/confluent-community-license + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package io.confluent.ksql.parser.json; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import io.confluent.ksql.schema.ksql.ColumnRef; +import io.confluent.ksql.schema.ksql.FormatOptions; +import io.confluent.ksql.util.IdentifierUtil; +import java.io.IOException; + +class ColumnRefSerializer extends JsonSerializer { + @Override + public void serialize( + final ColumnRef columnRef, + final JsonGenerator gen, + final SerializerProvider provider) throws IOException { + gen.writeString(columnRef.toString(FormatOptions.of(IdentifierUtil::needsQuotes))); + } +} diff --git a/ksql-parser/src/main/java/io/confluent/ksql/parser/json/ExpressionDeserializer.java b/ksql-parser/src/main/java/io/confluent/ksql/parser/json/ExpressionDeserializer.java new file mode 100644 index 00000000000..a6ae87bbdff --- /dev/null +++ b/ksql-parser/src/main/java/io/confluent/ksql/parser/json/ExpressionDeserializer.java @@ -0,0 +1,32 @@ +/* + * Copyright 2019 Confluent Inc. + * + * Licensed under the Confluent Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * http://www.confluent.io/confluent-community-license + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package io.confluent.ksql.parser.json; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import io.confluent.ksql.execution.expression.tree.Expression; +import io.confluent.ksql.parser.ExpressionParser; +import java.io.IOException; + +class ExpressionDeserializer extends JsonDeserializer { + @Override + @SuppressWarnings("unchecked") + public T deserialize(final JsonParser parser, final DeserializationContext ctx) + throws IOException { + return (T) ExpressionParser.parseExpression(parser.readValueAs(String.class)); + } +} diff --git a/ksql-parser/src/main/java/io/confluent/ksql/parser/json/ExpressionSerializer.java b/ksql-parser/src/main/java/io/confluent/ksql/parser/json/ExpressionSerializer.java new file mode 100644 index 00000000000..e33bd859ec9 --- /dev/null +++ b/ksql-parser/src/main/java/io/confluent/ksql/parser/json/ExpressionSerializer.java @@ -0,0 +1,40 @@ +/* + * Copyright 2019 Confluent Inc. + * + * Licensed under the Confluent Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * http://www.confluent.io/confluent-community-license + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package io.confluent.ksql.parser.json; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import io.confluent.ksql.execution.expression.formatter.ExpressionFormatter; +import io.confluent.ksql.execution.expression.tree.Expression; +import io.confluent.ksql.schema.ksql.FormatOptions; +import io.confluent.ksql.util.IdentifierUtil; +import java.io.IOException; + +class ExpressionSerializer extends JsonSerializer { + @Override + public void serialize( + final Expression expression, + final JsonGenerator jsonGenerator, + final SerializerProvider serializerProvider + ) throws IOException { + jsonGenerator.writeObject( + ExpressionFormatter.formatExpression( + expression, + FormatOptions.of(IdentifierUtil::needsQuotes)) + ); + } +} diff --git a/ksql-parser/src/main/java/io/confluent/ksql/parser/json/KsqlParserSerializationModule.java b/ksql-parser/src/main/java/io/confluent/ksql/parser/json/KsqlParserSerializationModule.java new file mode 100644 index 00000000000..1270ae3d801 --- /dev/null +++ b/ksql-parser/src/main/java/io/confluent/ksql/parser/json/KsqlParserSerializationModule.java @@ -0,0 +1,40 @@ +/* + * Copyright 2019 Confluent Inc. + * + * Licensed under the Confluent Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * http://www.confluent.io/confluent-community-license + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package io.confluent.ksql.parser.json; + +import com.fasterxml.jackson.databind.module.SimpleModule; +import io.confluent.ksql.execution.expression.tree.Expression; +import io.confluent.ksql.execution.expression.tree.FunctionCall; +import io.confluent.ksql.execution.plan.SelectExpression; +import io.confluent.ksql.execution.windows.KsqlWindowExpression; +import io.confluent.ksql.schema.ksql.ColumnRef; + +// CHECKSTYLE_RULES.OFF: ClassDataAbstractionCoupling +public class KsqlParserSerializationModule extends SimpleModule { + // CHECKSTYLE_RULES.ON: ClassDataAbstractionCoupling + public KsqlParserSerializationModule() { + super(); + addSerializer(Expression.class, new ExpressionSerializer()); + addDeserializer(Expression.class, new ExpressionDeserializer<>()); + addDeserializer(FunctionCall.class, new ExpressionDeserializer<>()); + addSerializer(ColumnRef.class, new ColumnRefSerializer()); + addDeserializer(ColumnRef.class, new ColumnRefDeserializer()); + addSerializer(SelectExpression.class, new SelectExpressionSerializer()); + addDeserializer(SelectExpression.class, new SelectExpressionDeserializer()); + addSerializer(KsqlWindowExpression.class, new KsqlWindowExpressionSerializer()); + addDeserializer(KsqlWindowExpression.class, new WindowExpressionDeserializer<>()); + } +} diff --git a/ksql-parser/src/main/java/io/confluent/ksql/parser/json/KsqlWindowExpressionSerializer.java b/ksql-parser/src/main/java/io/confluent/ksql/parser/json/KsqlWindowExpressionSerializer.java new file mode 100644 index 00000000000..2e184b1082f --- /dev/null +++ b/ksql-parser/src/main/java/io/confluent/ksql/parser/json/KsqlWindowExpressionSerializer.java @@ -0,0 +1,33 @@ +/* + * Copyright 2019 Confluent Inc. + * + * Licensed under the Confluent Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * http://www.confluent.io/confluent-community-license + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package io.confluent.ksql.parser.json; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import io.confluent.ksql.execution.windows.KsqlWindowExpression; +import java.io.IOException; + +class KsqlWindowExpressionSerializer extends JsonSerializer { + @Override + public void serialize( + final KsqlWindowExpression expression, + final JsonGenerator jsonGenerator, + final SerializerProvider serializerProvider + ) throws IOException { + jsonGenerator.writeObject(expression.toString()); + } +} diff --git a/ksql-parser/src/main/java/io/confluent/ksql/parser/json/SelectExpressionDeserializer.java b/ksql-parser/src/main/java/io/confluent/ksql/parser/json/SelectExpressionDeserializer.java new file mode 100644 index 00000000000..2d69743e721 --- /dev/null +++ b/ksql-parser/src/main/java/io/confluent/ksql/parser/json/SelectExpressionDeserializer.java @@ -0,0 +1,32 @@ +/* + * Copyright 2019 Confluent Inc. + * + * Licensed under the Confluent Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * http://www.confluent.io/confluent-community-license + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package io.confluent.ksql.parser.json; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import io.confluent.ksql.execution.plan.SelectExpression; +import io.confluent.ksql.parser.ExpressionParser; +import java.io.IOException; + +class SelectExpressionDeserializer extends JsonDeserializer { + @Override + @SuppressWarnings("unchecked") + public SelectExpression deserialize(final JsonParser parser, final DeserializationContext ctx) + throws IOException { + return ExpressionParser.parseSelectExpression(parser.readValueAs(String.class)); + } +} diff --git a/ksql-parser/src/main/java/io/confluent/ksql/parser/json/SelectExpressionSerializer.java b/ksql-parser/src/main/java/io/confluent/ksql/parser/json/SelectExpressionSerializer.java new file mode 100644 index 00000000000..e41743655d3 --- /dev/null +++ b/ksql-parser/src/main/java/io/confluent/ksql/parser/json/SelectExpressionSerializer.java @@ -0,0 +1,37 @@ +/* + * Copyright 2019 Confluent Inc. + * + * Licensed under the Confluent Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * http://www.confluent.io/confluent-community-license + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package io.confluent.ksql.parser.json; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import io.confluent.ksql.execution.plan.SelectExpression; +import io.confluent.ksql.schema.ksql.FormatOptions; +import io.confluent.ksql.util.IdentifierUtil; +import java.io.IOException; + +class SelectExpressionSerializer extends JsonSerializer { + @Override + public void serialize( + final SelectExpression selectExpression, + final JsonGenerator jsonGenerator, + final SerializerProvider serializerProvider + ) throws IOException { + jsonGenerator.writeObject( + selectExpression.format(FormatOptions.of(IdentifierUtil::needsQuotes)) + ); + } +} diff --git a/ksql-parser/src/main/java/io/confluent/ksql/parser/json/WindowExpressionDeserializer.java b/ksql-parser/src/main/java/io/confluent/ksql/parser/json/WindowExpressionDeserializer.java new file mode 100644 index 00000000000..2d4a645c335 --- /dev/null +++ b/ksql-parser/src/main/java/io/confluent/ksql/parser/json/WindowExpressionDeserializer.java @@ -0,0 +1,32 @@ +/* + * Copyright 2019 Confluent Inc. + * + * Licensed under the Confluent Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * http://www.confluent.io/confluent-community-license + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package io.confluent.ksql.parser.json; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import io.confluent.ksql.execution.windows.KsqlWindowExpression; +import io.confluent.ksql.parser.ExpressionParser; +import java.io.IOException; + +class WindowExpressionDeserializer extends JsonDeserializer { + @Override + @SuppressWarnings("unchecked") + public T deserialize(final JsonParser parser, final DeserializationContext ctx) + throws IOException { + return (T) ExpressionParser.parseWindowExpression(parser.readValueAs(String.class)); + } +} diff --git a/ksql-parser/src/main/java/io/confluent/ksql/util/IdentifierUtil.java b/ksql-parser/src/main/java/io/confluent/ksql/util/IdentifierUtil.java index e1f99572fd9..eb9fb72cb19 100644 --- a/ksql-parser/src/main/java/io/confluent/ksql/util/IdentifierUtil.java +++ b/ksql-parser/src/main/java/io/confluent/ksql/util/IdentifierUtil.java @@ -58,5 +58,4 @@ public static boolean needsQuotes(final String identifier) { private static boolean upperCase(final String identifier) { return identifier.toUpperCase().equals(identifier); } - } diff --git a/ksql-parser/src/test/java/io/confluent/ksql/parser/AstBuilderTest.java b/ksql-parser/src/test/java/io/confluent/ksql/parser/AstBuilderTest.java index b7f61321a87..b48456e8008 100644 --- a/ksql-parser/src/test/java/io/confluent/ksql/parser/AstBuilderTest.java +++ b/ksql-parser/src/test/java/io/confluent/ksql/parser/AstBuilderTest.java @@ -17,7 +17,6 @@ import static io.confluent.ksql.parser.tree.JoinMatchers.hasLeft; import static io.confluent.ksql.parser.tree.JoinMatchers.hasRight; -import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.instanceOf; @@ -34,22 +33,18 @@ import io.confluent.ksql.parser.SqlBaseParser.SingleStatementContext; import io.confluent.ksql.parser.tree.AliasedRelation; import io.confluent.ksql.parser.tree.AllColumns; -import io.confluent.ksql.parser.tree.AstNode; -import io.confluent.ksql.parser.tree.CreateStreamAsSelect; import io.confluent.ksql.parser.tree.Join; import io.confluent.ksql.parser.tree.Query; import io.confluent.ksql.parser.tree.QueryContainer; import io.confluent.ksql.parser.tree.ResultMaterialization; import io.confluent.ksql.parser.tree.Select; import io.confluent.ksql.parser.tree.SingleColumn; -import io.confluent.ksql.parser.tree.Statement; import io.confluent.ksql.parser.tree.Table; import io.confluent.ksql.schema.ksql.ColumnRef; import io.confluent.ksql.util.KsqlException; import io.confluent.ksql.util.MetaStoreFixture; import java.util.List; import java.util.Optional; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -81,7 +76,7 @@ public void shouldExtractUnaliasedDataSources() { final SingleStatementContext stmt = givenQuery("SELECT * FROM TEST1;"); // When: - final Query result = (Query) builder.build(stmt); + final Query result = (Query) builder.buildStatement(stmt); // Then: assertThat(result.getFrom(), is(new AliasedRelation(TEST1, TEST1_NAME))); @@ -93,7 +88,7 @@ public void shouldHandleAliasedDataSources() { final SingleStatementContext stmt = givenQuery("SELECT * FROM TEST1 t;"); // When: - final Query result = (Query) builder.build(stmt); + final Query result = (Query) builder.buildStatement(stmt); // Then: assertThat(result.getFrom(), is(new AliasedRelation(TEST1, SourceName.of("T")))); @@ -105,7 +100,7 @@ public void shouldExtractAsAliasedDataSources() { final SingleStatementContext stmt = givenQuery("SELECT * FROM TEST1 AS t;"); // When: - final Query result = (Query) builder.build(stmt); + final Query result = (Query) builder.buildStatement(stmt); // Then: assertThat(result.getFrom(), is(new AliasedRelation(TEST1, SourceName.of("T")))); @@ -118,7 +113,7 @@ public void shouldExtractUnaliasedJoinDataSources() { + " ON test1.col1 = test2.col1;"); // When: - final Query result = (Query) builder.build(stmt); + final Query result = (Query) builder.buildStatement(stmt); // Then: assertThat(result.getFrom(), is(instanceOf(Join.class))); @@ -133,7 +128,7 @@ public void shouldHandleAliasedJoinDataSources() { + " ON test1.col1 = test2.col1;"); // When: - final Query result = (Query) builder.build(stmt); + final Query result = (Query) builder.buildStatement(stmt); // Then: assertThat(result.getFrom(), is(instanceOf(Join.class))); @@ -148,7 +143,7 @@ public void shouldExtractAsAliasedJoinDataSources() { + " ON t1.col1 = t2.col1;"); // When: - final Query result = (Query) builder.build(stmt); + final Query result = (Query) builder.buildStatement(stmt); // Then: assertThat(result.getFrom(), is(instanceOf(Join.class))); @@ -162,7 +157,7 @@ public void shouldHandleUnqualifiedSelect() { final SingleStatementContext stmt = givenQuery("SELECT COL0 FROM TEST1;"); // When: - final Query result = (Query) builder.build(stmt); + final Query result = (Query) builder.buildStatement(stmt); // Then: assertThat(result.getSelect(), is(new Select(ImmutableList.of( @@ -176,7 +171,7 @@ public void shouldHandleQualifiedSelect() { final SingleStatementContext stmt = givenQuery("SELECT TEST1.COL0 FROM TEST1;"); // When: - final Query result = (Query) builder.build(stmt); + final Query result = (Query) builder.buildStatement(stmt); // Then: assertThat(result.getSelect(), is(new Select(ImmutableList.of( @@ -191,7 +186,7 @@ public void shouldHandleAliasQualifiedSelect() { final SingleStatementContext stmt = givenQuery("SELECT T.COL0 FROM TEST2 T;"); // When: - final Query result = (Query) builder.build(stmt); + final Query result = (Query) builder.buildStatement(stmt); // Then: assertThat(result.getSelect(), is(new Select(ImmutableList.of( @@ -210,7 +205,7 @@ public void shouldThrowOnUnknownSelectQualifier() { expectedException.expectMessage("'UNKNOWN' is not a valid stream/table name or alias."); // When: - builder.build(stmt); + builder.buildStatement(stmt); } @Test @@ -219,7 +214,7 @@ public void shouldOmitSelectAliasIfNotPresent() { final SingleStatementContext stmt = givenQuery("SELECT COL0 FROM TEST1;"); // When: - final Query result = (Query) builder.build(stmt); + final Query result = (Query) builder.buildStatement(stmt); // Then: assertThat(result.getSelect(), is(new Select(ImmutableList.of( @@ -234,7 +229,7 @@ public void shouldIncludeSelectAliasIfPresent() { final SingleStatementContext stmt = givenQuery("SELECT COL0 AS FOO FROM TEST1;"); // When: - final Query result = (Query) builder.build(stmt); + final Query result = (Query) builder.buildStatement(stmt); // Then: assertThat(result.getSelect(), is(new Select(ImmutableList.of( @@ -249,7 +244,7 @@ public void shouldHandleUnqualifiedSelectStar() { final SingleStatementContext stmt = givenQuery("SELECT * FROM TEST1;"); // When: - final Query result = (Query) builder.build(stmt); + final Query result = (Query) builder.buildStatement(stmt); // Then: assertThat(result.getSelect(), @@ -262,7 +257,7 @@ public void shouldHandleQualifiedSelectStar() { final SingleStatementContext stmt = givenQuery("SELECT TEST1.* FROM TEST1;"); // When: - final Query result = (Query) builder.build(stmt); + final Query result = (Query) builder.buildStatement(stmt); // Then: assertThat(result.getSelect(), @@ -275,7 +270,7 @@ public void shouldHandleAliasQualifiedSelectStar() { final SingleStatementContext stmt = givenQuery("SELECT T.* FROM TEST1 T;"); // When: - final Query result = (Query) builder.build(stmt); + final Query result = (Query) builder.buildStatement(stmt); // Then: assertThat(result.getSelect(), @@ -292,7 +287,7 @@ public void shouldThrowOnUnknownStarAlias() { expectedException.expectMessage("'UNKNOWN' is not a valid stream/table name or alias."); // When: - builder.build(stmt); + builder.buildStatement(stmt); } @Test @@ -302,7 +297,7 @@ public void shouldHandleUnqualifiedSelectStarOnJoin() { givenQuery("SELECT * FROM TEST1 JOIN TEST2 WITHIN 1 SECOND ON TEST1.ID = TEST2.ID;"); // When: - final Query result = (Query) builder.build(stmt); + final Query result = (Query) builder.buildStatement(stmt); // Then: assertThat(result.getSelect(), @@ -316,7 +311,7 @@ public void shouldHandleQualifiedSelectStarOnLeftJoinSource() { givenQuery("SELECT TEST1.* FROM TEST1 JOIN TEST2 WITHIN 1 SECOND ON TEST1.ID = TEST2.ID;"); // When: - final Query result = (Query) builder.build(stmt); + final Query result = (Query) builder.buildStatement(stmt); // Then: assertThat(result.getSelect(), @@ -330,7 +325,7 @@ public void shouldHandleQualifiedSelectStarOnRightJoinSource() { givenQuery("SELECT TEST2.* FROM TEST1 JOIN TEST2 WITHIN 1 SECOND ON TEST1.ID = TEST2.ID;"); // When: - final Query result = (Query) builder.build(stmt); + final Query result = (Query) builder.buildStatement(stmt); // Then: assertThat(result.getSelect(), @@ -344,7 +339,7 @@ public void shouldHandleAliasQualifiedSelectStarOnLeftJoinSource() { givenQuery("SELECT T1.* FROM TEST1 T1 JOIN TEST2 WITHIN 1 SECOND ON T1.ID = TEST2.ID;"); // When: - final Query result = (Query) builder.build(stmt); + final Query result = (Query) builder.buildStatement(stmt); // Then: assertThat(result.getSelect(), @@ -358,7 +353,7 @@ public void shouldHandleAliasQualifiedSelectStarOnRightJoinSource() { givenQuery("SELECT T2.* FROM TEST1 JOIN TEST2 T2 WITHIN 1 SECOND ON TEST1.ID = T2.ID;"); // When: - final Query result = (Query) builder.build(stmt); + final Query result = (Query) builder.buildStatement(stmt); // Then: assertThat(result.getSelect(), @@ -376,7 +371,7 @@ public void shouldThrowOnUnknownStarAliasOnJoin() { expectedException.expectMessage("'UNKNOWN' is not a valid stream/table name or alias."); // When: - builder.build(stmt); + builder.buildStatement(stmt); } @Test @@ -386,7 +381,7 @@ public void shouldDefaultToYieldFinalForBareQueries() { givenQuery("SELECT * FROM TEST1;"); // When: - final Query result = (Query) builder.build(stmt); + final Query result = (Query) builder.buildStatement(stmt); // Then: assertThat("Should be static", result.isStatic(), is(true)); @@ -400,7 +395,7 @@ public void shouldSupportExplicitEmitChangesOnBareQuery() { givenQuery("SELECT * FROM TEST1 EMIT CHANGES;"); // When: - final Query result = (Query) builder.build(stmt); + final Query result = (Query) builder.buildStatement(stmt); // Then: assertThat("Should be continuous", result.isStatic(), is(false)); @@ -415,7 +410,7 @@ public void shouldDefaultToEmitChangesForCsas() { givenQuery("CREATE STREAM X AS SELECT * FROM TEST1;"); // When: - final Query result = ((QueryContainer) builder.build(stmt)).getQuery(); + final Query result = ((QueryContainer) builder.buildStatement(stmt)).getQuery(); // Then: assertThat("Should be continuous", result.isStatic(), is(false)); @@ -429,7 +424,7 @@ public void shouldDefaultToEmitChangesForCtas() { givenQuery("CREATE TABLE X AS SELECT COUNT(1) FROM TEST1 GROUP BY ROWKEY;"); // When: - final Query result = ((QueryContainer) builder.build(stmt)).getQuery(); + final Query result = ((QueryContainer) builder.buildStatement(stmt)).getQuery(); // Then: assertThat("Should be continuous", result.isStatic(), is(false)); @@ -443,7 +438,7 @@ public void shouldDefaultToEmitChangesForInsertInto() { givenQuery("INSERT INTO TEST1 SELECT * FROM TEST2;"); // When: - final Query result = ((QueryContainer) builder.build(stmt)).getQuery(); + final Query result = ((QueryContainer) builder.buildStatement(stmt)).getQuery(); // Then: assertThat("Should be continuous", result.isStatic(), is(false)); @@ -457,7 +452,7 @@ public void shouldSupportExplicitEmitChangesForCsas() { givenQuery("CREATE STREAM X AS SELECT * FROM TEST1 EMIT CHANGES;"); // When: - final Query result = ((QueryContainer) builder.build(stmt)).getQuery(); + final Query result = ((QueryContainer) builder.buildStatement(stmt)).getQuery(); // Then: assertThat("Should be continuous", result.isStatic(), is(false)); @@ -471,7 +466,7 @@ public void shouldSupportExplicitEmitChangesForCtas() { givenQuery("CREATE TABLE X AS SELECT COUNT(1) FROM TEST1 GROUP BY ROWKEY EMIT CHANGES;"); // When: - final Query result = ((QueryContainer) builder.build(stmt)).getQuery(); + final Query result = ((QueryContainer) builder.buildStatement(stmt)).getQuery(); // Then: assertThat("Should be continuous", result.isStatic(), is(false)); @@ -484,7 +479,7 @@ public void shouldSupportExplicitEmitChangesForInsertInto() { givenQuery("INSERT INTO TEST1 SELECT * FROM TEST2 EMIT CHANGES;"); // When: - final Query result = ((QueryContainer) builder.build(stmt)).getQuery(); + final Query result = ((QueryContainer) builder.buildStatement(stmt)).getQuery(); // Then: assertThat("Should be continuous", result.isStatic(), is(false)); diff --git a/ksql-parser/src/test/java/io/confluent/ksql/parser/ExpressionParserTest.java b/ksql-parser/src/test/java/io/confluent/ksql/parser/ExpressionParserTest.java new file mode 100644 index 00000000000..25d1d3ba7ca --- /dev/null +++ b/ksql-parser/src/test/java/io/confluent/ksql/parser/ExpressionParserTest.java @@ -0,0 +1,106 @@ +/* + * Copyright 2019 Confluent Inc. + * + * Licensed under the Confluent Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * http://www.confluent.io/confluent-community-license + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package io.confluent.ksql.parser; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + +import io.confluent.ksql.execution.expression.tree.ArithmeticBinaryExpression; +import io.confluent.ksql.execution.expression.tree.Expression; +import io.confluent.ksql.execution.expression.tree.IntegerLiteral; +import io.confluent.ksql.execution.plan.SelectExpression; +import io.confluent.ksql.execution.windows.KsqlWindowExpression; +import io.confluent.ksql.execution.windows.TumblingWindowExpression; +import io.confluent.ksql.name.ColumnName; +import io.confluent.ksql.schema.Operator; +import java.util.concurrent.TimeUnit; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +public class ExpressionParserTest { + private static final IntegerLiteral ONE = new IntegerLiteral(1); + private static final IntegerLiteral TWO = new IntegerLiteral(2); + + @Rule + public final ExpectedException expectedException = ExpectedException.none(); + + @Test + public void shouldParseExpression() { + // When: + final Expression parsed = ExpressionParser.parseExpression("1 + 2"); + + // Then: + assertThat( + parsed, + equalTo(new ArithmeticBinaryExpression(parsed.getLocation(), Operator.ADD, ONE, TWO)) + ); + } + + @Test + public void shouldParseSelectExpression() { + // When: + final SelectExpression parsed = + ExpressionParser.parseSelectExpression("1 + 2 AS `three`"); + + // Then: + assertThat( + parsed, + equalTo( + SelectExpression.of( + ColumnName.of("three"), + new ArithmeticBinaryExpression( + parsed.getExpression().getLocation(), Operator.ADD, ONE, TWO + ) + ) + ) + ); + } + + @Test + public void shouldThrowOnSelectExpressionWithoutAlias() { + // Then: + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Select item must have identifier in: 1 + 2"); + + // When: + ExpressionParser.parseSelectExpression("1 + 2"); + } + + @Test + public void shouldThrowOnAllColumns() { + // Then: + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Illegal select item type in: *"); + + // When: + ExpressionParser.parseSelectExpression("*"); + } + + @Test + public void shouldParseWindowExpression() { + // When: + final KsqlWindowExpression parsed = ExpressionParser.parseWindowExpression( + "TUMBLING (SIZE 1 DAYS)" + ); + + // Then: + assertThat( + parsed, + equalTo(new TumblingWindowExpression(parsed.getLocation(), 1, TimeUnit.DAYS)) + ); + } +} \ No newline at end of file diff --git a/ksql-parser/src/test/java/io/confluent/ksql/parser/json/ColumnRefDeserializerTest.java b/ksql-parser/src/test/java/io/confluent/ksql/parser/json/ColumnRefDeserializerTest.java new file mode 100644 index 00000000000..5376620244d --- /dev/null +++ b/ksql-parser/src/test/java/io/confluent/ksql/parser/json/ColumnRefDeserializerTest.java @@ -0,0 +1,57 @@ +/* + * Copyright 2019 Confluent Inc. + * + * Licensed under the Confluent Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * http://www.confluent.io/confluent-community-license + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package io.confluent.ksql.parser.json; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; +import static io.confluent.ksql.parser.json.ColumnRefTestCase.*; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.confluent.ksql.schema.ksql.ColumnRef; +import java.io.IOException; +import org.junit.Test; + +public class ColumnRefDeserializerTest { + private static final ObjectMapper MAPPER = new ObjectMapper(); + + static { + MAPPER.registerModule(new KsqlParserSerializationModule()); + } + + @Test + public void shouldDeserializeColumnRef() throws IOException { + assertThat( + MAPPER.readValue(COLUMN_REF_TXT, ColumnRef.class), + equalTo(COLUMN_REF) + ); + } + + @Test + public void shouldDeserializeColumnRefWithNoSource() throws IOException { + assertThat( + MAPPER.readValue(COLUMN_REF_NO_SOURCE_TXT, ColumnRef.class), + equalTo(COLUMN_REF_NO_SOURCE) + ); + } + + @Test + public void shouldDeserializeColumnRefThatNeedsQuotes() throws IOException { + assertThat( + MAPPER.readValue(COLUMN_REF_NEEDS_QUOTES_TXT, ColumnRef.class), + equalTo(COLUMN_REF_NEEDS_QUOTES) + ); + } +} \ No newline at end of file diff --git a/ksql-parser/src/test/java/io/confluent/ksql/parser/json/ColumnRefSerializerTest.java b/ksql-parser/src/test/java/io/confluent/ksql/parser/json/ColumnRefSerializerTest.java new file mode 100644 index 00000000000..c36aa667299 --- /dev/null +++ b/ksql-parser/src/test/java/io/confluent/ksql/parser/json/ColumnRefSerializerTest.java @@ -0,0 +1,51 @@ +/* + * Copyright 2019 Confluent Inc. + * + * Licensed under the Confluent Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * http://www.confluent.io/confluent-community-license + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package io.confluent.ksql.parser.json; + +import static io.confluent.ksql.parser.json.ColumnRefTestCase.*; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; +import org.junit.Test; + +public class ColumnRefSerializerTest { + private static final ObjectMapper MAPPER = new ObjectMapper(); + + static { + MAPPER.registerModule(new KsqlParserSerializationModule()); + } + + @Test + public void shouldSerializeColumnRef() throws IOException { + assertThat(MAPPER.writeValueAsString(COLUMN_REF), equalTo(COLUMN_REF_TXT)); + } + + @Test + public void shouldSerializeColumnRefWithNoSource() throws IOException { + assertThat(MAPPER.writeValueAsString(COLUMN_REF_NO_SOURCE), equalTo(COLUMN_REF_NO_SOURCE_TXT)); + } + + @Test + public void shouldSerializeColumnRefThatNeedsQuotes() throws IOException { + assertThat( + MAPPER.writeValueAsString(COLUMN_REF_NEEDS_QUOTES), + equalTo(COLUMN_REF_NEEDS_QUOTES_TXT) + ); + } +} \ No newline at end of file diff --git a/ksql-parser/src/test/java/io/confluent/ksql/parser/json/ColumnRefTestCase.java b/ksql-parser/src/test/java/io/confluent/ksql/parser/json/ColumnRefTestCase.java new file mode 100644 index 00000000000..f1634c50a6f --- /dev/null +++ b/ksql-parser/src/test/java/io/confluent/ksql/parser/json/ColumnRefTestCase.java @@ -0,0 +1,32 @@ +/* + * Copyright 2019 Confluent Inc. + * + * Licensed under the Confluent Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * http://www.confluent.io/confluent-community-license + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package io.confluent.ksql.parser.json; + +import io.confluent.ksql.name.ColumnName; +import io.confluent.ksql.name.SourceName; +import io.confluent.ksql.schema.ksql.ColumnRef; + +final class ColumnRefTestCase { + static final ColumnRef COLUMN_REF = ColumnRef.of(SourceName.of("SOURCE"), ColumnName.of("COL")); + static final String COLUMN_REF_TXT = "\"SOURCE.COL\""; + + static final ColumnRef COLUMN_REF_NO_SOURCE = ColumnRef.withoutSource(ColumnName.of("COL")); + static final String COLUMN_REF_NO_SOURCE_TXT = "\"COL\""; + + static final ColumnRef COLUMN_REF_NEEDS_QUOTES = + ColumnRef.of(SourceName.of("STREAM"), ColumnName.of("foo")); + static final String COLUMN_REF_NEEDS_QUOTES_TXT = "\"`STREAM`.`foo`\""; +} diff --git a/ksql-parser/src/test/java/io/confluent/ksql/parser/json/ExpressionDeserializerTest.java b/ksql-parser/src/test/java/io/confluent/ksql/parser/json/ExpressionDeserializerTest.java new file mode 100644 index 00000000000..6ea262df85c --- /dev/null +++ b/ksql-parser/src/test/java/io/confluent/ksql/parser/json/ExpressionDeserializerTest.java @@ -0,0 +1,48 @@ +/* + * Copyright 2019 Confluent Inc. + * + * Licensed under the Confluent Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * http://www.confluent.io/confluent-community-license + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package io.confluent.ksql.parser.json; + +import static io.confluent.ksql.parser.json.ExpressionTestCase.EXPRESSION; +import static io.confluent.ksql.parser.json.ExpressionTestCase.EXPRESSION_NEEDS_QUOTES; +import static io.confluent.ksql.parser.json.ExpressionTestCase.EXPRESSION_NEEDS_QUOTES_TXT; +import static io.confluent.ksql.parser.json.ExpressionTestCase.EXPRESSION_TXT; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.*; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.confluent.ksql.execution.expression.tree.Expression; +import java.io.IOException; +import org.junit.Test; + +public class ExpressionDeserializerTest { + private static final ObjectMapper MAPPER = new ObjectMapper(); + + static { + MAPPER.registerModule(new KsqlParserSerializationModule()); + } + @Test + public void shouldDeserializeExpression() throws IOException { + assertThat(MAPPER.readValue(EXPRESSION_TXT, Expression.class), equalTo(EXPRESSION)); + } + + @Test + public void shouldDeserializeExpressionNeedingQuotes() throws IOException { + assertThat( + MAPPER.readValue(EXPRESSION_NEEDS_QUOTES_TXT, Expression.class), + equalTo(EXPRESSION_NEEDS_QUOTES) + ); + } +} \ No newline at end of file diff --git a/ksql-parser/src/test/java/io/confluent/ksql/parser/json/ExpressionSerializerTest.java b/ksql-parser/src/test/java/io/confluent/ksql/parser/json/ExpressionSerializerTest.java new file mode 100644 index 00000000000..75de154ade5 --- /dev/null +++ b/ksql-parser/src/test/java/io/confluent/ksql/parser/json/ExpressionSerializerTest.java @@ -0,0 +1,30 @@ +package io.confluent.ksql.parser.json; + +import static io.confluent.ksql.parser.json.ExpressionTestCase.*; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import org.junit.Test; + +public class ExpressionSerializerTest { + private static final ObjectMapper MAPPER = new ObjectMapper(); + + static { + MAPPER.registerModule(new KsqlParserSerializationModule()); + } + + @Test + public void shouldSerializeExpression() throws IOException { + assertThat(MAPPER.writeValueAsString(EXPRESSION), equalTo(EXPRESSION_TXT)); + } + + @Test + public void shouldSerializeExpressionNeedingQuotes() throws IOException { + assertThat( + MAPPER.writeValueAsString(EXPRESSION_NEEDS_QUOTES), + equalTo(EXPRESSION_NEEDS_QUOTES_TXT) + ); + } +} \ No newline at end of file diff --git a/ksql-parser/src/test/java/io/confluent/ksql/parser/json/ExpressionTestCase.java b/ksql-parser/src/test/java/io/confluent/ksql/parser/json/ExpressionTestCase.java new file mode 100644 index 00000000000..50915c84407 --- /dev/null +++ b/ksql-parser/src/test/java/io/confluent/ksql/parser/json/ExpressionTestCase.java @@ -0,0 +1,41 @@ +/* + * Copyright 2019 Confluent Inc. + * + * Licensed under the Confluent Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * http://www.confluent.io/confluent-community-license + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package io.confluent.ksql.parser.json; + +import io.confluent.ksql.execution.expression.tree.ArithmeticBinaryExpression; +import io.confluent.ksql.execution.expression.tree.ColumnReferenceExp; +import io.confluent.ksql.execution.expression.tree.DereferenceExpression; +import io.confluent.ksql.execution.expression.tree.Expression; +import io.confluent.ksql.execution.expression.tree.IntegerLiteral; +import io.confluent.ksql.name.ColumnName; +import io.confluent.ksql.name.SourceName; +import io.confluent.ksql.schema.Operator; +import io.confluent.ksql.schema.ksql.ColumnRef; +import java.util.Optional; + +public class ExpressionTestCase { + static final Expression EXPRESSION = new ArithmeticBinaryExpression( + Operator.ADD, new IntegerLiteral(1), new IntegerLiteral(2) + ); + static final String EXPRESSION_TXT = "\"(1 + 2)\""; + + static final Expression EXPRESSION_NEEDS_QUOTES = new DereferenceExpression( + Optional.empty(), + new ColumnReferenceExp(ColumnRef.of(SourceName.of("FOO"), ColumnName.of("STREAM"))), + "bar" + ); + static final String EXPRESSION_NEEDS_QUOTES_TXT = "\"FOO.`STREAM`->`bar`\""; +} diff --git a/ksql-parser/src/test/java/io/confluent/ksql/parser/json/SelectExpressionDeserializerTest.java b/ksql-parser/src/test/java/io/confluent/ksql/parser/json/SelectExpressionDeserializerTest.java new file mode 100644 index 00000000000..ae0fe3267f9 --- /dev/null +++ b/ksql-parser/src/test/java/io/confluent/ksql/parser/json/SelectExpressionDeserializerTest.java @@ -0,0 +1,42 @@ +package io.confluent.ksql.parser.json; + +import static io.confluent.ksql.parser.json.SelectExpressionTestCase.*; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.confluent.ksql.execution.plan.SelectExpression; +import java.io.IOException; +import org.junit.Test; + +public class SelectExpressionDeserializerTest { + private static final ObjectMapper MAPPER = new ObjectMapper(); + + static { + MAPPER.registerModule(new KsqlParserSerializationModule()); + } + + @Test + public void shouldDeserializeSelectExpression() throws IOException { + assertThat( + MAPPER.readValue(SELECT_EXPRESSION_TXT, SelectExpression.class), + equalTo(SELECT_EXPRESSION) + ); + } + + @Test + public void shouldDeserializeSelectExpressionNeedingQuotes() throws IOException { + assertThat( + MAPPER.readValue(SELECT_EXPRESSION_NEEDS_QUOTES_TXT, SelectExpression.class), + equalTo(SELECT_EXPRESSION_NEEDS_QUOTES) + ); + } + + @Test + public void shouldDeserializeSelectExpressionNeedingQuotesInName() throws IOException { + assertThat( + MAPPER.readValue(SELECT_EXPRESSION_NAME_NEEDS_QUOTES_TXT, SelectExpression.class), + equalTo(SELECT_EXPRESSION_NAME_NEEDS_QUOTES) + ); + } +} \ No newline at end of file diff --git a/ksql-parser/src/test/java/io/confluent/ksql/parser/json/SelectExpressionSerializerTest.java b/ksql-parser/src/test/java/io/confluent/ksql/parser/json/SelectExpressionSerializerTest.java new file mode 100644 index 00000000000..93505af4063 --- /dev/null +++ b/ksql-parser/src/test/java/io/confluent/ksql/parser/json/SelectExpressionSerializerTest.java @@ -0,0 +1,53 @@ +/* + * Copyright 2019 Confluent Inc. + * + * Licensed under the Confluent Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * http://www.confluent.io/confluent-community-license + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package io.confluent.ksql.parser.json; + +import static io.confluent.ksql.parser.json.SelectExpressionTestCase.*; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import org.junit.Test; + +public class SelectExpressionSerializerTest { + private static final ObjectMapper MAPPER = new ObjectMapper(); + + static { + MAPPER.registerModule(new KsqlParserSerializationModule()); + } + + @Test + public void shouldSerializeSelectExpression() throws IOException { + assertThat(MAPPER.writeValueAsString(SELECT_EXPRESSION), equalTo(SELECT_EXPRESSION_TXT)); + } + + @Test + public void shouldSerializeSelectExpressionNeedingQuotes() throws IOException{ + assertThat( + MAPPER.writeValueAsString(SELECT_EXPRESSION_NEEDS_QUOTES), + equalTo(SELECT_EXPRESSION_NEEDS_QUOTES_TXT) + ); + } + + @Test + public void shouldSerializeSelectExpressionNeedingQuotesInName() throws IOException{ + assertThat( + MAPPER.writeValueAsString(SELECT_EXPRESSION_NAME_NEEDS_QUOTES), + equalTo(SELECT_EXPRESSION_NAME_NEEDS_QUOTES_TXT) + ); + } +} \ No newline at end of file diff --git a/ksql-parser/src/test/java/io/confluent/ksql/parser/json/SelectExpressionTestCase.java b/ksql-parser/src/test/java/io/confluent/ksql/parser/json/SelectExpressionTestCase.java new file mode 100644 index 00000000000..d09094023d5 --- /dev/null +++ b/ksql-parser/src/test/java/io/confluent/ksql/parser/json/SelectExpressionTestCase.java @@ -0,0 +1,58 @@ +/* + * Copyright 2019 Confluent Inc. + * + * Licensed under the Confluent Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * http://www.confluent.io/confluent-community-license + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package io.confluent.ksql.parser.json; + +import io.confluent.ksql.execution.expression.tree.ArithmeticBinaryExpression; +import io.confluent.ksql.execution.expression.tree.ColumnReferenceExp; +import io.confluent.ksql.execution.expression.tree.DereferenceExpression; +import io.confluent.ksql.execution.expression.tree.IntegerLiteral; +import io.confluent.ksql.execution.plan.SelectExpression; +import io.confluent.ksql.name.ColumnName; +import io.confluent.ksql.name.SourceName; +import io.confluent.ksql.schema.Operator; +import io.confluent.ksql.schema.ksql.ColumnRef; +import java.util.Optional; + +public class SelectExpressionTestCase { + static final SelectExpression SELECT_EXPRESSION = SelectExpression.of( + ColumnName.of("FOO"), + new ArithmeticBinaryExpression( + Operator.ADD, + new IntegerLiteral(1), + new IntegerLiteral(2) + ) + ); + static final String SELECT_EXPRESSION_TXT = "\"(1 + 2) AS FOO\""; + static final SelectExpression SELECT_EXPRESSION_NEEDS_QUOTES = SelectExpression.of( + ColumnName.of("TEST"), + new DereferenceExpression( + Optional.empty(), + new ColumnReferenceExp(ColumnRef.of(SourceName.of("FOO"), ColumnName.of("STREAM"))), + "foo" + ) + ); + static final String SELECT_EXPRESSION_NEEDS_QUOTES_TXT = + "\"FOO.`STREAM`->`foo` AS TEST\""; + static final SelectExpression SELECT_EXPRESSION_NAME_NEEDS_QUOTES = SelectExpression.of( + ColumnName.of("STREAM"), + new ArithmeticBinaryExpression( + Operator.ADD, + new IntegerLiteral(1), + new IntegerLiteral(2) + ) + ); + static final String SELECT_EXPRESSION_NAME_NEEDS_QUOTES_TXT = "\"(1 + 2) AS `STREAM`\""; +} diff --git a/ksql-parser/src/test/java/io/confluent/ksql/parser/json/WindowExpressionDeserializerTest.java b/ksql-parser/src/test/java/io/confluent/ksql/parser/json/WindowExpressionDeserializerTest.java new file mode 100644 index 00000000000..d5f2a567dbc --- /dev/null +++ b/ksql-parser/src/test/java/io/confluent/ksql/parser/json/WindowExpressionDeserializerTest.java @@ -0,0 +1,42 @@ +/* + * Copyright 2019 Confluent Inc. + * + * Licensed under the Confluent Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * http://www.confluent.io/confluent-community-license + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package io.confluent.ksql.parser.json; + +import static io.confluent.ksql.parser.json.WindowExpressionTestCase.WINDOW_EXPRESSION; +import static io.confluent.ksql.parser.json.WindowExpressionTestCase.WINDOW_EXPRESSION_TXT; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.*; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.confluent.ksql.execution.windows.KsqlWindowExpression; +import java.io.IOException; +import org.junit.Test; + +public class WindowExpressionDeserializerTest { + private static final ObjectMapper MAPPER = new ObjectMapper(); + + static { + MAPPER.registerModule(new KsqlParserSerializationModule()); + } + + @Test + public void shouldDeserializeWindowExpression() throws IOException { + assertThat( + MAPPER.readValue(WINDOW_EXPRESSION_TXT, KsqlWindowExpression.class), + equalTo(WINDOW_EXPRESSION) + ); + } +} \ No newline at end of file diff --git a/ksql-parser/src/test/java/io/confluent/ksql/parser/json/WindowExpressionSerializerTest.java b/ksql-parser/src/test/java/io/confluent/ksql/parser/json/WindowExpressionSerializerTest.java new file mode 100644 index 00000000000..5eb5519b9e2 --- /dev/null +++ b/ksql-parser/src/test/java/io/confluent/ksql/parser/json/WindowExpressionSerializerTest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2019 Confluent Inc. + * + * Licensed under the Confluent Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * http://www.confluent.io/confluent-community-license + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package io.confluent.ksql.parser.json; + +import static io.confluent.ksql.parser.json.WindowExpressionTestCase.*; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import org.junit.Test; + +public class WindowExpressionSerializerTest { + private static final ObjectMapper MAPPER = new ObjectMapper(); + + static { + MAPPER.registerModule(new KsqlParserSerializationModule()); + } + + @Test + public void shouldSerializeWindowExpression() throws IOException { + assertThat(MAPPER.writeValueAsString(WINDOW_EXPRESSION), equalTo(WINDOW_EXPRESSION_TXT)); + } +} \ No newline at end of file diff --git a/ksql-parser/src/test/java/io/confluent/ksql/parser/json/WindowExpressionTestCase.java b/ksql-parser/src/test/java/io/confluent/ksql/parser/json/WindowExpressionTestCase.java new file mode 100644 index 00000000000..aea8a70734b --- /dev/null +++ b/ksql-parser/src/test/java/io/confluent/ksql/parser/json/WindowExpressionTestCase.java @@ -0,0 +1,29 @@ +/* + * Copyright 2019 Confluent Inc. + * + * Licensed under the Confluent Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * http://www.confluent.io/confluent-community-license + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package io.confluent.ksql.parser.json; + +import io.confluent.ksql.execution.windows.KsqlWindowExpression; +import io.confluent.ksql.execution.windows.TumblingWindowExpression; +import java.util.concurrent.TimeUnit; + +public class WindowExpressionTestCase { + static final KsqlWindowExpression WINDOW_EXPRESSION = + new TumblingWindowExpression( + 123, TimeUnit.DAYS + ); + static final String WINDOW_EXPRESSION_TXT = + "\" TUMBLING ( SIZE 123 DAYS ) \""; +}