diff --git a/src/main/java/net/sf/jsqlparser/expression/IntervalExpression.java b/src/main/java/net/sf/jsqlparser/expression/IntervalExpression.java index 9c028c769..022b269de 100644 --- a/src/main/java/net/sf/jsqlparser/expression/IntervalExpression.java +++ b/src/main/java/net/sf/jsqlparser/expression/IntervalExpression.java @@ -11,6 +11,7 @@ import net.sf.jsqlparser.parser.ASTNodeAccessImpl; +import java.io.Serializable; import java.util.Objects; public class IntervalExpression extends ASTNodeAccessImpl implements Expression { @@ -18,6 +19,7 @@ public class IntervalExpression extends ASTNodeAccessImpl implements Expression private final boolean intervalKeyword; private String parameter = null; private String intervalType = null; + private IntervalQualifier intervalQualifier = null; private Expression expression = null; public IntervalExpression() { @@ -47,12 +49,32 @@ public void setParameter(String parameter) { this.parameter = parameter; } + /** + * Returns the interval type for backwards compatibility. If a structured qualifier is present, + * its SQL representation is returned. + */ public String getIntervalType() { - return intervalType; + return intervalQualifier != null ? intervalQualifier.toString() : intervalType; } + /** + * Sets the legacy interval type and clears any structured qualifier. + */ public void setIntervalType(String intervalType) { this.intervalType = intervalType; + this.intervalQualifier = null; + } + + public IntervalQualifier getIntervalQualifier() { + return intervalQualifier; + } + + /** + * Sets the structured interval qualifier and clears the legacy interval type. + */ + public void setIntervalQualifier(IntervalQualifier intervalQualifier) { + this.intervalQualifier = intervalQualifier; + this.intervalType = null; } public Expression getExpression() { @@ -67,7 +89,9 @@ public void setExpression(Expression expression) { public String toString() { return (intervalKeyword ? "INTERVAL " : "") + Objects.toString(expression, parameter) - + (intervalType != null ? " " + intervalType : ""); + + (intervalQualifier != null + ? " " + intervalQualifier.toString() + : (intervalType != null ? " " + intervalType : "")); } @Override @@ -85,6 +109,11 @@ public IntervalExpression withIntervalType(String intervalType) { return this; } + public IntervalExpression withIntervalQualifier(IntervalQualifier intervalQualifier) { + this.setIntervalQualifier(intervalQualifier); + return this; + } + public IntervalExpression withExpression(Expression expression) { this.setExpression(expression); return this; @@ -93,4 +122,121 @@ public IntervalExpression withExpression(Expression expression) { public E getExpression(Class type) { return type.cast(getExpression()); } + + /** + * Models an SQL interval qualifier as defined by the SQL standard (see + * PostgreSQL: Interval + * Input), e.g. {@code YEAR}, {@code DAY TO SECOND}, {@code DAY(9) TO SECOND} or + * {@code SECOND(2, 4)}. + *

+ * It is shared by {@link IntervalExpression} (the {@code INTERVAL ...} expression literal, used + * in DML) and by {@code net.sf.jsqlparser.statement.create.table.ColDataType} (the + * {@code INTERVAL} column / cast target type, used in DDL), so the qualifier is represented + * once and round-trips consistently across all contexts. + */ + public static class IntervalQualifier implements Serializable { + + private final String leadingField; + private final Integer leadingFieldPrecision; + private final String trailingField; + private final Integer fractionalSecondsPrecision; + + public IntervalQualifier( + String leadingField, Integer leadingFieldPrecision, String trailingField, + Integer fractionalSecondsPrecision) { + this.leadingField = leadingField; + this.leadingFieldPrecision = leadingFieldPrecision; + this.trailingField = trailingField; + this.fractionalSecondsPrecision = fractionalSecondsPrecision; + } + + public String getLeadingField() { + return leadingField; + } + + public Integer getLeadingFieldPrecision() { + return leadingFieldPrecision; + } + + public String getTrailingField() { + return trailingField; + } + + public Integer getFractionalSecondsPrecision() { + return fractionalSecondsPrecision; + } + + public IntervalQualifier withLeadingField(String leadingField) { + return new IntervalQualifier( + leadingField, leadingFieldPrecision, trailingField, + fractionalSecondsPrecision); + } + + public IntervalQualifier withLeadingFieldPrecision(Integer leadingFieldPrecision) { + return new IntervalQualifier( + leadingField, leadingFieldPrecision, trailingField, + fractionalSecondsPrecision); + } + + public IntervalQualifier withTrailingField(String trailingField) { + return new IntervalQualifier( + leadingField, leadingFieldPrecision, trailingField, + fractionalSecondsPrecision); + } + + public IntervalQualifier withFractionalSecondsPrecision( + Integer fractionalSecondsPrecision) { + return new IntervalQualifier( + leadingField, leadingFieldPrecision, trailingField, + fractionalSecondsPrecision); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof IntervalQualifier)) { + return false; + } + IntervalQualifier that = (IntervalQualifier) o; + return Objects.equals(leadingField, that.leadingField) + && Objects.equals(leadingFieldPrecision, that.leadingFieldPrecision) + && Objects.equals(trailingField, that.trailingField) + && Objects.equals( + fractionalSecondsPrecision, that.fractionalSecondsPrecision); + } + + @Override + public int hashCode() { + return Objects.hash( + leadingField, leadingFieldPrecision, trailingField, + fractionalSecondsPrecision); + } + + /** + * Renders the qualifier back to SQL, e.g. {@code DAY}, {@code DAY TO SECOND}, + * {@code DAY(9) TO SECOND} or {@code SECOND(2, 4)}. + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(Objects.toString(leadingField, "")); + if (leadingFieldPrecision != null) { + sb.append("(").append(leadingFieldPrecision); + // For a single-field qualifier such as SECOND(2, 4), the fractional seconds + // precision is rendered together with the leading precision. + if (trailingField == null && fractionalSecondsPrecision != null) { + sb.append(", ").append(fractionalSecondsPrecision); + } + sb.append(")"); + } + if (trailingField != null) { + sb.append(" TO ").append(trailingField); + if (fractionalSecondsPrecision != null) { + sb.append("(").append(fractionalSecondsPrecision).append(")"); + } + } + return sb.toString(); + } + } } diff --git a/src/main/java/net/sf/jsqlparser/statement/create/table/ColDataType.java b/src/main/java/net/sf/jsqlparser/statement/create/table/ColDataType.java index b7b7a9ae9..436c54939 100644 --- a/src/main/java/net/sf/jsqlparser/statement/create/table/ColDataType.java +++ b/src/main/java/net/sf/jsqlparser/statement/create/table/ColDataType.java @@ -9,6 +9,7 @@ */ package net.sf.jsqlparser.statement.create.table; +import net.sf.jsqlparser.expression.IntervalExpression.IntervalQualifier; import net.sf.jsqlparser.statement.select.PlainSelect; import java.io.Serializable; @@ -26,6 +27,7 @@ public class ColDataType implements Serializable { private String dataType; private List argumentsStringList; private String characterSet; + private IntervalQualifier intervalQualifier; private List arrayData = new ArrayList(); public ColDataType() { @@ -76,6 +78,14 @@ public void setCharacterSet(String characterSet) { this.characterSet = characterSet; } + public IntervalQualifier getIntervalQualifier() { + return intervalQualifier; + } + + public void setIntervalQualifier(IntervalQualifier intervalQualifier) { + this.intervalQualifier = intervalQualifier; + } + public List getArrayData() { return arrayData; } @@ -95,6 +105,7 @@ public String toString() { arraySpec.append("]"); } return dataType + + (intervalQualifier != null ? " " + intervalQualifier.toString() : "") + (argumentsStringList != null ? " " + PlainSelect.getStringList(argumentsStringList, true, true) : "") @@ -117,6 +128,11 @@ public ColDataType withCharacterSet(String characterSet) { return this; } + public ColDataType withIntervalQualifier(IntervalQualifier intervalQualifier) { + this.setIntervalQualifier(intervalQualifier); + return this; + } + public ColDataType withArrayData(List arrayData) { this.setArrayData(arrayData); return this; @@ -161,6 +177,7 @@ public final boolean equals(Object o) { return dataType.equalsIgnoreCase(that.dataType) && Objects.equals(argumentsStringList, that.argumentsStringList) && Objects.equals(characterSet, that.characterSet) + && Objects.equals(intervalQualifier, that.intervalQualifier) && Objects.equals(arrayData, that.arrayData); } @@ -169,6 +186,7 @@ public int hashCode() { int result = dataType.hashCode(); result = 31 * result + Objects.hashCode(argumentsStringList); result = 31 * result + Objects.hashCode(characterSet); + result = 31 * result + Objects.hashCode(intervalQualifier); result = 31 * result + Objects.hashCode(arrayData); return result; } diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java index 184088fe7..00e01cccf 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java @@ -1320,7 +1320,9 @@ public StringBuilder visit(IntervalExpression intervalExpression, S context) } else { builder.append(intervalExpression.getParameter()); } - if (intervalExpression.getIntervalType() != null) { + if (intervalExpression.getIntervalQualifier() != null) { + builder.append(" ").append(intervalExpression.getIntervalQualifier().toString()); + } else if (intervalExpression.getIntervalType() != null) { builder.append(" ").append(intervalExpression.getIntervalType()); } return builder; diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 47903e9a5..465e63b16 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -9065,6 +9065,7 @@ IntervalExpression IntervalExpression() : { Token token = null; Expression expr = null; boolean signed = false; + IntervalExpression.IntervalQualifier qualifier = null; } { @@ -9088,7 +9089,13 @@ IntervalExpression IntervalExpression() : { interval.setParameter((signed?"-":"") + token.image); } } - [ LOOKAHEAD(2) (token = | token = ) { interval.setIntervalType(token.image); } ] + [ + // Standard qualifier (field [TO field] [(precision)]) such as DAY or DAY TO SECOND. + LOOKAHEAD(2, { getToken(1).kind == K_DATE_LITERAL }) qualifier = IntervalQualifier() { interval.setIntervalQualifier(qualifier); } + // Non-standard single identifier field (e.g. MySQL `INTERVAL 1 foo`), kept for + // backwards compatibility. + | LOOKAHEAD(2) (token = ) { interval.setIntervalType(token.image); } + ] { return interval; } @@ -9096,7 +9103,7 @@ IntervalExpression IntervalExpression() : { IntervalExpression IntervalExpressionWithoutInterval(Expression expr) : { IntervalExpression interval; - Token token; + IntervalExpression.IntervalQualifier qualifier = null; boolean signed = false; } { @@ -9104,13 +9111,45 @@ IntervalExpression IntervalExpressionWithoutInterval(Expression expr) : { interval = new IntervalExpression(false); interval.setExpression(expr); } - token = + qualifier = IntervalQualifier() { - interval.setIntervalType(token.image); + interval.setIntervalQualifier(qualifier); return interval; } } +/** + * Parses an SQL interval qualifier, e.g. {@code DAY}, {@code DAY TO SECOND}, + * {@code DAY(9) TO SECOND} or {@code SECOND(2, 4)}, as defined by the SQL standard. + * Shared by {@link IntervalExpression} (DML interval literals) and by {@code ColDataType} + * (DDL {@code INTERVAL} column / cast target types). + */ +IntervalExpression.IntervalQualifier IntervalQualifier() : { + Token field = null; + Token toField = null; + Token leadingPrecision = null; + Token fractionalPrecision = null; +} +{ + field = + [ + "(" leadingPrecision = + [ "," fractionalPrecision = ] + ")" + ] + [ + toField = + [ "(" fractionalPrecision = ")" ] + ] + { + return new IntervalExpression.IntervalQualifier( + field != null ? field.image : null, + leadingPrecision != null ? Integer.valueOf(leadingPrecision.image) : null, + toField != null ? toField.image : null, + fractionalPrecision != null ? Integer.valueOf(fractionalPrecision.image) : null); + } +} + KeepExpression KeepExpression() : { KeepExpression keep = new KeepExpression(); Token token; @@ -10818,6 +10857,7 @@ ColDataType ColDataType(): List name; ColDataType arrayType; ColDataType nestedType = null; + IntervalExpression.IntervalQualifier intervalQualifier = null; int precision = -1; int scale = -1; @@ -10859,6 +10899,15 @@ ColDataType ColDataType(): | tk= ) { schema = tk.image; } + // Consume an optional INTERVAL qualifier such as `hour to minute` or + // `day(9) to second`. Only applicable when the matched type is an INTERVAL and + // is immediately followed by a field word, so other types (and the existing + // `interval (2)` precision form handled below) are unaffected. + [ + LOOKAHEAD({ "interval".equalsIgnoreCase(schema) + && getToken(1).kind == K_DATE_LITERAL }) + intervalQualifier = IntervalQualifier() { colDataType.setIntervalQualifier(intervalQualifier); } + ] [ LOOKAHEAD(2) "." arrayType = ColDataType() { schema += "." + arrayType.toString(); } ] { colDataType.setDataType(schema); } ) diff --git a/src/test/java/net/sf/jsqlparser/expression/IntervalExpressionTest.java b/src/test/java/net/sf/jsqlparser/expression/IntervalExpressionTest.java index 30645789f..bbd2f64ca 100644 --- a/src/test/java/net/sf/jsqlparser/expression/IntervalExpressionTest.java +++ b/src/test/java/net/sf/jsqlparser/expression/IntervalExpressionTest.java @@ -10,9 +10,16 @@ package net.sf.jsqlparser.expression; import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.IntervalExpression.IntervalQualifier; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.select.Select; import net.sf.jsqlparser.test.TestUtils; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + class IntervalExpressionTest { @@ -24,4 +31,97 @@ void testExtractExpressionIssue2172() throws JSQLParserException { sqlStr = "SELECT UNIX_TIMESTAMP(date_sub(date_sub(date_format(now(),'%y-%m-%d'),interval extract(day from now())-1 day),interval 1 month))*1000"; TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); } + + // ---- Interval qualifier support (issue #1728 / SQL standard) ---- + + @Test + void testIntervalSingleFieldRoundTrip() throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT INTERVAL '1' HOUR", true); + } + + @Test + void testIntervalFieldToFieldRoundTrip() throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT INTERVAL '1' HOUR TO MINUTE", true); + TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT INTERVAL '4 5:12' DAY TO MINUTE", true); + } + + @Test + void testIntervalFieldWithPrecisionRoundTrip() throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT INTERVAL '400' DAY(3)", true); + } + + @Test + void testIntervalFieldToFieldWithPrecisionRoundTrip() throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT INTERVAL '4 5:12:10.222' DAY TO SECOND(3)", true); + TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT INTERVAL '400 5' DAY(3) TO HOUR", true); + } + + @Test + void testIntervalSecondWithLeadingAndFractionalPrecisionRoundTrip() throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT INTERVAL '30.12345' SECOND(2, 4)", true); + } + + @Test + void testIntervalSingleFieldKeepsLegacyGetter() throws JSQLParserException { + String sql = "SELECT INTERVAL '1' DAY"; + Select select = (Select) TestUtils.assertSqlCanBeParsedAndDeparsed(sql, true); + IntervalExpression interval = (IntervalExpression) select.getPlainSelect() + .getSelectItems().get(0).getExpression(); + + assertEquals("DAY", interval.getIntervalType()); + assertEquals("INTERVAL '1' DAY", interval.toString()); + TestUtils.assertExpressionCanBeDeparsedAs(interval, "INTERVAL '1' DAY"); + } + + @Test + void testIntervalQualifierStructurallyAttached() throws JSQLParserException { + String sql = "SELECT INTERVAL '1' HOUR TO MINUTE"; + Select select = (Select) TestUtils.assertSqlCanBeParsedAndDeparsed(sql, true); + IntervalExpression interval = (IntervalExpression) select.getPlainSelect() + .getSelectItems().get(0).getExpression(); + assertEquals("HOUR TO MINUTE", interval.getIntervalType()); + IntervalQualifier qualifier = interval.getIntervalQualifier(); + assertNotNull(qualifier, "qualifier must be a structured property of the interval"); + assertEquals("HOUR", qualifier.getLeadingField()); + assertEquals("MINUTE", qualifier.getTrailingField()); + assertEquals("HOUR TO MINUTE", qualifier.toString()); + TestUtils.assertExpressionCanBeDeparsedAs(interval, "INTERVAL '1' HOUR TO MINUTE"); + } + + @Test + void testLegacySetterOverridesStructuredQualifier() throws JSQLParserException { + Select select = (Select) CCJSqlParserUtil.parse("SELECT INTERVAL '1' DAY"); + IntervalExpression interval = (IntervalExpression) select.getPlainSelect() + .getSelectItems().get(0).getExpression(); + + interval.setIntervalType("HOUR"); + + assertNull(interval.getIntervalQualifier()); + assertEquals("HOUR", interval.getIntervalType()); + assertEquals("INTERVAL '1' HOUR", interval.toString()); + TestUtils.assertExpressionCanBeDeparsedAs(interval, "INTERVAL '1' HOUR"); + TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT " + interval, true); + } + + @Test + void testStructuredSetterOverridesLegacyType() throws JSQLParserException { + IntervalExpression interval = new IntervalExpression() + .withExpression(new StringValue("1")) + .withIntervalType("DAY"); + + interval.withIntervalQualifier(new IntervalQualifier("HOUR", null, "MINUTE", null)); + + assertEquals("HOUR TO MINUTE", interval.getIntervalType()); + assertEquals("INTERVAL '1' HOUR TO MINUTE", interval.toString()); + TestUtils.assertExpressionCanBeDeparsedAs(interval, "INTERVAL '1' HOUR TO MINUTE"); + TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT " + interval, true); + } + + @Test + void testPostfixIntervalQualifierRoundTrip() throws JSQLParserException { + // Oracle-style postfix qualifier: (expr) DAY(9) TO SECOND + TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT (systimestamp - order_date) DAY(9) TO SECOND FROM orders", true); + } } diff --git a/src/test/java/net/sf/jsqlparser/statement/create/table/ColDataTypeTest.java b/src/test/java/net/sf/jsqlparser/statement/create/table/ColDataTypeTest.java index 5fdae46c2..535a61a82 100644 --- a/src/test/java/net/sf/jsqlparser/statement/create/table/ColDataTypeTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/create/table/ColDataTypeTest.java @@ -10,10 +10,13 @@ package net.sf.jsqlparser.statement.create.table; import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.IntervalExpression.IntervalQualifier; import net.sf.jsqlparser.test.TestUtils; import org.junit.jupiter.api.Test; import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; class ColDataTypeTest { @Test @@ -57,4 +60,41 @@ void testStruct() throws JSQLParserException { " );\n"; assertSqlCanBeParsedAndDeparsed(sqlStr, true); } + + // ---- INTERVAL column type with qualifier (issue #1728 / SQL standard) ---- + + @Test + void testIntervalColumnFieldToFieldRoundTrip() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("CREATE TABLE t (a interval hour to minute)", true); + assertSqlCanBeParsedAndDeparsed("CREATE TABLE t (a interval year to month)", true); + assertSqlCanBeParsedAndDeparsed("CREATE TABLE t (a interval day to second)", true); + } + + @Test + void testIntervalColumnBarePrecisionUnaffected() throws JSQLParserException { + // the leading-field-less precision form must keep working + assertSqlCanBeParsedAndDeparsed("CREATE TABLE t (a interval(2))", true); + } + + @Test + void testIntervalQualifierStructurallyAttachedToColumnType() throws JSQLParserException { + CreateTable create = (CreateTable) assertSqlCanBeParsedAndDeparsed( + "CREATE TABLE t (len interval hour to minute)", true); + ColumnDefinition len = create.getColumnDefinitions().stream() + .filter(c -> c.getColumnName().equalsIgnoreCase("len")) + .findFirst() + .orElseThrow(); + // The qualifier must be a structured property of the column type, not a bare string. + IntervalQualifier qualifier = len.getColDataType().getIntervalQualifier(); + assertNotNull(qualifier); + assertEquals("hour", qualifier.getLeadingField()); + assertEquals("minute", qualifier.getTrailingField()); + } + + @Test + void testCastAsIntervalWithQualifierRoundTrip() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed( + "SELECT CAST(col AS INTERVAL DAY TO SECOND)", true); + assertSqlCanBeParsedAndDeparsed("SELECT CAST(col AS INTERVAL HOUR)", true); + } } diff --git a/src/test/java/net/sf/jsqlparser/statement/select/SpecialOracleTest.java b/src/test/java/net/sf/jsqlparser/statement/select/SpecialOracleTest.java index 4c95872f8..2650c5cd1 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/SpecialOracleTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/SpecialOracleTest.java @@ -98,7 +98,8 @@ public class SpecialOracleTest { "groupby13.sql", "groupby14.sql", "groupby15.sql", "groupby16.sql", "groupby17.sql", "groupby19.sql", "groupby20.sql", "groupby21.sql", "groupby22.sql", "groupby23.sql", "insert02.sql", "insert04.sql", "insert05.sql", "insert06.sql", "insert07.sql", - "insert11.sql", "insert12.sql", "interval02.sql", "interval04.sql", + "insert11.sql", "insert12.sql", "interval01.sql", "interval02.sql", + "interval03.sql", "interval04.sql", "interval05.sql", "join01.sql", "join02.sql", "join03.sql", "join04.sql", "join06.sql", "join07.sql", "join08.sql", "join09.sql", "join10.sql", "join11.sql", "join12.sql", "join13.sql", "join14.sql", diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/interval01.sql b/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/interval01.sql index 4ad2121c9..978d421f7 100644 --- a/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/interval01.sql +++ b/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/interval01.sql @@ -10,7 +10,4 @@ select (systimestamp - order_date) day(9) to second from orders where order_id = 2458 ---@FAILURE: Encountered unexpected token: "(" "(" recorded first on Aug 3, 2021, 7:20:08 AM ---@FAILURE: Encountered: "(" / "(", at line 10, column 39, in lexical state DEFAULT. recorded first on 15 May 2025, 16:24:08 ---@FAILURE: Encountered: / "(", at line 10, column 39, in lexical state DEFAULT. recorded first on 9 Jul 2025, 17:09:17 ---@FAILURE: --- recorded first on 11 Apr 2026, 22:18:19 \ No newline at end of file +--@SUCCESSFULLY_PARSED_AND_DEPARSED first on 2026年8月10日 上午11:23:23 \ No newline at end of file diff --git a/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/interval03.sql b/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/interval03.sql index 25e2b711a..cfae5bf60 100644 --- a/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/interval03.sql +++ b/src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/interval03.sql @@ -25,6 +25,4 @@ select ,interval :a day from dual ---@FAILURE: Encountered unexpected token: "second" recorded first on Aug 3, 2021, 7:20:08 AM ---@FAILURE: Encountered: / "second", at line 11, column 34, in lexical state DEFAULT. recorded first on 15 May 2025, 16:24:08 ---@FAILURE: --- recorded first on 11 Apr 2026, 22:18:19 \ No newline at end of file +--@SUCCESSFULLY_PARSED_AND_DEPARSED first on 2026年8月10日 上午11:23:23 \ No newline at end of file