Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,16 @@ public class DateUnitExpression extends ASTNodeAccessImpl implements Expression

private final DateUnit type;

private final String text;

public DateUnitExpression(DateUnit type) {
this.type = Objects.requireNonNull(type);
this.text = type.toString();
}

public DateUnitExpression(String DateUnitStr) {
this.type = Objects.requireNonNull(DateUnit.from(DateUnitStr));
this.text = DateUnitStr;
}

public DateUnit getType() {
Expand All @@ -38,7 +42,7 @@ public <T, S> T accept(ExpressionVisitor<T> expressionVisitor, S context) {

@Override
public String toString() {
return type.toString();
return text;
}

public enum DateUnit {
Expand Down
51 changes: 42 additions & 9 deletions src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -1039,6 +1039,47 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
function.setExtraKeyword(getNextToken().image);
}

/**
* An unquoted YEAR, MONTH, DAY, HOUR, MINUTE or SECOND passed on its own as a function argument, as in
* DATEADD(day, 1, d), is a date unit. Anywhere else it is an ordinary identifier, so it is parsed as a
* Column and only converted here, once it is known to be a whole argument.
*/
private void normalizeDateUnitArguments(Function function) {
ExpressionList<?> parameters = function.getParameters();
if (parameters == null || parameters instanceof ParenthesedExpressionList) {
return;
}
ExpressionList<Expression> normalized = null;
for (int i = 0; i < parameters.size(); i++) {
if (!isDateUnitColumn(parameters.get(i))) {
continue;
}
Column column = (Column) parameters.get(i);
DateUnitExpression unit = new DateUnitExpression(column.getColumnName());
linkAST(unit, column.getASTNode());
if (normalized == null) {
normalized = new ExpressionList<Expression>(parameters);
normalized.setASTNode(parameters.getASTNode());
}
normalized.set(i, unit);
}
if (normalized != null) {
function.setParameters(normalized);
}
}

private static boolean isDateUnitColumn(Expression expression) {
if (!(expression instanceof Column)) {
return false;
}
Node node = ((Column) expression).getASTNode();
if (node == null) {
return false;
}
Token first = node.jjtGetFirstToken();
return first != null && first == node.jjtGetLastToken() && first.kind == K_DATE_LITERAL;
}

private void normalizeOdbcTimestampInterval(Function function) {
if (!function.isEscaped() || function.getMultipartName().size() != 1
|| !("TIMESTAMPADD".equalsIgnoreCase(function.getName())
Expand Down Expand Up @@ -12074,8 +12115,6 @@ Expression PrimaryExpression() #PrimaryExpression:
| LOOKAHEAD({ !interrupted && isFunctionAhead() })
retval = Function() [ LOOKAHEAD(2) retval = AnalyticExpression( (Function) retval ) ]

| LOOKAHEAD(2) retval = DateUnitExpression()

| LOOKAHEAD(2, {!interrupted}) retval = IntervalExpression() { dateExpressionAllowed = false; }

| token=<S_DOUBLE> { retval = new DoubleValue(token.image); }
Expand Down Expand Up @@ -12394,13 +12433,6 @@ DateTimeLiteralExpression DateTimeLiteralExpression() : {
}
}

DateUnitExpression DateUnitExpression() : {
Token t;
}
{
t=<K_DATE_LITERAL> { return new DateUnitExpression( t.image ); }
}

RangeExpression RangeExpression(Expression startExpression):
{
Expression endExpression;
Expand Down Expand Up @@ -14052,6 +14084,7 @@ Function Function() #Function:
| function = InternalFunction(false)
)
{
normalizeDateUnitArguments(function);
linkAST(function,jjtThis);
return function;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
package net.sf.jsqlparser.expression;

import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.operators.arithmetic.Addition;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.update.Update;
import net.sf.jsqlparser.test.TestUtils;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
Expand All @@ -27,4 +30,67 @@ void testParsing() throws JSQLParserException {
Function f = select.getSelectItem(0).getExpression(Function.class);
Assertions.assertThat(f.getParameters().get(1)).isInstanceOf(DateUnitExpression.class);
}

@Test
void testUnitArgumentKeepsItsSpelling() throws JSQLParserException {
String sqlStr = "SELECT DATEADD(day, 1, d) FROM t";

PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr);

Function f = select.getSelectItem(0).getExpression(Function.class);
DateUnitExpression unit = (DateUnitExpression) f.getParameters().get(0);
Assertions.assertThat(unit.getType()).isEqualTo(DateUnitExpression.DateUnit.DAY);
Assertions.assertThat(unit).hasToString("day");
}

@Test
void testUnitWordOutsideFunctionArgumentsIsColumn() throws JSQLParserException {
String sqlStr = "SELECT year FROM t";

PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr);

Column column = select.getSelectItem(0).getExpression(Column.class);
Assertions.assertThat(column.getColumnName()).isEqualTo("year");
}

@Test
void testUnitWordAsQualifierIsColumn() throws JSQLParserException {
String sqlStr = "SELECT month.amount FROM sales month";

PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr);

Column column = select.getSelectItem(0).getExpression(Column.class);
Assertions.assertThat(column.getFullyQualifiedName()).isEqualTo("month.amount");
}

@Test
void testUnitWordWithinArgumentExpressionIsColumn() throws JSQLParserException {
String sqlStr = "SELECT COALESCE(year + 1, 0) FROM t";

PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr);

Function f = select.getSelectItem(0).getExpression(Function.class);
Addition addition = (Addition) f.getParameters().get(0);
Assertions.assertThat(addition.getLeftExpression()).isInstanceOf(Column.class);
}

@Test
void testQuotedUnitWordArgumentIsColumn() throws JSQLParserException {
String sqlStr = "SELECT DATEADD(\"day\", 1, d) FROM t";

PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr);

Function f = select.getSelectItem(0).getExpression(Function.class);
Assertions.assertThat(f.getParameters().get(0)).isInstanceOf(Column.class);
}

@Test
void testUnitWordInUpdateSetTupleIsColumn() throws JSQLParserException {
String sqlStr = "UPDATE t SET (a, year) = (1, 2)";

Update update = (Update) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr);

Assertions.assertThat(update.getUpdateSets().get(0).getColumn(1).getColumnName())
.isEqualTo("year");
}
}
Loading