|
| 1 | +/*- |
| 2 | + * #%L |
| 3 | + * JSQLParser library |
| 4 | + * %% |
| 5 | + * Copyright (C) 2004 - 2026 JSQLParser |
| 6 | + * %% |
| 7 | + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 |
| 8 | + * #L% |
| 9 | + */ |
| 10 | +package net.sf.jsqlparser.parser; |
| 11 | + |
| 12 | +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; |
| 13 | +import static org.junit.jupiter.api.Assertions.assertEquals; |
| 14 | +import static org.junit.jupiter.api.Assertions.assertInstanceOf; |
| 15 | +import static org.junit.jupiter.api.Assertions.assertThrows; |
| 16 | + |
| 17 | +import java.util.stream.Stream; |
| 18 | +import net.sf.jsqlparser.JSQLParserException; |
| 19 | +import net.sf.jsqlparser.expression.AllValue; |
| 20 | +import net.sf.jsqlparser.expression.BooleanValue; |
| 21 | +import net.sf.jsqlparser.expression.Expression; |
| 22 | +import net.sf.jsqlparser.expression.Function; |
| 23 | +import net.sf.jsqlparser.expression.OracleNamedFunctionParameter; |
| 24 | +import net.sf.jsqlparser.expression.OverlapsCondition; |
| 25 | +import net.sf.jsqlparser.expression.PostgresNamedFunctionParameter; |
| 26 | +import net.sf.jsqlparser.expression.TimeKeyExpression; |
| 27 | +import net.sf.jsqlparser.expression.operators.relational.Between; |
| 28 | +import net.sf.jsqlparser.expression.operators.relational.EqualsTo; |
| 29 | +import net.sf.jsqlparser.expression.operators.relational.ExistsExpression; |
| 30 | +import net.sf.jsqlparser.expression.operators.relational.InExpression; |
| 31 | +import net.sf.jsqlparser.expression.operators.relational.IsNullExpression; |
| 32 | +import net.sf.jsqlparser.expression.operators.relational.SupportsOldOracleJoinSyntax; |
| 33 | +import net.sf.jsqlparser.schema.Column; |
| 34 | +import net.sf.jsqlparser.statement.select.PlainSelect; |
| 35 | +import org.junit.jupiter.api.Test; |
| 36 | +import org.junit.jupiter.params.ParameterizedTest; |
| 37 | +import org.junit.jupiter.params.provider.Arguments; |
| 38 | +import org.junit.jupiter.params.provider.CsvSource; |
| 39 | +import org.junit.jupiter.params.provider.MethodSource; |
| 40 | +import org.junit.jupiter.params.provider.ValueSource; |
| 41 | + |
| 42 | +class LookaheadDispatchTest { |
| 43 | + @ParameterizedTest |
| 44 | + @MethodSource("singleTokenExpressions") |
| 45 | + void preservesSingleTokenExpressionKinds(String sql, Class<? extends Expression> type, |
| 46 | + String deparsed) throws Exception { |
| 47 | + CCJSqlParser parser = CCJSqlParserUtil.newParser(sql); |
| 48 | + Expression expression = parser.PrimaryExpression(); |
| 49 | + assertInstanceOf(type, expression); |
| 50 | + assertEquals(deparsed, expression.toString()); |
| 51 | + assertEquals(CCJSqlParserConstants.EOF, parser.getNextToken().kind); |
| 52 | + Expression parsed = CCJSqlParserUtil.parseExpression(sql, false); |
| 53 | + assertInstanceOf(type, parsed); |
| 54 | + assertEquals(deparsed, parsed.toString()); |
| 55 | + } |
| 56 | + |
| 57 | + static Stream<Arguments> singleTokenExpressions() { |
| 58 | + return Stream.of( |
| 59 | + Arguments.of("TRUE", BooleanValue.class, "true"), |
| 60 | + Arguments.of("FALSE", BooleanValue.class, "false"), |
| 61 | + Arguments.of("ALL", AllValue.class, "ALL"), |
| 62 | + Arguments.of("CURRENT", TimeKeyExpression.class, "CURRENT"), |
| 63 | + Arguments.of("CURRENT_DATE", TimeKeyExpression.class, "CURRENT_DATE"), |
| 64 | + Arguments.of("CURRENT_TIME", TimeKeyExpression.class, "CURRENT_TIME"), |
| 65 | + Arguments.of("CURRENT_TIMESTAMP", TimeKeyExpression.class, "CURRENT_TIMESTAMP"), |
| 66 | + Arguments.of("CURRENT_TIMEZONE", TimeKeyExpression.class, "CURRENT_TIMEZONE"), |
| 67 | + Arguments.of("CURRENT_DATE()", TimeKeyExpression.class, "CURRENT_DATE()")); |
| 68 | + } |
| 69 | + |
| 70 | + @ParameterizedTest |
| 71 | + @ValueSource(strings = {"\"TRUE\"", "\"CURRENT_DATE\"", "\"ALL\""}) |
| 72 | + void preservesQuotedNames(String name) throws JSQLParserException { |
| 73 | + PlainSelect select = (PlainSelect) assertSqlCanBeParsedAndDeparsed("SELECT " + name); |
| 74 | + assertEquals(name, assertInstanceOf(Column.class, select.getSelectItem(0).getExpression()) |
| 75 | + .getColumnName()); |
| 76 | + } |
| 77 | + |
| 78 | + @ParameterizedTest |
| 79 | + @ValueSource(strings = {"TRUE", "ALL", "CURRENT_DATE"}) |
| 80 | + void honorsInterruptionBeforeSingleTokenExpressions(String sql) { |
| 81 | + CCJSqlParser parser = CCJSqlParserUtil.newParser(sql); |
| 82 | + parser.interrupted = true; |
| 83 | + assertThrows(ParseException.class, parser::PrimaryExpression); |
| 84 | + assertEquals(sql, parser.getNextToken().image); |
| 85 | + } |
| 86 | + |
| 87 | + @ParameterizedTest |
| 88 | + @MethodSource("conditions") |
| 89 | + void preservesConditionAlternatives(String sql, Class<? extends Expression> type) |
| 90 | + throws Exception { |
| 91 | + CCJSqlParser conditionParser = CCJSqlParserUtil.newParser(sql); |
| 92 | + Expression condition = conditionParser.Condition(); |
| 93 | + assertInstanceOf(type, condition); |
| 94 | + assertEquals(sql, condition.toString()); |
| 95 | + assertEquals(CCJSqlParserConstants.EOF, conditionParser.getNextToken().kind); |
| 96 | + |
| 97 | + CCJSqlParser sqlConditionParser = CCJSqlParserUtil.newParser(sql); |
| 98 | + Expression sqlCondition = sqlConditionParser.SQLCondition(); |
| 99 | + assertInstanceOf(type, sqlCondition); |
| 100 | + assertEquals(sql, sqlCondition.toString()); |
| 101 | + assertEquals(CCJSqlParserConstants.EOF, sqlConditionParser.getNextToken().kind); |
| 102 | + |
| 103 | + PlainSelect select = (PlainSelect) assertSqlCanBeParsedAndDeparsed( |
| 104 | + "SELECT * FROM t WHERE " + sql); |
| 105 | + assertInstanceOf(type, select.getWhere()); |
| 106 | + } |
| 107 | + |
| 108 | + static Stream<Arguments> conditions() { |
| 109 | + return Stream.of( |
| 110 | + Arguments.of("a", Column.class), |
| 111 | + Arguments.of("exists", Column.class), |
| 112 | + Arguments.of("exists.col", Column.class), |
| 113 | + Arguments.of("EXISTS (SELECT 1)", ExistsExpression.class), |
| 114 | + Arguments.of("EXISTS (1)", ExistsExpression.class), |
| 115 | + Arguments.of("a IN (1, 2)", InExpression.class), |
| 116 | + Arguments.of("a BETWEEN 1 AND 2", Between.class), |
| 117 | + Arguments.of("a IS NULL", IsNullExpression.class), |
| 118 | + Arguments.of("(a, b) OVERLAPS (c, d)", OverlapsCondition.class)); |
| 119 | + } |
| 120 | + |
| 121 | + @ParameterizedTest |
| 122 | + @ValueSource(strings = {"a", "exists", "exists.col"}) |
| 123 | + void preservesComparisons(String column) throws Exception { |
| 124 | + String sql = column + " = 1"; |
| 125 | + CCJSqlParser parser = CCJSqlParserUtil.newParser(sql); |
| 126 | + EqualsTo condition = assertInstanceOf(EqualsTo.class, parser.Condition()); |
| 127 | + assertEquals(column, assertInstanceOf(Column.class, condition.getLeftExpression()) |
| 128 | + .getFullyQualifiedName()); |
| 129 | + assertEquals(sql, condition.toString()); |
| 130 | + assertEquals(CCJSqlParserConstants.EOF, parser.getNextToken().kind); |
| 131 | + assertSqlCanBeParsedAndDeparsed("SELECT * FROM t WHERE " + sql); |
| 132 | + } |
| 133 | + |
| 134 | + @Test |
| 135 | + void preservesOraclePriorPosition() throws Exception { |
| 136 | + CCJSqlParser parser = CCJSqlParserUtil.newParser("PRIOR a = b"); |
| 137 | + EqualsTo condition = assertInstanceOf(EqualsTo.class, parser.Condition()); |
| 138 | + assertEquals(SupportsOldOracleJoinSyntax.ORACLE_PRIOR_START, |
| 139 | + condition.getOraclePriorPosition()); |
| 140 | + assertEquals("a", assertInstanceOf(Column.class, condition.getLeftExpression()) |
| 141 | + .getColumnName()); |
| 142 | + assertEquals("b", assertInstanceOf(Column.class, condition.getRightExpression()) |
| 143 | + .getColumnName()); |
| 144 | + assertEquals(CCJSqlParserConstants.EOF, parser.getNextToken().kind); |
| 145 | + assertSqlCanBeParsedAndDeparsed("SELECT * FROM t CONNECT BY PRIOR a = b"); |
| 146 | + } |
| 147 | + |
| 148 | + @ParameterizedTest |
| 149 | + @ValueSource(strings = {"a IN (", "a BETWEEN 1 AND", "EXISTS (SELECT", |
| 150 | + "(a, b) OVERLAPS ("}) |
| 151 | + void rejectsMalformedConditions(String sql) { |
| 152 | + assertThrows(ParseException.class, () -> CCJSqlParserUtil.newParser(sql).Condition()); |
| 153 | + assertThrows(ParseException.class, () -> CCJSqlParserUtil.newParser(sql).SQLCondition()); |
| 154 | + assertThrows(JSQLParserException.class, |
| 155 | + () -> CCJSqlParserUtil.parse("SELECT * FROM t WHERE " + sql)); |
| 156 | + } |
| 157 | + |
| 158 | + @ParameterizedTest |
| 159 | + @ValueSource(strings = {"a =", "exists ="}) |
| 160 | + void rejectsMalformedComparisons(String sql) { |
| 161 | + assertThrows(ParseException.class, () -> CCJSqlParserUtil.newParser(sql).Condition()); |
| 162 | + assertThrows(JSQLParserException.class, |
| 163 | + () -> CCJSqlParserUtil.parse("SELECT * FROM t WHERE " + sql)); |
| 164 | + } |
| 165 | + |
| 166 | + @ParameterizedTest |
| 167 | + @ValueSource(booleans = {false, true}) |
| 168 | + void honorsInterruptionBeforeConditionDispatch(boolean sqlCondition) { |
| 169 | + CCJSqlParser parser = CCJSqlParserUtil.newParser("1"); |
| 170 | + parser.interrupted = true; |
| 171 | + assertThrows(ParseException.class, () -> { |
| 172 | + if (sqlCondition) { |
| 173 | + parser.SQLCondition(); |
| 174 | + } else { |
| 175 | + parser.Condition(); |
| 176 | + } |
| 177 | + }); |
| 178 | + assertEquals("1", parser.getNextToken().image); |
| 179 | + } |
| 180 | + |
| 181 | + @ParameterizedTest |
| 182 | + @CsvSource(value = {"arg|=>", "arg|:=", "OUTER|=>", "OUTER|:=", "\"arg\"|=>", |
| 183 | + "\"arg\"|:=", "overlaps|=>", "overlaps|:="}, delimiter = '|') |
| 184 | + void preservesNamedParameterKindsInEveryArgumentPosition(String name, String assignment) |
| 185 | + throws JSQLParserException { |
| 186 | + for (String prefix : new String[] {"", "a.b, "}) { |
| 187 | + String sql = "SELECT f(" + prefix + name + " " + assignment + " value_col)"; |
| 188 | + PlainSelect select = (PlainSelect) assertSqlCanBeParsedAndDeparsed(sql); |
| 189 | + Function function = assertInstanceOf(Function.class, |
| 190 | + select.getSelectItem(0).getExpression()); |
| 191 | + int index = prefix.isEmpty() ? 0 : 1; |
| 192 | + assertEquals(index + 1, function.getParameters().size()); |
| 193 | + if (index == 1) { |
| 194 | + assertEquals("a.b", assertInstanceOf(Column.class, |
| 195 | + function.getParameters().get(0)).getFullyQualifiedName()); |
| 196 | + } |
| 197 | + Expression argument = function.getParameters().get(index); |
| 198 | + Expression value; |
| 199 | + if ("=>".equals(assignment)) { |
| 200 | + OracleNamedFunctionParameter parameter = |
| 201 | + assertInstanceOf(OracleNamedFunctionParameter.class, argument); |
| 202 | + assertEquals(name, parameter.getName()); |
| 203 | + value = parameter.getExpression(); |
| 204 | + } else { |
| 205 | + PostgresNamedFunctionParameter parameter = |
| 206 | + assertInstanceOf(PostgresNamedFunctionParameter.class, argument); |
| 207 | + assertEquals(name, parameter.getName()); |
| 208 | + value = parameter.getExpression(); |
| 209 | + } |
| 210 | + assertEquals("value_col", assertInstanceOf(Column.class, value).getColumnName()); |
| 211 | + } |
| 212 | + } |
| 213 | + |
| 214 | + @ParameterizedTest |
| 215 | + @ValueSource(strings = {"a.b => 1", "a.b := 1", "1 => 1", "1 := 1", "'a' => 1", |
| 216 | + "'a' := 1", "arg =>", "arg :="}) |
| 217 | + void rejectsInvalidNamedParametersInEveryArgumentPosition(String argument) { |
| 218 | + for (String prefix : new String[] {"", "a.b, "}) { |
| 219 | + assertThrows(JSQLParserException.class, |
| 220 | + () -> CCJSqlParserUtil.parse("SELECT f(" + prefix + argument + ")")); |
| 221 | + } |
| 222 | + } |
| 223 | +} |
0 commit comments