Skip to content

Commit a604618

Browse files
committed
fix: model interval qualifier as a structured property (#1728)
IntervalQualifier is promoted to a top-level type and made the canonical representation of an SQL interval qualifier, shared by IntervalExpression (DML literals), ColDataType (DDL column types / cast targets) and the Oracle postfix form. * net.sf.jsqlparser.expression.IntervalQualifier models the SQL-standard form: single field, field TO field, leading-field precision and fractional-seconds precision, with value equals/hashCode and SQL rendering. It is attached to IntervalExpression and ColDataType. * The legacy getIntervalType()/setIntervalType()/withIntervalType() are @deprecated and kept only for the non-standard single-identifier form (e.g. MySQL INTERVAL 1 foo); the structured qualifier is canonical. * The shared IntervalQualifier() grammar production uses LOOKAHEAD(2) on its optionals and rejects contradictory fractional-seconds precision (e.g. SECOND(2, 4) TO SECOND(3)) instead of silently dropping a value. * Turns the long-standing Oracle interval01.sql and interval03.sql failures green (full qualifier matrix including second(2,4)). Fixes #1728 Signed-off-by: 付典 <fudianchn@gmail.com>
1 parent 32a7949 commit a604618

10 files changed

Lines changed: 433 additions & 14 deletions

File tree

src/main/java/net/sf/jsqlparser/expression/IntervalExpression.java

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public class IntervalExpression extends ASTNodeAccessImpl implements Expression
1818
private final boolean intervalKeyword;
1919
private String parameter = null;
2020
private String intervalType = null;
21+
private IntervalQualifier intervalQualifier = null;
2122
private Expression expression = null;
2223

2324
public IntervalExpression() {
@@ -47,12 +48,42 @@ public void setParameter(String parameter) {
4748
this.parameter = parameter;
4849
}
4950

51+
/**
52+
* Returns the non-standard single-identifier interval type (e.g. MySQL {@code INTERVAL 1 foo}),
53+
* or {@code null} when a structured {@link IntervalQualifier} is present.
54+
*
55+
* @deprecated the structured {@link #getIntervalQualifier()} is the canonical representation.
56+
* This legacy field is kept only for the non-standard single-identifier form.
57+
* @return the legacy interval type, or {@code null} if a qualifier is set
58+
*/
59+
@Deprecated
5060
public String getIntervalType() {
5161
return intervalType;
5262
}
5363

64+
/**
65+
* Sets the non-standard single-identifier interval type (e.g. MySQL {@code INTERVAL 1 foo}) and
66+
* clears any structured qualifier.
67+
*
68+
* @deprecated use {@link #setIntervalQualifier(IntervalQualifier)} for the standard form.
69+
* @param intervalType the legacy interval type
70+
*/
71+
@Deprecated
5472
public void setIntervalType(String intervalType) {
5573
this.intervalType = intervalType;
74+
this.intervalQualifier = null;
75+
}
76+
77+
public IntervalQualifier getIntervalQualifier() {
78+
return intervalQualifier;
79+
}
80+
81+
/**
82+
* Sets the structured interval qualifier and clears the legacy interval type.
83+
*/
84+
public void setIntervalQualifier(IntervalQualifier intervalQualifier) {
85+
this.intervalQualifier = intervalQualifier;
86+
this.intervalType = null;
5687
}
5788

5889
public Expression getExpression() {
@@ -67,7 +98,9 @@ public void setExpression(Expression expression) {
6798
public String toString() {
6899
return (intervalKeyword ? "INTERVAL " : "")
69100
+ Objects.toString(expression, parameter)
70-
+ (intervalType != null ? " " + intervalType : "");
101+
+ (intervalQualifier != null
102+
? " " + intervalQualifier.toString()
103+
: (intervalType != null ? " " + intervalType : ""));
71104
}
72105

73106
@Override
@@ -80,11 +113,22 @@ public IntervalExpression withParameter(String parameter) {
80113
return this;
81114
}
82115

116+
/**
117+
* @deprecated use {@link #withIntervalQualifier(IntervalQualifier)} for the standard form.
118+
* @param intervalType the legacy interval type
119+
* @return this instance
120+
*/
121+
@Deprecated
83122
public IntervalExpression withIntervalType(String intervalType) {
84123
this.setIntervalType(intervalType);
85124
return this;
86125
}
87126

127+
public IntervalExpression withIntervalQualifier(IntervalQualifier intervalQualifier) {
128+
this.setIntervalQualifier(intervalQualifier);
129+
return this;
130+
}
131+
88132
public IntervalExpression withExpression(Expression expression) {
89133
this.setExpression(expression);
90134
return this;
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/*-
2+
* #%L
3+
* JSQLParser library
4+
* %%
5+
* Copyright (C) 2004 - 2019 JSQLParser
6+
* %%
7+
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
8+
* #L%
9+
*/
10+
package net.sf.jsqlparser.expression;
11+
12+
import java.io.Serializable;
13+
import java.util.Objects;
14+
15+
/**
16+
* Models an SQL interval qualifier as defined by the SQL standard (see
17+
* <a href="https://www.postgresql.org/docs/current/datatype-datetime.html">PostgreSQL: Interval
18+
* Input</a>), e.g. {@code YEAR}, {@code DAY TO SECOND}, {@code DAY(9) TO SECOND} or
19+
* {@code SECOND(2, 4)}.
20+
* <p>
21+
* It is shared by {@link IntervalExpression} (the {@code INTERVAL ...} expression literal, used in
22+
* DML) and by {@code net.sf.jsqlparser.statement.create.table.ColDataType} (the {@code INTERVAL}
23+
* column / cast target type, used in DDL), so the qualifier is represented once and round-trips
24+
* consistently across all contexts.
25+
*/
26+
public final class IntervalQualifier implements Serializable {
27+
28+
private final String leadingField;
29+
private final Integer leadingFieldPrecision;
30+
private final String trailingField;
31+
private final Integer fractionalSecondsPrecision;
32+
33+
public IntervalQualifier(
34+
String leadingField, Integer leadingFieldPrecision, String trailingField,
35+
Integer fractionalSecondsPrecision) {
36+
this.leadingField = leadingField;
37+
this.leadingFieldPrecision = leadingFieldPrecision;
38+
this.trailingField = trailingField;
39+
this.fractionalSecondsPrecision = fractionalSecondsPrecision;
40+
}
41+
42+
public String getLeadingField() {
43+
return leadingField;
44+
}
45+
46+
public Integer getLeadingFieldPrecision() {
47+
return leadingFieldPrecision;
48+
}
49+
50+
public String getTrailingField() {
51+
return trailingField;
52+
}
53+
54+
public Integer getFractionalSecondsPrecision() {
55+
return fractionalSecondsPrecision;
56+
}
57+
58+
public IntervalQualifier withLeadingField(String leadingField) {
59+
return new IntervalQualifier(
60+
leadingField, leadingFieldPrecision, trailingField, fractionalSecondsPrecision);
61+
}
62+
63+
public IntervalQualifier withLeadingFieldPrecision(Integer leadingFieldPrecision) {
64+
return new IntervalQualifier(
65+
leadingField, leadingFieldPrecision, trailingField, fractionalSecondsPrecision);
66+
}
67+
68+
public IntervalQualifier withTrailingField(String trailingField) {
69+
return new IntervalQualifier(
70+
leadingField, leadingFieldPrecision, trailingField, fractionalSecondsPrecision);
71+
}
72+
73+
public IntervalQualifier withFractionalSecondsPrecision(Integer fractionalSecondsPrecision) {
74+
return new IntervalQualifier(
75+
leadingField, leadingFieldPrecision, trailingField, fractionalSecondsPrecision);
76+
}
77+
78+
@Override
79+
public boolean equals(Object o) {
80+
if (this == o) {
81+
return true;
82+
}
83+
if (!(o instanceof IntervalQualifier)) {
84+
return false;
85+
}
86+
IntervalQualifier that = (IntervalQualifier) o;
87+
return Objects.equals(leadingField, that.leadingField)
88+
&& Objects.equals(leadingFieldPrecision, that.leadingFieldPrecision)
89+
&& Objects.equals(trailingField, that.trailingField)
90+
&& Objects.equals(fractionalSecondsPrecision, that.fractionalSecondsPrecision);
91+
}
92+
93+
@Override
94+
public int hashCode() {
95+
return Objects.hash(
96+
leadingField, leadingFieldPrecision, trailingField, fractionalSecondsPrecision);
97+
}
98+
99+
/**
100+
* Renders the qualifier back to SQL, e.g. {@code DAY}, {@code DAY TO SECOND},
101+
* {@code DAY(9) TO SECOND} or {@code SECOND(2, 4)}.
102+
*/
103+
@Override
104+
public String toString() {
105+
StringBuilder sb = new StringBuilder(Objects.toString(leadingField, ""));
106+
if (leadingFieldPrecision != null) {
107+
sb.append("(").append(leadingFieldPrecision);
108+
// For a single-field qualifier such as SECOND(2, 4), the fractional seconds
109+
// precision is rendered together with the leading precision.
110+
if (trailingField == null && fractionalSecondsPrecision != null) {
111+
sb.append(", ").append(fractionalSecondsPrecision);
112+
}
113+
sb.append(")");
114+
}
115+
if (trailingField != null) {
116+
sb.append(" TO ").append(trailingField);
117+
if (fractionalSecondsPrecision != null) {
118+
sb.append("(").append(fractionalSecondsPrecision).append(")");
119+
}
120+
}
121+
return sb.toString();
122+
}
123+
}

src/main/java/net/sf/jsqlparser/statement/create/table/ColDataType.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
*/
1010
package net.sf.jsqlparser.statement.create.table;
1111

12+
import net.sf.jsqlparser.expression.IntervalQualifier;
1213
import net.sf.jsqlparser.statement.select.PlainSelect;
1314

1415
import java.io.Serializable;
@@ -26,6 +27,7 @@ public class ColDataType implements Serializable {
2627
private String dataType;
2728
private List<String> argumentsStringList;
2829
private String characterSet;
30+
private IntervalQualifier intervalQualifier;
2931
private List<Integer> arrayData = new ArrayList<Integer>();
3032

3133
public ColDataType() {
@@ -76,6 +78,14 @@ public void setCharacterSet(String characterSet) {
7678
this.characterSet = characterSet;
7779
}
7880

81+
public IntervalQualifier getIntervalQualifier() {
82+
return intervalQualifier;
83+
}
84+
85+
public void setIntervalQualifier(IntervalQualifier intervalQualifier) {
86+
this.intervalQualifier = intervalQualifier;
87+
}
88+
7989
public List<Integer> getArrayData() {
8090
return arrayData;
8191
}
@@ -95,6 +105,7 @@ public String toString() {
95105
arraySpec.append("]");
96106
}
97107
return dataType
108+
+ (intervalQualifier != null ? " " + intervalQualifier.toString() : "")
98109
+ (argumentsStringList != null
99110
? " " + PlainSelect.getStringList(argumentsStringList, true, true)
100111
: "")
@@ -117,6 +128,11 @@ public ColDataType withCharacterSet(String characterSet) {
117128
return this;
118129
}
119130

131+
public ColDataType withIntervalQualifier(IntervalQualifier intervalQualifier) {
132+
this.setIntervalQualifier(intervalQualifier);
133+
return this;
134+
}
135+
120136
public ColDataType withArrayData(List<Integer> arrayData) {
121137
this.setArrayData(arrayData);
122138
return this;
@@ -161,6 +177,7 @@ public final boolean equals(Object o) {
161177
return dataType.equalsIgnoreCase(that.dataType)
162178
&& Objects.equals(argumentsStringList, that.argumentsStringList)
163179
&& Objects.equals(characterSet, that.characterSet)
180+
&& Objects.equals(intervalQualifier, that.intervalQualifier)
164181
&& Objects.equals(arrayData, that.arrayData);
165182
}
166183

@@ -169,6 +186,7 @@ public int hashCode() {
169186
int result = dataType.hashCode();
170187
result = 31 * result + Objects.hashCode(argumentsStringList);
171188
result = 31 * result + Objects.hashCode(characterSet);
189+
result = 31 * result + Objects.hashCode(intervalQualifier);
172190
result = 31 * result + Objects.hashCode(arrayData);
173191
return result;
174192
}

src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1311,6 +1311,10 @@ public <S> StringBuilder visit(ExtractExpression extractExpression, S context) {
13111311
}
13121312

13131313
@Override
1314+
@SuppressWarnings("deprecation")
1315+
// The deparser must round-trip both the canonical IntervalQualifier and the deprecated
1316+
// legacy interval type (non-standard form such as MySQL INTERVAL 1 foo), so it reads
1317+
// getIntervalType() intentionally.
13141318
public <S> StringBuilder visit(IntervalExpression intervalExpression, S context) {
13151319
if (intervalExpression.isUsingIntervalKeyword()) {
13161320
builder.append("INTERVAL ");
@@ -1320,7 +1324,9 @@ public <S> StringBuilder visit(IntervalExpression intervalExpression, S context)
13201324
} else {
13211325
builder.append(intervalExpression.getParameter());
13221326
}
1323-
if (intervalExpression.getIntervalType() != null) {
1327+
if (intervalExpression.getIntervalQualifier() != null) {
1328+
builder.append(" ").append(intervalExpression.getIntervalQualifier().toString());
1329+
} else if (intervalExpression.getIntervalType() != null) {
13241330
builder.append(" ").append(intervalExpression.getIntervalType());
13251331
}
13261332
return builder;

0 commit comments

Comments
 (0)