Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: include lower-case identifiers among those that need quotes (#3723) #5139

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public enum Option {
/**
* Construct instance.
*
* <p>The {@code reservedWordPredicate} allows this formatter, which lives in the common module,
* <p>The {@code addQuotesPredicate} allows this formatter, which lives in the common module,
* to be wired up to the set of reserved words defined in the parser module. Wire up to
* {@code ParserUtil::isReservedWord}.
*
Expand All @@ -72,19 +72,19 @@ public enum Option {
* quotes. NB: this also makes the field name case-sensitive. So care must be taken to ensure
* field names have the correct case.
*
* @param reservedWordPredicate predicate to determine if a word is reserved in the SQL syntax.
* @param addQuotesPredicate predicate to determine if a word should be quoted.
* @param options the options to use when formatting the SQL.
*/
public SqlSchemaFormatter(
final Predicate<String> reservedWordPredicate,
final Predicate<String> addQuotesPredicate,
final Option... options
) {
this.options = options.length == 0
? EnumSet.noneOf(Option.class)
: EnumSet.of(options[0], options);

this.formatOptions = FormatOptions.of(
requireNonNull(reservedWordPredicate, "reservedWordPredicate")
requireNonNull(addQuotesPredicate, "addQuotesPredicate")
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

public final class FormatOptions {

private final Predicate<String> reservedWordPredicate;
private final Predicate<String> addQuotesPredicate;

public static FormatOptions none() {
return new FormatOptions(word -> true);
Expand All @@ -41,7 +41,7 @@ public static FormatOptions noEscape() {
/**
* Construct instance.
*
* <p>The {@code reservedWordPredicate} allows code that lives in the common module
* <p>The {@code addQuotesPredicate} allows code that lives in the common module
* to be wired up to the set of reserved words defined in the parser module. Wire this up to
* {@code ParserUtil::isReservedIdentifier}.
*
Expand All @@ -53,29 +53,29 @@ public static FormatOptions noEscape() {
* quotes. NB: this also makes the field name case-sensitive. So care must be taken to ensure
* field names have the correct case.
*
* @param reservedWordPredicate predicate to test if a word is a reserved in SQL syntax.
* @param addQuotesPredicate predicate to test if a word should be quoted.
* @return instance of {@code FormatOptions}.
*/
public static FormatOptions of(final Predicate<String> reservedWordPredicate) {
return new FormatOptions(reservedWordPredicate);
public static FormatOptions of(final Predicate<String> addQuotesPredicate) {
return new FormatOptions(addQuotesPredicate);
}

private FormatOptions(final Predicate<String> fieldNameEscaper) {
this.reservedWordPredicate = requireNonNull(fieldNameEscaper, "reservedWordPredicate");
this.addQuotesPredicate = requireNonNull(fieldNameEscaper, "addQuotesPredicate");
}

public boolean isReservedWord(final String word) {
return reservedWordPredicate.test(word);
private boolean shouldQuote(final String word) {
return addQuotesPredicate.test(word);
}

/**
* Escapes {@code word} if it is a reserved word, determined by {@link #isReservedWord(String)}.
* Escapes {@code word} if it is a reserved word, determined by {@link #shouldQuote(String)}.
*
* @param word the word to escape
* @return {@code word}, if it is not a reserved word, otherwise {@code word} wrapped in
* back quotes
*/
public String escape(final String word) {
return isReservedWord(word) ? KsqlConstants.ESCAPE + word + KsqlConstants.ESCAPE : word;
return shouldQuote(word) ? KsqlConstants.ESCAPE + word + KsqlConstants.ESCAPE : word;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
public class DefaultSchemaInjector implements Injector {

private static final SqlSchemaFormatter FORMATTER = new SqlSchemaFormatter(
IdentifierUtil::needsQuotes, Option.AS_COLUMN_LIST);
w -> !IdentifierUtil.isValid(w), Option.AS_COLUMN_LIST);

private final TopicSchemaSupplier schemaSupplier;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ public void shouldBuildNewCsStatementText() {

// Then:
assertThat(result.getStatementText(), is(
"CREATE STREAM cs ("
"CREATE STREAM `cs` ("
+ "INTFIELD INTEGER, "
+ "BIGINTFIELD BIGINT, "
+ "DOUBLEFIELD DOUBLE, "
Expand All @@ -302,7 +302,7 @@ public void shouldBuildNewCtStatementText() {

// Then:
assertThat(result.getStatementText(), is(
"CREATE TABLE ct ("
"CREATE TABLE `ct` ("
+ "INTFIELD INTEGER, "
+ "BIGINTFIELD BIGINT, "
+ "DOUBLEFIELD DOUBLE, "
Expand All @@ -329,7 +329,7 @@ public void shouldBuildNewCsStatementTextFromId() {

// Then:
assertThat(result.getStatementText(), is(
"CREATE STREAM cs ("
"CREATE STREAM `cs` ("
+ "INTFIELD INTEGER, "
+ "BIGINTFIELD BIGINT, "
+ "DOUBLEFIELD DOUBLE, "
Expand All @@ -356,7 +356,7 @@ public void shouldBuildNewCtStatementTextFromId() {

// Then:
assertThat(result.getStatementText(), is(
"CREATE TABLE ct ("
"CREATE TABLE `ct` ("
+ "INTFIELD INTEGER, "
+ "BIGINTFIELD BIGINT, "
+ "DOUBLEFIELD DOUBLE, "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -784,7 +784,7 @@ public void shouldSummarizeExecutionPlanCorrectly() {

// When/Then:
assertThat(schemaKtream.getExecutionPlan(new QueryId("query"), ""), equalTo(
" > [ SOURCE ] | Schema: [ROWKEY STRING KEY, key STRING, val BIGINT] | "
" > [ SOURCE ] | Schema: [ROWKEY STRING KEY, `key` STRING, `val` BIGINT] | "
+ "Logger: query.node.source\n"
+ "\tparent plan"));
}
Expand All @@ -809,7 +809,7 @@ public void shouldSummarizeExecutionPlanCorrectlyForRoot() {

// When/Then:
assertThat(schemaKtream.getExecutionPlan(new QueryId("query"), ""), equalTo(
" > [ SOURCE ] | Schema: [ROWKEY STRING KEY, key STRING, val BIGINT] | "
" > [ SOURCE ] | Schema: [ROWKEY STRING KEY, `key` STRING, `val` BIGINT] | "
+ "Logger: query.node.source\n"));
}

Expand Down Expand Up @@ -839,7 +839,7 @@ public void shouldSummarizeExecutionPlanCorrectlyWhenMultipleParents() {

// When/Then:
assertThat(schemaKtream.getExecutionPlan(new QueryId("query"), ""), equalTo(
" > [ SOURCE ] | Schema: [ROWKEY STRING KEY, key STRING, val BIGINT] | "
" > [ SOURCE ] | Schema: [ROWKEY STRING KEY, `key` STRING, `val` BIGINT] | "
+ "Logger: query.node.source\n"
+ "\tparent 1 plan"
+ "\tparent 2 plan"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ private IdentifierUtil() { }
* @param identifier the identifier
* @return whether or not {@code identifier} is a valid identifier without quotes
*/
public static boolean needsQuotes(final String identifier) {
public static boolean isValid(final String identifier) {
final SqlBaseLexer sqlBaseLexer = new SqlBaseLexer(
new CaseInsensitiveStream(CharStreams.fromString(identifier)));
final CommonTokenStream tokenStream = new CommonTokenStream(sqlBaseLexer);
Expand All @@ -43,8 +43,20 @@ public static boolean needsQuotes(final String identifier) {
sqlBaseParser.identifier();

// needs quotes if the `identifier` was not able to read the entire line
return sqlBaseParser.getNumberOfSyntaxErrors() != 0
|| sqlBaseParser.getCurrentToken().getCharPositionInLine() != identifier.length();
return sqlBaseParser.getNumberOfSyntaxErrors() == 0
&& sqlBaseParser.getCurrentToken().getCharPositionInLine() == identifier.length();
}

/**
* @param identifier the identifier
* @return whether or not {@code identifier} needs quotes to be parsed as the same identifier
*/
public static boolean needsQuotes(final String identifier) {
return !(isValid(identifier) && upperCase(identifier));
}

private static boolean upperCase(final String identifier) {
return identifier.toUpperCase().equals(identifier);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ public void shouldFormatCreateStreamStatementWithExplicitKey() {
final String sql = SqlFormatter.formatSql(createStream);

// Then:
assertThat(sql, is("CREATE STREAM TEST (ROWKEY STRING KEY, Foo STRING) "
assertThat(sql, is("CREATE STREAM TEST (ROWKEY STRING KEY, `Foo` STRING) "
+ "WITH (KAFKA_TOPIC='topic_test', KEY='ORDERID', VALUE_FORMAT='JSON');"));
}

Expand All @@ -242,7 +242,7 @@ public void shouldFormatCreateStreamStatementWithImplicitKey() {
final String sql = SqlFormatter.formatSql(createStream);

// Then:
assertThat(sql, is("CREATE STREAM TEST (Foo STRING, Bar STRING) "
assertThat(sql, is("CREATE STREAM TEST (`Foo` STRING, `Bar` STRING) "
+ "WITH (KAFKA_TOPIC='topic_test', KEY='ORDERID', VALUE_FORMAT='JSON');"));
}

Expand All @@ -259,7 +259,7 @@ public void shouldFormatCreateTableStatementWithExplicitKey() {
final String sql = SqlFormatter.formatSql(createTable);

// Then:
assertThat(sql, is("CREATE TABLE TEST (ROWKEY STRING KEY, Foo STRING) "
assertThat(sql, is("CREATE TABLE TEST (ROWKEY STRING KEY, `Foo` STRING) "
+ "WITH (KAFKA_TOPIC='topic_test', KEY='ORDERID', VALUE_FORMAT='JSON');"));
}

Expand All @@ -276,7 +276,7 @@ public void shouldFormatCreateTableStatementWithImplicitKey() {
final String sql = SqlFormatter.formatSql(createTable);

// Then:
assertThat(sql, is("CREATE TABLE TEST (Foo STRING, Bar STRING) "
assertThat(sql, is("CREATE TABLE TEST (`Foo` STRING, `Bar` STRING) "
+ "WITH (KAFKA_TOPIC='topic_test', KEY='ORDERID', VALUE_FORMAT='JSON');"));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public void shouldNeedBackQuotes() {
"SELECT", // reserved word
"@ID", // invalid character
"FOO.BAR", // with a dot
"foo" // lower case
};

// Then:
Expand All @@ -52,4 +53,39 @@ public void shouldNotNeedBackQuotes() {
}
}

@Test
public void shouldBeValid() {
// Given:
final String[] identifiers = new String[]{
"FOO", // nothing special
"foo", // lower-case
};

// Then:
for (final String identifier : identifiers) {
assertThat(
"Expected " + identifier + " to be valid.",
IdentifierUtil.isValid(identifier)
);
}
}

@Test
public void shouldNotBeValid() {
// Given:
final String[] identifiers = new String[]{
"@FOO", // invalid character
"FOO.BAR", // Dot
"SELECT" // reserved word
};

// Then:
for (final String identifier : identifiers) {
assertThat(
"Expected " + identifier + " to be invalid",
!IdentifierUtil.isValid(identifier)
);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public final class ProcessingLogServerUtils {

private static final Logger LOGGER = LoggerFactory.getLogger(ProcessingLogServerUtils.class);
private static final SqlSchemaFormatter FORMATTER =
new SqlSchemaFormatter(IdentifierUtil::needsQuotes, Option.AS_COLUMN_LIST);
new SqlSchemaFormatter(w -> !IdentifierUtil.isValid(w), Option.AS_COLUMN_LIST);

private ProcessingLogServerUtils() {
}
Expand Down