From c8c02f53ab67a7bc0ac219190847e64675da09b1 Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Tue, 3 Jun 2025 09:34:13 +0200 Subject: [PATCH] [jOOQ/jOOQ#18547] Parser cannot parse non-scalar subquery NULL predicate --- .../main/java/org/jooq/impl/ParserImpl.java | 333 +++++++++++------- 1 file changed, 198 insertions(+), 135 deletions(-) diff --git a/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java b/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java index 62ce8db9bd..c9e2566d89 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java @@ -631,6 +631,7 @@ import org.jooq.DropTableStep; import org.jooq.DropTypeStep; import org.jooq.Field; import org.jooq.FieldOrRow; +import org.jooq.FieldOrRowOrSelect; // ... // ... import org.jooq.Function1; @@ -713,6 +714,7 @@ import org.jooq.SQLDialect; import org.jooq.SQLDialectCategory; import org.jooq.Schema; import org.jooq.Select; +import org.jooq.SelectCorrelatedSubqueryStep; import org.jooq.SelectField; import org.jooq.SelectFieldOrAsterisk; import org.jooq.Sequence; @@ -1419,6 +1421,19 @@ final class DefaultParseContext extends AbstractParseContext implements ParseCon } private final Field parseScalarSubqueryIf() { + FieldOrRowOrSelect r = parseSubqueryIf(); + + if (r instanceof Select s) { + if (Tools.degree(s) != 1) + throw exception("Select list must contain exactly one column"); + + return field((Select) s); + } + + return null; + } + + private final FieldOrRowOrSelect parseSubqueryIf() { int p = position(); try { @@ -1426,10 +1441,8 @@ final class DefaultParseContext extends AbstractParseContext implements ParseCon parse('('); SelectQueryImpl select = parseWithOrSelect(); parse(')'); - if (Tools.degree(select) != 1) - throw exception("Select list must contain exactly one column"); - return field((Select) select); + return select; } } catch (ParserException e) { @@ -1782,7 +1795,11 @@ final class DefaultParseContext extends AbstractParseContext implements ParseCon return lhs; } - private final SelectQueryImpl degreeCheck(int expected, SelectQueryImpl s) { + private final > S degreeCheck(int expected, S s) { + return degreeCheck(expected, s, true); + } + + private final > S degreeCheck(int expected, S s, boolean throwIfNotMatched) { if (expected == 0) return s; @@ -1791,7 +1808,10 @@ final class DefaultParseContext extends AbstractParseContext implements ParseCon return s; if (expected != actual) - throw exception("Select list must contain " + expected + " columns. Got: " + actual); + if (throwIfNotMatched) + throw exception("Select list must contain " + expected + " columns. Got: " + actual); + else + return null; return s; } @@ -7098,10 +7118,11 @@ final class DefaultParseContext extends AbstractParseContext implements ParseCon } boolean notOp = false; - FieldOrRow left = parseConcat(); + FieldOrRowOrSelect left = parseConcat(); + Field leftScalar = toField(left, false); + Select leftSelect = left instanceof Select s ? s : null; int p2 = position(); boolean not = parseKeywordIf("NOT"); - boolean isField = left instanceof Field; Comparator comp; TSQLOuterJoinComparator outer; @@ -7131,25 +7152,25 @@ final class DefaultParseContext extends AbstractParseContext implements ParseCon // TODO equal degrees Condition result = all - ? isField + ? leftScalar != null ? peekSelectOrWith(true) - ? ((Field) left).compare(comp, DSL.all(parseWithOrSelect(1))) - : ((Field) left).compare(comp, DSL.all(parseList(',', c -> c.parseField()).toArray(EMPTY_FIELD))) + ? leftScalar.compare(comp, DSL.all(parseWithOrSelect(1))) + : leftScalar.compare(comp, DSL.all(parseList(',', c -> c.parseField()).toArray(EMPTY_FIELD))) // TODO: Support quantifiers also for rows : new RowSubqueryCondition((Row) left, DSL.all(parseWithOrSelect(((Row) left).size())), comp) : any - ? isField + ? leftScalar != null ? peekSelectOrWith(true) - ? ((Field) left).compare(comp, DSL.any(parseWithOrSelect(1))) - : ((Field) left).compare(comp, DSL.any(parseList(',', c -> c.parseField()).toArray(EMPTY_FIELD))) + ? leftScalar.compare(comp, DSL.any(parseWithOrSelect(1))) + : leftScalar.compare(comp, DSL.any(parseList(',', c -> c.parseField()).toArray(EMPTY_FIELD))) // TODO: Support quantifiers also for rows : new RowSubqueryCondition((Row) left, DSL.any(parseWithOrSelect(((Row) left).size())), comp) - : isField - ? ((Field) left).compare(comp, toField(parseConcat())) + : leftScalar != null + ? leftScalar.compare(comp, toField(parseConcat())) : AbstractRow.compare((Row) left, comp, parseRow(((Row) left).size(), true)); if (all || any) @@ -7162,25 +7183,32 @@ final class DefaultParseContext extends AbstractParseContext implements ParseCon if (parseKeywordIf("NULL")) return not - ? isField - ? ((Field) left).isNotNull() + ? leftScalar != null + ? leftScalar.isNotNull() + : leftSelect != null + ? new SelectIsNotNull(leftSelect) : ((Row) left).isNotNull() - : isField - ? ((Field) left).isNull() + : leftScalar != null + ? leftScalar.isNull() + : leftSelect != null + ? new SelectIsNull(leftSelect) : ((Row) left).isNull(); - else if (isField && parseKeywordIf("JSON")) + else if (leftScalar != null && parseKeywordIf("JSON")) return not - ? ((Field) left).isNotJson() - : ((Field) left).isJson(); - else if (isField && parseKeywordIf("DOCUMENT")) + ? leftScalar.isNotJson() + : leftScalar.isJson(); + else if (leftScalar != null && parseKeywordIf("DOCUMENT")) return not - ? ((Field) left).isNotDocument() - : ((Field) left).isDocument(); + ? leftScalar.isNotDocument() + : leftScalar.isDocument(); not = parseKeywordIf("DISTINCT FROM") == not; - if (left instanceof Field f) { + if (leftScalar != null) { Field right = toField(parseConcat()); - return not ? f.isNotDistinctFrom(right) : f.isDistinctFrom(right); + return not ? leftScalar.isNotDistinctFrom(right) : leftScalar.isDistinctFrom(right); + } + else if (leftSelect != null) { + throw notImplementedNonScalarSelectPredicate(); } else { Row right = parseRow(((Row) left).size(), true); @@ -7194,37 +7222,40 @@ final class DefaultParseContext extends AbstractParseContext implements ParseCon Condition result; // [#12691] Some dialects support A IN B syntax without parentheses for single element in lists - if (isField && !peek('(')) { + if (leftScalar != null && !peek('(')) { result = not - ? ((Field) left).notIn(parseConcat()) - : ((Field) left).in(parseConcat()); + ? leftScalar.notIn(parseConcat()) + : leftScalar.in(parseConcat()); } else { parse('('); + if (leftScalar == null && leftSelect != null) + throw notImplementedNonScalarSelectPredicate(); + if (peek(')')) result = not - ? isField - ? ((Field) left).notIn(EMPTY_FIELD) + ? leftScalar != null + ? leftScalar.notIn(EMPTY_FIELD) : new RowInCondition((Row) left, new QueryPartList<>(), true) - : isField - ? ((Field) left).in(EMPTY_FIELD) + : leftScalar != null + ? leftScalar.in(EMPTY_FIELD) : new RowInCondition((Row) left, new QueryPartList<>(), false); else if (peekSelectOrWith(true)) result = not - ? isField - ? ((Field) left).notIn(parseWithOrSelect(1)) + ? leftScalar != null + ? leftScalar.notIn(parseWithOrSelect(1)) : new RowSubqueryCondition((Row) left, parseWithOrSelect(((Row) left).size()), NOT_IN) - : isField - ? ((Field) left).in(parseWithOrSelect(1)) + : leftScalar != null + ? leftScalar.in(parseWithOrSelect(1)) : new RowSubqueryCondition((Row) left, parseWithOrSelect(((Row) left).size()), IN); else result = not - ? isField - ? ((Field) left).notIn(parseList(',', c -> c.parseField())) + ? leftScalar != null + ? leftScalar.notIn(parseList(',', c -> c.parseField())) : new RowInCondition((Row) left, new QueryPartList<>(parseList(',', c -> parseRow(((Row) left).size()))), true) - : isField - ? ((Field) left).in(parseList(',', c -> c.parseField())) + : leftScalar != null + ? leftScalar.in(parseList(',', c -> c.parseField())) : new RowInCondition((Row) left, new QueryPartList<>(parseList(',', c -> parseRow(((Row) left).size()))), false); parse(')'); @@ -7234,40 +7265,44 @@ final class DefaultParseContext extends AbstractParseContext implements ParseCon } else if (parseKeywordIf("BETWEEN")) { boolean symmetric = !parseKeywordIf("ASYMMETRIC") && parseKeywordIf("SYMMETRIC"); - FieldOrRow r1 = isField + + if (leftScalar == null && leftSelect != null) + throw notImplementedNonScalarSelectPredicate(); + + FieldOrRowOrSelect r1 = leftScalar != null ? parseConcat() : parseRow(((Row) left).size()); parseKeyword("AND"); - FieldOrRow r2 = isField + FieldOrRowOrSelect r2 = leftScalar != null ? parseConcat() : parseRow(((Row) left).size()); return symmetric ? not - ? isField - ? ((Field) left).notBetweenSymmetric((Field) r1, (Field) r2) + ? leftScalar != null + ? leftScalar.notBetweenSymmetric((Field) r1, (Field) r2) : new RowBetweenCondition((Row) left, (Row) r1, not, symmetric, (Row) r2) - : isField - ? ((Field) left).betweenSymmetric((Field) r1, (Field) r2) + : leftScalar != null + ? leftScalar.betweenSymmetric((Field) r1, (Field) r2) : new RowBetweenCondition((Row) left, (Row) r1, not, symmetric, (Row) r2) : not - ? isField - ? ((Field) left).notBetween((Field) r1, (Field) r2) + ? leftScalar != null + ? leftScalar.notBetween((Field) r1, (Field) r2) : new RowBetweenCondition((Row) left, (Row) r1, not, symmetric, (Row) r2) - : isField - ? ((Field) left).between((Field) r1, (Field) r2) + : leftScalar != null + ? leftScalar.between((Field) r1, (Field) r2) : new RowBetweenCondition((Row) left, (Row) r1, not, symmetric, (Row) r2); } - else if (isField && (parseKeywordIf("LIKE") || parseOperatorIf("~~") || (notOp = parseOperatorIf("!~~")))) { + else if (leftScalar != null && (parseKeywordIf("LIKE") || parseOperatorIf("~~") || (notOp = parseOperatorIf("!~~")))) { if (parseKeywordIf("ANY")) { parse('('); if (peekSelectOrWith(true)) { Select select = parseWithOrSelect(); parse(')'); - if (binary((Field) left)) - return (not ^ notOp) ? ((Field) left).notBinaryLike(any(select)) : ((Field) left).binaryLike(any(select)); + if (binary(leftScalar)) + return (not ^ notOp) ? leftScalar.notBinaryLike(any(select)) : leftScalar.binaryLike(any(select)); else - return parseEscapeClauseIf((not ^ notOp) ? ((Field) left).notLike(any(select)) : ((Field) left).like(any(select))); + return parseEscapeClauseIf((not ^ notOp) ? leftScalar.notLike(any(select)) : leftScalar.like(any(select))); } else { List> fields; @@ -7278,10 +7313,10 @@ final class DefaultParseContext extends AbstractParseContext implements ParseCon fields = parseList(',', c -> toField(parseConcat())); parse(')'); } - if (binary((Field) left)) - return (not ^ notOp) ? ((Field) left).notBinaryLike(any((Field[]) fields.toArray(EMPTY_FIELD))) : ((Field) left).binaryLike(any((Field[]) fields.toArray(EMPTY_FIELD))); + if (binary(leftScalar)) + return (not ^ notOp) ? leftScalar.notBinaryLike(any((Field[]) fields.toArray(EMPTY_FIELD))) : leftScalar.binaryLike(any((Field[]) fields.toArray(EMPTY_FIELD))); else - return parseEscapeClauseIf((not ^ notOp) ? ((Field) left).notLike(any((Field[]) fields.toArray(EMPTY_FIELD))) : ((Field) left).like(any((Field[]) fields.toArray(EMPTY_FIELD)))); + return parseEscapeClauseIf((not ^ notOp) ? leftScalar.notLike(any((Field[]) fields.toArray(EMPTY_FIELD))) : leftScalar.like(any((Field[]) fields.toArray(EMPTY_FIELD)))); } } else if (parseKeywordIf("ALL")) { @@ -7289,10 +7324,10 @@ final class DefaultParseContext extends AbstractParseContext implements ParseCon if (peekSelectOrWith(true)) { Select select = parseWithOrSelect(); parse(')'); - if (binary((Field) left)) - return (not ^ notOp) ? ((Field) left).notBinaryLike(all(select)) : ((Field) left).binaryLike(all(select)); + if (binary(leftScalar)) + return (not ^ notOp) ? leftScalar.notBinaryLike(all(select)) : leftScalar.binaryLike(all(select)); else - return parseEscapeClauseIf((not ^ notOp) ? ((Field) left).notLike(all(select)) : ((Field) left).like(all(select))); + return parseEscapeClauseIf((not ^ notOp) ? leftScalar.notLike(all(select)) : leftScalar.like(all(select))); } else { List> fields; @@ -7303,39 +7338,39 @@ final class DefaultParseContext extends AbstractParseContext implements ParseCon fields = parseList(',', c -> toField(parseConcat())); parse(')'); } - if (binary((Field) left)) - return (not ^ notOp) ? ((Field) left).notBinaryLike(all((Field[]) fields.toArray(EMPTY_FIELD))) : ((Field) left).binaryLike(all((Field[]) fields.toArray(EMPTY_FIELD))); + if (binary(leftScalar)) + return (not ^ notOp) ? leftScalar.notBinaryLike(all((Field[]) fields.toArray(EMPTY_FIELD))) : leftScalar.binaryLike(all((Field[]) fields.toArray(EMPTY_FIELD))); else - return parseEscapeClauseIf((not ^ notOp) ? ((Field) left).notLike(all((Field[]) fields.toArray(EMPTY_FIELD))) : ((Field) left).like(all((Field[]) fields.toArray(EMPTY_FIELD)))); + return parseEscapeClauseIf((not ^ notOp) ? leftScalar.notLike(all((Field[]) fields.toArray(EMPTY_FIELD))) : leftScalar.like(all((Field[]) fields.toArray(EMPTY_FIELD)))); } } else { Field right = toField(parseConcat()); - if (binary((Field) left) || binary(right)) - return (not ^ notOp) ? ((Field) left).notBinaryLike(right) : ((Field) left).binaryLike(right); + if (binary(leftScalar) || binary(right)) + return (not ^ notOp) ? leftScalar.notBinaryLike(right) : leftScalar.binaryLike(right); else - return parseEscapeClauseIf((not ^ notOp) ? ((Field) left).notLike(right) : ((Field) left).like(right)); + return parseEscapeClauseIf((not ^ notOp) ? leftScalar.notLike(right) : leftScalar.like(right)); } } - else if (isField && (parseKeywordIf("ILIKE") || parseOperatorIf("~~*") || (notOp = parseOperatorIf("!~~*")))) { + else if (leftScalar != null && (parseKeywordIf("ILIKE") || parseOperatorIf("~~*") || (notOp = parseOperatorIf("!~~*")))) { Field right = toField(parseConcat()); - LikeEscapeStep like = (not ^ notOp) ? ((Field) left).notLikeIgnoreCase(right) : ((Field) left).likeIgnoreCase(right); + LikeEscapeStep like = (not ^ notOp) ? leftScalar.notLikeIgnoreCase(right) : leftScalar.likeIgnoreCase(right); return parseEscapeClauseIf(like); } - else if (isField && (parseKeywordIf("REGEXP") + else if (leftScalar != null && (parseKeywordIf("REGEXP") || parseKeywordIf("RLIKE") || parseKeywordIf("LIKE_REGEX") || parseOperatorIf("~") || (notOp = parseOperatorIf("!~")))) { Field right = toField(parseConcat()); return (not ^ notOp) - ? ((Field) left).notLikeRegex(right) - : ((Field) left).likeRegex(right); + ? leftScalar.notLikeRegex(right) + : leftScalar.likeRegex(right); } - else if (isField && parseKeywordIf("SIMILAR TO")) { + else if (leftScalar != null && parseKeywordIf("SIMILAR TO")) { Field right = toField(parseConcat()); - LikeEscapeStep like = not ? ((Field) left).notSimilarTo(right) : ((Field) left).similarTo(right); + LikeEscapeStep like = not ? leftScalar.notSimilarTo(right) : leftScalar.similarTo(right); return parseEscapeClauseIf(like); } else if (left instanceof Row && ((Row) left).size() == 2 && parseKeywordIf("OVERLAPS")) { @@ -7357,11 +7392,12 @@ final class DefaultParseContext extends AbstractParseContext implements ParseCon Condition result; parse('('); - FieldOrRow left = parseConcat(); + FieldOrRowOrSelect left = parseConcat(); parse(','); - if (left instanceof Field f) - result = f.isNotDistinctFrom((Field) toField(parseConcat())); + Field f = toField(left, false); + if (f != null) + result = f.isNotDistinctFrom(toField(parseConcat())); else result = new RowIsDistinctFrom((Row) left, parseRow(((Row) left).size(), true), true); @@ -8416,6 +8452,8 @@ final class DefaultParseContext extends AbstractParseContext implements ParseCon return null; else if (part instanceof Field f) return f; + else if (part instanceof Select s) + return DSL.field((Select) degreeCheck(1, s)); else if (part instanceof Row r) return r; else @@ -8423,20 +8461,32 @@ final class DefaultParseContext extends AbstractParseContext implements ParseCon } private final Field toField(QueryPart part) { + return toField(part, true); + } + + private final Field toField(QueryPart part, boolean throwIfNonScalar) { if (part == null) return null; else if (part instanceof Field f) return f; - else + else if (part instanceof Select s) + if (degreeCheck(1, s, throwIfNonScalar) != null) + return DSL.field(s); + else + return null; + else if (throwIfNonScalar) throw expected("Field"); + else + return null; } - private final FieldOrRow parseConcat() { - FieldOrRow r = parseCollated(); + private final FieldOrRowOrSelect parseConcat() { + FieldOrRowOrSelect r = parseCollated(); - if (r instanceof Field) + Field f = toField(r, false); + if (f != null) while (parseIf("||")) - r = concatOperator((Field) r, toField(parseCollated())); + r = f = concatOperator(f, toField(parseCollated())); return r; } @@ -8450,12 +8500,13 @@ final class DefaultParseContext extends AbstractParseContext implements ParseCon return DSL.concat(a1, a2); } - private final FieldOrRow parseCollated() { - FieldOrRow r = parseOp(); + private final FieldOrRowOrSelect parseCollated() { + FieldOrRowOrSelect r = parseOp(); - if (r instanceof Field) { + Field f = toField(r, false); + if (f != null) { if (parseKeywordIf("COLLATE")) - r = ((Field) r).collate(parseCollation()); + r = f = f.collate(parseCollation()); @@ -8605,79 +8656,81 @@ final class DefaultParseContext extends AbstractParseContext implements ParseCon // Any numeric operator of low precedence // See https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-PRECEDENCE - private final FieldOrRow parseOp() { - FieldOrRow l = parseSum(); + private final FieldOrRowOrSelect parseOp() { + FieldOrRowOrSelect l = parseSum(); - if (l instanceof Field) + Field f = toField(l, false); + if (f != null) for (;;) if (parseIf("<<")) - l = ((Field) l).shl((Field) parseSum()); + l = f = f.shl(toField(parseSum())); else if (parseIf(">>")) - l = ((Field) l).shr((Field) parseSum()); + l = f = f.shr(toField(parseSum())); else if (parseIf("->>")) { - Field r = (Field) parseSum(); + Field r = toField(parseSum()); // [#10018] We cannot really know reliably whether this is a // index or attribute access. Let's default to the // more popular attribute access for now. Also, // JSONB is likely more popular than JSON. if (r.getDataType().isNumeric()) - if (((Field) l).getDataType().getFromType() == JSON.class) - l = jsonGetElementAsText((Field) l, r); + if (f.getDataType().getFromType() == JSON.class) + l = f = jsonGetElementAsText(f, r); else - l = jsonbGetElementAsText((Field) l, r); + l = f = jsonbGetElementAsText(f, r); else - if (((Field) l).getDataType().getFromType() == JSON.class) - l = jsonGetAttributeAsText((Field) l, r); + if (f.getDataType().getFromType() == JSON.class) + l = f = jsonGetAttributeAsText(f, r); else - l = jsonbGetAttributeAsText((Field) l, r); + l = f = jsonbGetAttributeAsText(f, r); } else if (parseIf("->")) { - Field r = (Field) parseSum(); + Field r = toField(parseSum()); // [#10018] We cannot really know reliably whether this is a // index or attribute access. Let's default to the // more popular attribute access for now. Also, // JSONB is likely more popular than JSON. if (r.getDataType().isNumeric()) - if (((Field) l).getDataType().getFromType() == JSON.class) - l = jsonGetElement((Field) l, r); + if (f.getDataType().getFromType() == JSON.class) + l = f = jsonGetElement(f, r); else - l = jsonbGetElement((Field) l, r); + l = f = jsonbGetElement(f, r); else - if (((Field) l).getDataType().getFromType() == JSON.class) - l = jsonGetAttribute((Field) l, r); + if (f.getDataType().getFromType() == JSON.class) + l = f = jsonGetAttribute(f, r); else - l = jsonbGetAttribute((Field) l, r); + l = f = jsonbGetAttribute(f, r); } else if (parseIf("??") || parseIf("?")) - if (((Field) l).getDataType().getFromType() == JSON.class) - return jsonKeyExists((Field) l, (Field) parseSum()); + if (f.getDataType().getFromType() == JSON.class) + return jsonKeyExists(f, (Field) toField(parseSum())); else - return jsonbKeyExists((Field) l, (Field) parseSum()); + return jsonbKeyExists(f, (Field) toField(parseSum())); else break; return l; } - private final FieldOrRow parseSum() { - FieldOrRow r = parseFactor(); + private final FieldOrRowOrSelect parseSum() { + FieldOrRowOrSelect r = parseFactor(); - if (r instanceof Field) + Field f = toField(r, false); + if (f != null) for (;;) if (parseIf('+')) - r = parseSumRightOperand(r, true); + r = f = parseSumRightOperand(f, true); else if (!peek("->") && parseIf('-')) - r = parseSumRightOperand(r, false); + r = f = parseSumRightOperand(f, false); else break; return r; } - private final Field parseSumRightOperand(FieldOrRow r, boolean add) { - Field rhs = (Field) parseFactor(); + private final Field parseSumRightOperand(FieldOrRowOrSelect r, boolean add) { + Field rhs = toField(parseFactor()); DatePart part; if (parseProKeywordIf("YEAR", "YEARS")) @@ -8695,7 +8748,7 @@ final class DefaultParseContext extends AbstractParseContext implements ParseCon else part = null; - Field lhs = (Field) r; + Field lhs = toField(r); @@ -8720,17 +8773,18 @@ final class DefaultParseContext extends AbstractParseContext implements ParseCon return lhs.sub(rhs); } - private final FieldOrRow parseFactor() { - FieldOrRow r = parseExp(); + private final FieldOrRowOrSelect parseFactor() { + FieldOrRowOrSelect r = parseExp(); - if (r instanceof Field) + Field f = toField(r, false); + if (f != null) for (;;) if (!peek("*=") && parseIf('*')) - r = ((Field) r).mul((Field) parseExp()); + r = f = f.mul(toField(parseExp())); else if (parseIf('/')) - r = ((Field) r).div((Field) parseExp()); + r = f = f.div(toField(parseExp())); else if (parseIf('%')) - r = ((Field) r).mod((Field) parseExp()); + r = f = f.mod(toField(parseExp())); @@ -8743,20 +8797,21 @@ final class DefaultParseContext extends AbstractParseContext implements ParseCon return r; } - private final FieldOrRow parseExp() { - FieldOrRow r = parseUnaryOps(); + private final FieldOrRowOrSelect parseExp() { + FieldOrRowOrSelect r = parseUnaryOps(); - if (r instanceof Field) + Field f = toField(r, false); + if (f != null) for (;;) if (!peek("^=") && parseIf('^') || parseIf("**")) - r = ((Field) r).pow(toField(parseUnaryOps())); + r = f = f.pow(toField(parseUnaryOps())); else break; return r; } - private final FieldOrRow parseUnaryOps() { + private final FieldOrRowOrSelect parseUnaryOps() { if (parseProKeywordIf("CONNECT_BY_ROOT")) { @@ -8766,7 +8821,7 @@ final class DefaultParseContext extends AbstractParseContext implements ParseCon if (parseIf('~')) return toField(parseUnaryOps()).bitNot(); - FieldOrRow r; + FieldOrRowOrSelect r; Sign sign = parseSign(); if (sign == Sign.NONE) @@ -8809,7 +8864,7 @@ final class DefaultParseContext extends AbstractParseContext implements ParseCon return r; } - private final FieldOrRow parseMethodCallIf(FieldOrRow r) { + private final FieldOrRowOrSelect parseMethodCallIf(FieldOrRowOrSelect r) { @@ -8821,7 +8876,8 @@ final class DefaultParseContext extends AbstractParseContext implements ParseCon return r; } - private final FieldOrRow parseMethodCallIf0(FieldOrRow r) { + private final FieldOrRowOrSelect parseMethodCallIf0(FieldOrRowOrSelect r) { + @@ -8979,8 +9035,8 @@ final class DefaultParseContext extends AbstractParseContext implements ParseCon } } - private final FieldOrRow parseTerm() { - FieldOrRow field; + private final FieldOrRowOrSelect parseTerm() { + FieldOrRowOrSelect field; Object value; @@ -10152,12 +10208,15 @@ final class DefaultParseContext extends AbstractParseContext implements ParseCon if (!forbidden.isEmpty()) forbidden = EnumSet.noneOf(FunctionKeyword.class); - FieldOrRow r = parseScalarSubqueryIf(); + FieldOrRowOrSelect r = parseSubqueryIf(); if (r != null) return r; parse('('); - r = parseFieldOrRow(); + + // [#18548] This cast is safe in 3.20. In 3.21, it is unnecessary because + // FieldOrRow <: FieldOrRowOrSelect + r = (FieldOrRowOrSelect) parseFieldOrRow(); List> list = null; if (r instanceof Field f) { @@ -15950,6 +16009,10 @@ final class DefaultParseContext extends AbstractParseContext implements ParseCon return notImplemented(feature, "https://github.com/jOOQ/jOOQ/issues/16487"); } + private final ParserException notImplementedNonScalarSelectPredicate() { + return notImplemented("Non-scalar SELECT predicate", "https://github.com/jOOQ/jOOQ/issues/10176"); + } + private final ParserException notImplemented(String feature, String link) { return init(new ParserException(mark(), feature + " not yet implemented. If you're interested in this feature, please comment on " + link)); }