diff --git a/jOOQ-test/src/org/jooq/test/_/testcases/PlainSQLTests.java b/jOOQ-test/src/org/jooq/test/_/testcases/PlainSQLTests.java index f77ae69694..73458e2acc 100644 --- a/jOOQ-test/src/org/jooq/test/_/testcases/PlainSQLTests.java +++ b/jOOQ-test/src/org/jooq/test/_/testcases/PlainSQLTests.java @@ -40,6 +40,8 @@ import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertTrue; import static org.jooq.SQLDialect.FIREBIRD; +import static org.jooq.SQLDialect.H2; +import static org.jooq.conf.StatementType.STATIC_STATEMENT; import static org.jooq.impl.Factory.field; import static org.jooq.impl.Factory.fieldByName; import static org.jooq.impl.Factory.function; @@ -68,6 +70,7 @@ import org.jooq.ResultQuery; import org.jooq.Table; import org.jooq.TableRecord; import org.jooq.UpdatableRecord; +import org.jooq.conf.Settings; import org.jooq.impl.CustomCondition; import org.jooq.impl.CustomField; import org.jooq.impl.Factory; @@ -249,6 +252,56 @@ extends BaseTest substitutes) { int bindIndex = 0; char[] sqlChars = sql.toCharArray(); - boolean stringLiteral = false; for (int i = 0; i < sqlChars.length; i++) { + // [#1797] Skip content inside of single-line comments, e.g. + // select 1 x -- what's this ?'? + // from t_book -- what's that ?'? + // where id = ? + if (peek(sqlChars, i, "--")) { + + // Consume the complete comment + for (; sqlChars[i] != '\r' && sqlChars[i] != '\n'; context.sql(sqlChars[i++])); + + // Consume the newline character + context.sql(sqlChars[i]); + } + + // [#1797] Skip content inside of multi-line comments, e.g. + // select 1 x /* what's this ?'? + // I don't know ?'? */ + // from t_book where id = ? + else if (peek(sqlChars, i, "/*")) { + + // Consume the complete comment + for (; !peek(sqlChars, i, "*/"); context.sql(sqlChars[i++])); + + // Consume the comment delimiter + context.sql(sqlChars[i++]); + context.sql(sqlChars[i]); + } + // [#1031] [#1032] Skip ? inside of string literals, e.g. // insert into x values ('Hello? Anybody out there?'); - if (sqlChars[i] == '\'') { + else if (sqlChars[i] == '\'') { - // Delimiter is actually an escaping apostrophe - if (i + 1 < sqlChars.length && sqlChars[i + 1] == '\'') { + // Consume the initial string literal delimiter + context.sql(sqlChars[i++]); - // Skip subsequent character + // Consume the whole string literal + for (;;) { + + // Consume an escaped apostrophe + if (peek(sqlChars, i, "''")) { + context.sql(sqlChars[i++]); + } + + // Break on the terminal string literal delimiter + else if (peek(sqlChars, i, "'")) { + break; + } + + // Consume string literal content context.sql(sqlChars[i++]); - context.sql(sqlChars[i]); } - else { - stringLiteral = !stringLiteral; - context.sql(sqlChars[i]); - } + // Consume the terminal string literal delimiter + context.sql(sqlChars[i]); } // TODO: This case should be mutually exclusive with the next one // Inline bind variables only outside of string literals - else if (sqlChars[i] == '?' && context.inline() && !stringLiteral && bindIndex < substitutes.size()) { + else if (sqlChars[i] == '?' && context.inline() && bindIndex < substitutes.size()) { context.sql(substitutes.get(bindIndex++)); } // TODO: This case should be mutually exclusive with the previous one // [#1432] Inline substitues for {numbered placeholders} outside of string literals - else if (sqlChars[i] == '{' && !stringLiteral && bindIndex < substitutes.size()) { + else if (sqlChars[i] == '{' && bindIndex < substitutes.size()) { // [#1461] Be careful not to match any JDBC escape syntax if (JDBC_ESCAPE_PATTERN.matcher(sql.substring(i)).matches()) { @@ -451,6 +487,28 @@ final class Util { } } + /** + * Peek for a string at a given index of a char[] + * + * @param sqlChars The char array to peek into + * @param index The index within the char array to peek for a string + * @param peek The string to peek for + */ + static final boolean peek(char[] sqlChars, int index, String peek) { + char[] peekArray = peek.toCharArray(); + + for (int i = 0; i < peekArray.length; i++) { + if (index + i >= sqlChars.length) { + return false; + } + if (sqlChars[index + i] != peekArray[i]) { + return false; + } + } + + return true; + } + /** * Create {@link QueryPart} objects from bind values or substitutes */ diff --git a/jOOQ/src/test/java/org/jooq/test/BasicTest.java b/jOOQ/src/test/java/org/jooq/test/BasicTest.java index 0e1bf9d345..bfc585de56 100644 --- a/jOOQ/src/test/java/org/jooq/test/BasicTest.java +++ b/jOOQ/src/test/java/org/jooq/test/BasicTest.java @@ -1059,6 +1059,36 @@ public class BasicTest { context.assertIsSatisfied(); } + @Test + public void testPlainSQLComments() throws Exception { + Field f = field( + "-- comment ? '\n" + + "/* another comment ? '\n" + + " continuing -- */" + + "a bind value : ? /* a comment : ? */ another bind value : ?", "a", "b"); + + assertEquals( + "-- comment ? '\n" + + "/* another comment ? '\n" + + " continuing -- */" + + "a bind value : 'a' /* a comment : ? */ another bind value : 'b'", r_refI().render(f)); + assertEquals( + "-- comment ? '\n" + + "/* another comment ? '\n" + + " continuing -- */" + + "a bind value : ? /* a comment : ? */ another bind value : ?", r_ref().render(f)); + + context.checking(new Expectations() {{ + oneOf(statement).setString(1, "a"); + oneOf(statement).setString(2, "b"); + }}); + + int i = b_ref().bind(f).peekIndex(); + assertEquals(3, i); + + context.assertIsSatisfied(); + } + @Test public void testPlainSQLField() throws Exception { Field f1 = field("DECODE(TABLE1.ID, 1, 'a', 'b')");