[jOOQ/jOOQ#9888] Support parsing some PostgreSQL specific operators

This commit is contained in:
Lukas Eder 2020-02-27 12:28:47 +01:00
parent cdae95bdd2
commit 7301ef7764

View File

@ -4616,6 +4616,7 @@ final class ParserImpl implements Parser {
Comparator comp;
TSQLOuterJoinComparator outer;
boolean not;
boolean notOp = false;
left = parseConcat(ctx, null);
not = parseKeywordIf(ctx, "NOT");
@ -4749,13 +4750,13 @@ final class ParserImpl implements Parser {
? ((Field) left).between((Field) r1, (Field) r2)
: new RowBetweenCondition((Row) left, (Row) r1, not, symmetric, (Row) r2);
}
else if (left instanceof Field && parseKeywordIf(ctx, "LIKE")) {
else if (left instanceof Field && (parseKeywordIf(ctx, "LIKE") || parseOperatorIf(ctx, "~~") || (notOp = parseOperatorIf(ctx, "!~~")))) {
if (parseKeywordIf(ctx, "ANY")) {
parse(ctx, '(');
if (peekKeyword(ctx, "SELECT") || peekKeyword(ctx, "SEL") || peekKeyword(ctx, "WITH")) {
Select<?> select = parseWithOrSelect(ctx);
parse(ctx, ')');
LikeEscapeStep result = not ? ((Field) left).notLike(any(select)) : ((Field) left).like(any(select));
LikeEscapeStep result = (not ^ notOp) ? ((Field) left).notLike(any(select)) : ((Field) left).like(any(select));
return parseEscapeClauseIf(ctx, result);
}
else {
@ -4771,7 +4772,7 @@ final class ParserImpl implements Parser {
parse(ctx, ')');
}
Field<String>[] fieldArray = fields.toArray(new Field[0]);
LikeEscapeStep result = not ? ((Field<String>) left).notLike(any(fieldArray)) : ((Field<String>) left).like(any(fieldArray));
LikeEscapeStep result = (not ^ notOp) ? ((Field<String>) left).notLike(any(fieldArray)) : ((Field<String>) left).like(any(fieldArray));
return parseEscapeClauseIf(ctx, result);
}
}
@ -4780,7 +4781,7 @@ final class ParserImpl implements Parser {
if (peekKeyword(ctx, "SELECT") || peekKeyword(ctx, "SEL") || peekKeyword(ctx, "WITH")) {
Select<?> select = parseWithOrSelect(ctx);
parse(ctx, ')');
LikeEscapeStep result = not ? ((Field) left).notLike(all(select)) : ((Field) left).like(all(select));
LikeEscapeStep result = (not ^ notOp) ? ((Field) left).notLike(all(select)) : ((Field) left).like(all(select));
return parseEscapeClauseIf(ctx, result);
}
else {
@ -4796,26 +4797,28 @@ final class ParserImpl implements Parser {
parse(ctx, ')');
}
Field<String>[] fieldArray = fields.toArray(new Field[0]);
LikeEscapeStep result = not ? ((Field<String>) left).notLike(all(fieldArray)) : ((Field<String>) left).like(all(fieldArray));
LikeEscapeStep result = (not ^ notOp) ? ((Field<String>) left).notLike(all(fieldArray)) : ((Field<String>) left).like(all(fieldArray));
return parseEscapeClauseIf(ctx, result);
}
}
else {
Field right = toField(ctx, parseConcat(ctx, null));
LikeEscapeStep like = not ? ((Field) left).notLike(right) : ((Field) left).like(right);
LikeEscapeStep like = (not ^ notOp) ? ((Field) left).notLike(right) : ((Field) left).like(right);
return parseEscapeClauseIf(ctx, like);
}
}
else if (left instanceof Field && parseKeywordIf(ctx, "ILIKE")) {
else if (left instanceof Field && (parseKeywordIf(ctx, "ILIKE") || parseOperatorIf(ctx, "~~*") || (notOp = parseOperatorIf(ctx, "!~~*")))) {
Field right = toField(ctx, parseConcat(ctx, null));
LikeEscapeStep like = not ? ((Field) left).notLikeIgnoreCase(right) : ((Field) left).likeIgnoreCase(right);
LikeEscapeStep like = (not ^ notOp) ? ((Field) left).notLikeIgnoreCase(right) : ((Field) left).likeIgnoreCase(right);
return parseEscapeClauseIf(ctx, like);
}
else if (left instanceof Field && (parseKeywordIf(ctx, "REGEXP")
|| parseKeywordIf(ctx, "RLIKE")
|| parseKeywordIf(ctx, "LIKE_REGEX"))) {
|| parseKeywordIf(ctx, "LIKE_REGEX")
|| parseOperatorIf(ctx, "~")
|| (notOp = parseOperatorIf(ctx, "!~")))) {
Field right = toField(ctx, parseConcat(ctx, null));
return not
return (not ^ notOp)
? ((Field) left).notLikeRegex(right)
: ((Field) left).likeRegex(right);
}
@ -10532,6 +10535,46 @@ final class ParserImpl implements Parser {
return peekKeyword(ctx, string, true, false, true);
}
private static final boolean parseOperator(ParserContext ctx, String operator) {
if (!parseOperatorIf(ctx, operator))
throw ctx.expected("Operator '" + operator + "'");
return true;
}
private static final boolean parseOperatorIf(ParserContext ctx, String operator) {
return peekOperator(ctx, operator, true);
}
private static final boolean peekOperator(ParserContext ctx, String operator) {
return peekOperator(ctx, operator, false);
}
private static final boolean peekOperator(ParserContext ctx, String operator, boolean updatePosition) {
int length = operator.length();
int position = ctx.position();
if (ctx.sql.length < position + length)
return false;
int pos = afterWhitespace(ctx, position, false);
for (int i = 0; i < length; i++, pos++)
if (ctx.sql[pos] != operator.charAt(i))
return false;
// [#9888] An operator that is followed by a special character is very likely another, more complex operator
if (ctx.isOperatorPart(pos))
return false;
if (updatePosition) {
ctx.position(pos);
parseWhitespaceIf(ctx);
}
return true;
}
private static final boolean parseKeyword(ParserContext ctx, String keyword) {
if (!parseKeywordIf(ctx, keyword))
throw ctx.expected("Keyword '" + keyword + "'");
@ -10605,34 +10648,12 @@ final class ParserImpl implements Parser {
private static final boolean peekKeyword(ParserContext ctx, String keyword, boolean updatePosition, boolean peekIntoParens, boolean requireFunction) {
int length = keyword.length();
int skip;
int position = ctx.position();
if (ctx.sql.length < position + length)
return false;
skipLoop:
for (skip = 0; position + skip < ctx.sql.length; skip++) {
char c = ctx.sql[position + skip];
switch (c) {
case ' ':
case '\t':
case '\r':
case '\n':
continue skipLoop;
case '(':
if (peekIntoParens)
continue skipLoop;
else
break skipLoop;
default:
break skipLoop;
}
}
int skip = afterWhitespace(ctx, position, peekIntoParens) - position;
for (int i = 0; i < length; i++) {
char c = keyword.charAt(i);
@ -10676,6 +10697,10 @@ final class ParserImpl implements Parser {
}
private static final int afterWhitespace(ParserContext ctx, int position) {
return afterWhitespace(ctx, position, false);
}
private static final int afterWhitespace(ParserContext ctx, int position, boolean peekIntoParens) {
// [#8074] The SQL standard and some implementations (e.g. PostgreSQL,
// SQL Server) support nesting block comments
@ -10692,6 +10717,12 @@ final class ParserImpl implements Parser {
position = i + 1;
continue loop;
case '(':
if (peekIntoParens)
continue loop;
else
break loop;
case '/':
if (i + 1 < ctx.sql.length && ctx.sql[i + 1] == '*') {
i = i + 2;
@ -11165,6 +11196,41 @@ final class ParserContext {
return Character.isWhitespace(character(pos));
}
boolean isOperatorPart() {
return isOperatorPart(character());
}
boolean isOperatorPart(int pos) {
return isOperatorPart(character(pos));
}
boolean isOperatorPart(char character) {
// Obtain all distinct, built-in PostgreSQL operator characters:
// select distinct regexp_split_to_table(oprname, '') from pg_catalog.pg_operator order by 1;
switch (character) {
case '!':
case '#':
case '%':
case '&':
case '*':
case '+':
case '-':
case '/':
case ':':
case '<':
case '=':
case '>':
case '?':
case '@':
case '^':
case '|':
case '~':
return true;
}
return false;
}
boolean isIdentifierPart() {
return isIdentifierPart(character());
}