Skip to content
Merged
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 @@ -29,6 +29,8 @@ public class ColDataType implements Serializable {
private String characterSet;
private IntervalQualifier intervalQualifier;
private List<Integer> arrayData = new ArrayList<Integer>();
private Integer precision;
private Integer scale;

public ColDataType() {
// empty constructor
Expand All @@ -38,8 +40,10 @@ public ColDataType(String dataType, int precision, int scale) {
this.dataType = dataType;

if (precision >= 0) {
this.precision = precision;
this.dataType += " (" + (precision == Integer.MAX_VALUE ? "MAX" : precision);
if (scale >= 0) {
this.scale = scale;
this.dataType += ", " + scale;
}
this.dataType += ")";
Expand Down Expand Up @@ -94,6 +98,32 @@ public void setArrayData(List<Integer> arrayData) {
this.arrayData = arrayData;
}

/**
* The first numeric type parameter, e.g. {@code 255} for {@code VARCHAR(255)} or {@code 10} for
* {@code DECIMAL(10, 2)}. {@code MAX} is reported as {@link Integer#MAX_VALUE}. Returns
* {@code null} when the type carries no numeric parameters, e.g. {@code INT} or
* {@code ENUM('a', 'b')}.
*/
public Integer getPrecision() {
return precision;
}

public void setPrecision(Integer precision) {
this.precision = precision;
}

/**
* The second numeric type parameter, e.g. {@code 2} for {@code DECIMAL(10, 2)}. Returns
* {@code null} when absent.
*/
public Integer getScale() {
return scale;
}

public void setScale(Integer scale) {
this.scale = scale;
}

@Override
public String toString() {
StringBuilder arraySpec = new StringBuilder();
Expand Down Expand Up @@ -138,6 +168,16 @@ public ColDataType withArrayData(List<Integer> arrayData) {
return this;
}

public ColDataType withPrecision(Integer precision) {
this.setPrecision(precision);
return this;
}

public ColDataType withScale(Integer scale) {
this.setScale(scale);
return this;
}

public ColDataType addArgumentsStringList(String... argumentsStringList) {
List<String> collection =
Optional.ofNullable(getArgumentsStringList()).orElseGet(ArrayList::new);
Expand Down
44 changes: 41 additions & 3 deletions src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -1293,6 +1293,17 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
}
}

/**
* Extracts the numeric precision embedded in a DT_ZONE token image such as
* "TIMESTAMP(3) WITH TIME ZONE", or null when the image carries no parameter.
*/
private static Integer zonedTypePrecision(String image) {
int open = image.indexOf('(');
if (open < 0) {
return null;
}
return Integer.valueOf(image.substring(open + 1, image.indexOf(')', open)).trim());
}

}

Expand Down Expand Up @@ -11661,6 +11672,7 @@ ColDataType DataType():
List<Integer> array = new ArrayList<Integer>();
List<String> name;
ColDataType arrayType;
Integer zonePrecision = null;

int precision = -1;
int scale = -1;
Expand All @@ -11681,7 +11693,12 @@ ColDataType DataType():
(
( tk=<K_DATETIMELITERAL> | tk=<DT_ZONE> | tk = <DATA_TYPE> | tk = <K_SIGNED> | tk = <K_UNSIGNED>
| tk=<K_CHARACTER> | tk=<K_BIT> | tk=<K_BYTES> | tk=<K_BINARY> | tk=<K_BOOLEAN>
| tk=<K_CHAR> | tk=<K_JSON> | tk=<K_STRING> ) { type = tk.image; }
| tk=<K_CHAR> | tk=<K_JSON> | tk=<K_STRING> )
{
type = tk.image;
// A DT_ZONE image already contains its parameter, e.g. "TIMESTAMP(3) WITH TIME ZONE".
zonePrecision = tk.kind == DT_ZONE ? zonedTypePrecision(tk.image) : null;
}
(
// MySQL seems to allow: INT UNSIGNED. Do not consume CHARACTER when it starts
// the trailing CHARACTER SET clause of a character type.
Expand All @@ -11698,6 +11715,9 @@ ColDataType DataType():
]
{
colDataType = new ColDataType(type, precision, scale);
if (zonePrecision != null) {
colDataType.setPrecision(zonePrecision);
}
}
)
)
Expand All @@ -11721,6 +11741,7 @@ ColDataType ColDataType():
ColDataType arrayType;
ColDataType nestedType = null;
IntervalQualifier intervalQualifier = null;
Integer zonePrecision = null;

int precision = -1;
int scale = -1;
Expand Down Expand Up @@ -11760,7 +11781,12 @@ ColDataType ColDataType():
| tk=<K_PUBLIC>
| tk=<K_DATA>
| tk=<K_NAME>
) { schema = tk.image; }
)
{
schema = tk.image;
// A DT_ZONE image already contains its parameter, e.g. "TIMESTAMP(3) WITH TIME ZONE".
zonePrecision = tk.kind == DT_ZONE ? zonedTypePrecision(tk.image) : null;
}

// 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
Expand Down Expand Up @@ -11810,8 +11836,20 @@ ColDataType ColDataType():
[ LOOKAHEAD(2) <K_CHARACTER> <K_SET> (tk=<S_IDENTIFIER> | tk=<K_BINARY>) { colDataType.setCharacterSet(tk.image); } ]

{
if (argumentsStringList.size() > 0)
if (argumentsStringList.size() > 0) {
colDataType.setArgumentsStringList(argumentsStringList);
// Digits-only arguments are the type's numeric parameters, e.g. mediumint(9).
if (argumentsStringList.size() == 1 && argumentsStringList.get(0).matches("\\d+")) {
colDataType.setPrecision(Integer.valueOf(argumentsStringList.get(0)));
} else if (argumentsStringList.size() == 2 && argumentsStringList.get(0).matches("\\d+")
&& argumentsStringList.get(1).matches("\\d+")) {
colDataType.setPrecision(Integer.valueOf(argumentsStringList.get(0)));
colDataType.setScale(Integer.valueOf(argumentsStringList.get(1)));
}
}
if (zonePrecision != null) {
colDataType.setPrecision(zonePrecision);
}
return colDataType;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ public void testDeclareType() throws JSQLParserException {
DeclareStatement created = new DeclareStatement()
.addTypeDefExprList(
new TypeDefExpr(new UserVariable().withName("find"),
new ColDataType().withDataType("nvarchar (30)"), null))
new ColDataType().withDataType("nvarchar (30)").withPrecision(30),
null))
.withDeclareType(DeclareType.TYPE);
assertDeparse(created, statement);
assertEqualsObjectTree(parsed, created);
Expand All @@ -49,7 +50,7 @@ public void testDeclareTypeWithDefault() throws JSQLParserException {
Statement parsed = assertSqlCanBeParsedAndDeparsed(statement);
DeclareStatement created = new DeclareStatement()
.addTypeDefExprList(new TypeDefExpr(new UserVariable().withName("find"),
new ColDataType().withDataType("varchar (30)"),
new ColDataType().withDataType("varchar (30)").withPrecision(30),
new StringValue().withValue("Man%")))
.withDeclareType(DeclareType.TYPE);
assertDeparse(created, statement);
Expand All @@ -63,7 +64,7 @@ public void testDeclareTypeList() throws JSQLParserException {
DeclareStatement created = new DeclareStatement().addTypeDefExprList(asList( //
new TypeDefExpr(
new UserVariable().withName("group"),
new ColDataType().withDataType("nvarchar (50)"),
new ColDataType().withDataType("nvarchar (50)").withPrecision(50),
null),
new TypeDefExpr(new UserVariable().withName("sales"),
new ColDataType().withDataType("money"), null)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed;
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 ColDataTypeTest {
@Test
Expand Down Expand Up @@ -97,4 +98,57 @@ void testCastAsIntervalWithQualifierRoundTrip() throws JSQLParserException {
"SELECT CAST(col AS INTERVAL DAY TO SECOND)", true);
assertSqlCanBeParsedAndDeparsed("SELECT CAST(col AS INTERVAL HOUR)", true);
}

@Test
void testStructuredPrecisionForKeywordTypes() throws JSQLParserException {
ColDataType varchar = parseColumnType("CREATE TABLE t (a VARCHAR(255))");
assertEquals(255, varchar.getPrecision());
assertNull(varchar.getScale());
// the rendered string keeps its historical shape
assertEquals("VARCHAR (255)", varchar.getDataType());

ColDataType decimal = parseColumnType("CREATE TABLE t (a DECIMAL(10, 2))");
assertEquals(10, decimal.getPrecision());
assertEquals(2, decimal.getScale());
assertEquals("DECIMAL (10, 2)", decimal.getDataType());

ColDataType max = parseColumnType("CREATE TABLE t (a VARCHAR(MAX))");
assertEquals(Integer.MAX_VALUE, max.getPrecision());

ColDataType plain = parseColumnType("CREATE TABLE t (a INT)");
assertNull(plain.getPrecision());
assertNull(plain.getScale());
}

@Test
void testStructuredPrecisionForIdentifierTypes() throws JSQLParserException {
ColDataType mediumInt = parseColumnType("CREATE TABLE t (a mediumint(9))");
assertEquals(9, mediumInt.getPrecision());
assertNull(mediumInt.getScale());
// the string arguments stay available as before
assertEquals(java.util.List.of("9"), mediumInt.getArgumentsStringList());

// non-numeric arguments are not numeric parameters
ColDataType enumType = parseColumnType("CREATE TABLE t (a ENUM('small', 'medium'))");
assertNull(enumType.getPrecision());
assertNull(enumType.getScale());
}

@Test
void testStructuredPrecisionForZonedTypes() throws JSQLParserException {
ColDataType zoned = parseColumnType("CREATE TABLE t (a TIMESTAMP(3) WITH TIME ZONE)");
assertEquals(3, zoned.getPrecision());
assertNull(zoned.getScale());
// the token image keeps its historical shape
assertEquals("TIMESTAMP(3) WITH TIME ZONE", zoned.getDataType());

ColDataType unparameterized =
parseColumnType("CREATE TABLE t (a TIMESTAMP WITH TIME ZONE)");
assertNull(unparameterized.getPrecision());
}

private ColDataType parseColumnType(String sqlStr) throws JSQLParserException {
CreateTable create = (CreateTable) assertSqlCanBeParsedAndDeparsed(sqlStr, true);
return create.getColumnDefinitions().get(0).getColDataType();
}
}
Loading