[jOOQ/jOOQ#18480] Parser doesn't recognise built-in functions when quoted

This commit is contained in:
Lukas Eder 2025-05-20 08:29:32 +02:00
parent c237949fa0
commit 8a21c7b98c
3 changed files with 80 additions and 4 deletions

View File

@ -227,6 +227,10 @@ abstract class AbstractParseContext extends AbstractScope {
abstract int afterWhitespace(int p);
final char lower(char c) {
return c >= 'A' && c <= 'Z' ? (char) (c + ('a' - 'A')) : c;
}
final char upper(char c) {
return c >= 'a' && c <= 'z' ? (char) (c - ('a' - 'A')) : c;
}
@ -340,6 +344,10 @@ abstract class AbstractParseContext extends AbstractScope {
return character() == c;
}
final boolean peek(char c, int p) {
return character(p) == c;
}
public final boolean peek(String string) {
return peek(string, position());
}

View File

@ -55,6 +55,7 @@ import static org.jooq.JoinType.JOIN;
// ...
// ...
import static org.jooq.SQLDialect.DERBY;
import static org.jooq.SQLDialect.FIREBIRD;
// ...
import static org.jooq.SQLDialect.HSQLDB;
// ...
@ -515,6 +516,7 @@ import static org.jooq.impl.Tools.asInt;
import static org.jooq.impl.Tools.deleteQueryImpl;
import static org.jooq.impl.Tools.map;
import static org.jooq.impl.Tools.normaliseNameCase;
import static org.jooq.impl.Tools.parseNameCase;
import static org.jooq.impl.Tools.selectQueryImpl;
import static org.jooq.impl.Tools.updateQueryImpl;
import static org.jooq.impl.Tools.BooleanDataKey.DATA_PARSE_ON_CONFLICT;
@ -755,6 +757,7 @@ import org.jooq.XMLAttributes;
import org.jooq.XMLTableColumnPathStep;
import org.jooq.XMLTableColumnsStep;
import org.jooq.XMLTablePassingStep;
import org.jooq.conf.ParseNameCase;
import org.jooq.conf.ParseSearchSchema;
import org.jooq.conf.ParseUnknownFunctions;
import org.jooq.conf.ParseUnsupportedSyntax;
@ -927,7 +930,7 @@ final class DefaultParseContext extends AbstractParseContext implements ParseCon
static final Set<SQLDialect> SUPPORTS_HASH_COMMENT_SYNTAX = SQLDialect.supportedBy(MARIADB, MYSQL);
static final Set<SQLDialect> NO_SUPPORT_QUOTED_BUILT_IN_FUNCION_NAMES = SQLDialect.supportedBy(DERBY, HSQLDB);
static final Set<SQLDialect> NO_SUPPORT_QUOTED_BUILT_IN_FUNCION_NAMES = SQLDialect.supportedBy(DERBY, FIREBIRD, HSQLDB);
final Queries parse() {
return wrap(() -> {
@ -8982,7 +8985,22 @@ final class DefaultParseContext extends AbstractParseContext implements ParseCon
switch (characterUpper()) {
char u = characterUpper();
// [#18480] Allow for quoted built-in function identifiers
switch (u) {
case '`':
case '[':
case '"':
u = characterNextUpper();
break;
}
switch (u) {
// [#8821] Known prefixes so far:
case ':':
@ -9419,7 +9437,7 @@ final class DefaultParseContext extends AbstractParseContext implements ParseCon
case 'L':
if (parseFunctionNameIf("LOWER", "LCASE"))
return lower((Field) parseFieldParenthesised());
return DSL.lower((Field) parseFieldParenthesised());
else if (parseFunctionNameIf("LPAD", "leftPad"))
return parseFunctionArgs3(DSL::lpad, DSL::lpad);
else if (parseFunctionNameIf("LTRIM"))
@ -13603,6 +13621,14 @@ final class DefaultParseContext extends AbstractParseContext implements ParseCon
: 0;
}
private final char peekQuote(boolean allowAposQuotes, int p) {
return peek('"', p) ? '"'
: peek('`', p) ? '`'
: peek('[', p) ? ']'
: allowAposQuotes && peek('\'', p) ? '\''
: 0;
}
private final DataType<?> parseCastDataType() {
char character = characterUpper();
@ -15287,8 +15313,41 @@ final class DefaultParseContext extends AbstractParseContext implements ParseCon
int skip = afterWhitespace(p, peekIntoParens) - p;
// [#18480] Function names are allowed to be quoted
char quoteEnd = requireFunction ? peekQuote(false, p + skip) : 0;
boolean quoted = quoteEnd != 0;
ParseNameCase nameCase = null;
if (quoted) {
if (NO_SUPPORT_QUOTED_BUILT_IN_FUNCION_NAMES.contains(parseDialect()))
return false;
nameCase = parseNameCase(configuration);
switch (parseDialect()) {
default:
caseSensitive =
nameCase != ParseNameCase.AS_IS
&& nameCase != ParseNameCase.LOWER;
break;
}
skip++;
}
for (int i = 0; i < length; i++) {
char c = keyword.charAt(i);
if (caseSensitive && (
nameCase == ParseNameCase.LOWER_IF_UNQUOTED ||
nameCase == ParseNameCase.LOWER))
c = lower(c);
int pos = p + i + skip;
switch (c) {
@ -15313,6 +15372,15 @@ final class DefaultParseContext extends AbstractParseContext implements ParseCon
int pos = p + length + skip;
if (quoted) {
if (character(pos) == quoteEnd) {
pos++;
skip++;
}
else
return false;
}
// [#8806] A keyword that is followed by a period is very likely an identifier
if (isIdentifierPart(pos) || character(pos) == '.')
return false;

View File

@ -7212,7 +7212,7 @@ final class Tools {
* Get the {@link ParseNameCase}, looking up the default value from the
* parse dialect.
*/
private static final ParseNameCase parseNameCase(Configuration configuration) {
static final ParseNameCase parseNameCase(Configuration configuration) {
ParseNameCase result = defaultIfNull(configuration.settings().getParseNameCase(), ParseNameCase.DEFAULT);
if (result == ParseNameCase.DEFAULT) {