Skip to content

Commit

Permalink
[CALCITE-6560] Allow types with negative scale
Browse files Browse the repository at this point in the history
In RelDataTypeSystem, add methods getMinScale(SqlTypeName)
and getDefaultScale(SqlTypeName), deprecating the SqlTypeName
methods getMinScale(), getDefaultScale(), getMinPrecision().

Change parser to allow negative scale;

Move class CustomRelDataTypeSystem to testkit, and rename it
to CustomTypeSystems.

Close #3945
  • Loading branch information
NobiGo authored and julianhyde committed Sep 28, 2024
1 parent 9985b6a commit a4a27e3
Show file tree
Hide file tree
Showing 24 changed files with 882 additions and 74 deletions.
6 changes: 3 additions & 3 deletions core/src/main/codegen/templates/Parser.jj
Original file line number Diff line number Diff line change
Expand Up @@ -5930,8 +5930,8 @@ SqlTypeNameSpec SqlTypeName2(Span s) :
SqlTypeNameSpec SqlTypeName3(Span s) :
{
final SqlTypeName sqlTypeName;
int precision = -1;
int scale = -1;
int precision = RelDataType.PRECISION_NOT_SPECIFIED;
int scale = RelDataType.SCALE_NOT_SPECIFIED;
}
{
(
Expand All @@ -5944,7 +5944,7 @@ SqlTypeNameSpec SqlTypeName3(Span s) :
precision = UnsignedIntLiteral()
[
<COMMA>
scale = UnsignedIntLiteral()
scale = IntLiteral()
]
<RPAREN>
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,7 @@ private ImmutableList<MetaTypeInfo> getAllDefaultType() {
false,
false,
typeSystem.isAutoincrement(sqlTypeName),
(short) sqlTypeName.getMinScale(),
(short) typeSystem.getMinScale(sqlTypeName),
(short) typeSystem.getMaxScale(sqlTypeName),
typeSystem.getNumTypeRadix(sqlTypeName)));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,26 @@ protected DelegatingTypeSystem(RelDataTypeSystem typeSystem) {
return typeSystem.getMaxScale(typeName);
}

@Override public int getMinScale(SqlTypeName typeName) {
return typeSystem.getMinScale(typeName);
}

@Override public int getDefaultPrecision(SqlTypeName typeName) {
return typeSystem.getDefaultPrecision(typeName);
}

@Override public int getDefaultScale(SqlTypeName typeName) {
return typeSystem.getDefaultScale(typeName);
}

@Override public int getMaxPrecision(SqlTypeName typeName) {
return typeSystem.getMaxPrecision(typeName);
}

@Override public int getMinPrecision(SqlTypeName typeName) {
return typeSystem.getMinPrecision(typeName);
}

@Override public int getMaxNumericScale() {
return typeSystem.getMaxNumericScale();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,29 +37,64 @@ public interface RelDataTypeSystem {
/** Default type system. */
RelDataTypeSystem DEFAULT = new RelDataTypeSystemImpl() { };

/** Returns the maximum scale of a given type. */
/**
* Returns the maximum scale allowed for this type, or
* {@link RelDataType#SCALE_NOT_SPECIFIED}
* if scale is not applicable for this type.
*
* @return Maximum allowed scale
*/
int getMaxScale(SqlTypeName typeName);

/**
* Returns default precision for this type if supported, otherwise -1 if
* precision is either unsupported or must be specified explicitly.
* Returns the minimum scale allowed for this type, or
* {@link RelDataType#SCALE_NOT_SPECIFIED}
* if scale are not applicable for this type.
*
* @return Minimum allowed scale
*/
int getMinScale(SqlTypeName typeName);

/**
* Returns default precision for this type if supported, otherwise
* {@link RelDataType#PRECISION_NOT_SPECIFIED}
* if precision is either unsupported or must be specified explicitly.
*
* @return Default precision
*/
int getDefaultPrecision(SqlTypeName typeName);

/**
* Returns the maximum precision (or length) allowed for this type, or -1 if
* precision/length are not applicable for this type.
* Returns default scale for this type if supported, otherwise
* {@link RelDataType#SCALE_NOT_SPECIFIED}
* if scale is either unsupported or must be specified explicitly.
*
* @return Default scale
*/
int getDefaultScale(SqlTypeName typeName);

/**
* Returns the maximum precision (or length) allowed for this type, or
* {@link RelDataType#PRECISION_NOT_SPECIFIED}
* if precision/length are not applicable for this type.
*
* @return Maximum allowed precision
*/
int getMaxPrecision(SqlTypeName typeName);

/** Returns the maximum scale of a NUMERIC or DECIMAL type. */
/**
* Returns the minimum precision (or length) allowed for this type, or
* {@link RelDataType#PRECISION_NOT_SPECIFIED}
* if precision/length are not applicable for this type.
*
* @return Minimum allowed precision
*/
int getMinPrecision(SqlTypeName typeName);

/** Returns the maximum scale of a NUMERIC or DECIMAL type. And the default value is 19. */
int getMaxNumericScale();

/** Returns the maximum precision of a NUMERIC or DECIMAL type. */
/** Returns the maximum precision of a NUMERIC or DECIMAL type. And the default value is 19. */
int getMaxNumericPrecision();

/** Returns the rounding behavior for numerical operations capable of discarding precision. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@

import java.math.RoundingMode;

import static org.apache.calcite.sql.type.SqlTypeName.DEFAULT_INTERVAL_FRACTIONAL_SECOND_PRECISION;
import static org.apache.calcite.sql.type.SqlTypeName.MIN_INTERVAL_FRACTIONAL_SECOND_PRECISION;
import static org.apache.calcite.sql.type.SqlTypeName.MIN_INTERVAL_START_PRECISION;

/** Default implementation of
* {@link org.apache.calcite.rel.type.RelDataTypeSystem},
* providing parameters from the SQL standard.
Expand All @@ -35,6 +39,8 @@
* <caption>Parameter values</caption>
* <tr><th>Parameter</th> <th>Value</th></tr>
* <tr><td>MAX_NUMERIC_SCALE</td> <td>19</td></tr>
* <tr><td>MIN_NUMERIC_SCALE</td> <td>0</td></tr>
* <tr><td>MAX_NUMERIC_PRECISION</td> <td>19</td></tr>
* </table>
*/
public abstract class RelDataTypeSystemImpl implements RelDataTypeSystem {
Expand All @@ -61,6 +67,36 @@ public abstract class RelDataTypeSystemImpl implements RelDataTypeSystem {
}
}

/**
* Returns the minimum scale (or fractional second precision in the case of
* intervals) allowed for this type, or {@link RelDataType#SCALE_NOT_SPECIFIED}
* if precision/length are not applicable for this type.
*
* @return Minimum allowed scale
*/
@Override public int getMinScale(SqlTypeName typeName) {
switch (typeName) {
case DECIMAL:
return 0;
case INTERVAL_YEAR:
case INTERVAL_YEAR_MONTH:
case INTERVAL_MONTH:
case INTERVAL_DAY:
case INTERVAL_DAY_HOUR:
case INTERVAL_DAY_MINUTE:
case INTERVAL_DAY_SECOND:
case INTERVAL_HOUR:
case INTERVAL_HOUR_MINUTE:
case INTERVAL_HOUR_SECOND:
case INTERVAL_MINUTE:
case INTERVAL_MINUTE_SECOND:
case INTERVAL_SECOND:
return MIN_INTERVAL_FRACTIONAL_SECOND_PRECISION;
default:
return RelDataType.SCALE_NOT_SPECIFIED;
}
}

@Override public int getDefaultPrecision(SqlTypeName typeName) {
// Following BasicSqlType precision as the default
switch (typeName) {
Expand Down Expand Up @@ -118,6 +154,31 @@ public abstract class RelDataTypeSystemImpl implements RelDataTypeSystem {
}
}

/** Returns the default scale for this type if supported, otherwise {@link RelDataType#SCALE_NOT_SPECIFIED}
* if scale is either unsupported or must be specified explicitly. */
@Override public int getDefaultScale(SqlTypeName typeName) {
switch (typeName) {
case DECIMAL:
return 0;
case INTERVAL_YEAR:
case INTERVAL_YEAR_MONTH:
case INTERVAL_MONTH:
case INTERVAL_DAY:
case INTERVAL_DAY_HOUR:
case INTERVAL_DAY_MINUTE:
case INTERVAL_DAY_SECOND:
case INTERVAL_HOUR:
case INTERVAL_HOUR_MINUTE:
case INTERVAL_HOUR_SECOND:
case INTERVAL_MINUTE:
case INTERVAL_MINUTE_SECOND:
case INTERVAL_SECOND:
return DEFAULT_INTERVAL_FRACTIONAL_SECOND_PRECISION;
default:
return RelDataType.SCALE_NOT_SPECIFIED;
}
}

@Override public int getMaxPrecision(SqlTypeName typeName) {
switch (typeName) {
case DECIMAL:
Expand Down Expand Up @@ -154,6 +215,46 @@ public abstract class RelDataTypeSystemImpl implements RelDataTypeSystem {
}
}

/**
* Returns the minimum precision (or length) allowed for this type,
* or {@link RelDataType#PRECISION_NOT_SPECIFIED}
* if precision/length are not applicable for this type.
*
* @return Minimum allowed precision
*/
@Override public int getMinPrecision(SqlTypeName typeName) {
switch (typeName) {
case DECIMAL:
case VARCHAR:
case CHAR:
case VARBINARY:
case BINARY:
case TIME:
case TIME_WITH_LOCAL_TIME_ZONE:
case TIME_TZ:
case TIMESTAMP:
case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
case TIMESTAMP_TZ:
return 1;
case INTERVAL_YEAR:
case INTERVAL_YEAR_MONTH:
case INTERVAL_MONTH:
case INTERVAL_DAY:
case INTERVAL_DAY_HOUR:
case INTERVAL_DAY_MINUTE:
case INTERVAL_DAY_SECOND:
case INTERVAL_HOUR:
case INTERVAL_HOUR_MINUTE:
case INTERVAL_HOUR_SECOND:
case INTERVAL_MINUTE:
case INTERVAL_MINUTE_SECOND:
case INTERVAL_SECOND:
return MIN_INTERVAL_START_PRECISION;
default:
return RelDataType.PRECISION_NOT_SPECIFIED;
}
}

@Override public int getMaxNumericScale() {
return 19;
}
Expand Down
5 changes: 4 additions & 1 deletion core/src/main/java/org/apache/calcite/rex/RexBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -1338,7 +1338,10 @@ protected RexLiteral makeLiteral(
case DECIMAL:
if (o != null && type.getScale() != RelDataType.SCALE_NOT_SPECIFIED) {
assert o instanceof BigDecimal;
o = ((BigDecimal) o).setScale(type.getScale(), RoundingMode.DOWN);
o = ((BigDecimal) o).setScale(type.getScale(), typeFactory.getTypeSystem().roundingMode());
if (type.getScale() < 0) {
o = new BigDecimal(((BigDecimal) o).toPlainString());
}
}
break;
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -942,6 +942,12 @@ ExInst<CalciteException> illegalArgumentForTableFunctionCall(String a0,
@BaseMessage("Illegal arguments for 'MAP_VALUES' function: using a map with a null key is not allowed")
ExInst<CalciteException> illegalMapValuesWithNullKey();

@BaseMessage("DECIMAL precision {0,number,#} must be between 1 and {1,number,#}")
ExInst<CalciteException> invalidPrecisionForDecimalType(int precision, int maxPrecision);

@BaseMessage("DECIMAL scale {0,number,#} must be between {1,number,#} and {2,number,#}")
ExInst<CalciteException> invalidScaleForDecimalType(int scale, int minScale, int maxScale);

@BaseMessage("Illegal arguments: The length of the keys array {0,number,#} is not equal to the length of the values array {1,number,#} in MAP_FROM_ARRAYS function")
ExInst<CalciteException> illegalArgumentsInMapFromArraysFunc(int arg0, int arg1);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,16 +105,16 @@ public SqlBasicTypeNameSpec(
}

public SqlBasicTypeNameSpec(SqlTypeName typeName, SqlParserPos pos) {
this(typeName, -1, -1, null, pos);
this(typeName, RelDataType.PRECISION_NOT_SPECIFIED, RelDataType.SCALE_NOT_SPECIFIED, null, pos);
}

public SqlBasicTypeNameSpec(SqlTypeName typeName, int precision, SqlParserPos pos) {
this(typeName, precision, -1, null, pos);
this(typeName, precision, RelDataType.SCALE_NOT_SPECIFIED, null, pos);
}

public SqlBasicTypeNameSpec(SqlTypeName typeName, int precision,
String charSetName, SqlParserPos pos) {
this(typeName, precision, -1, charSetName, pos);
this(typeName, precision, RelDataType.SCALE_NOT_SPECIFIED, charSetName, pos);
}

public SqlBasicTypeNameSpec(SqlTypeName typeName, int precision,
Expand Down Expand Up @@ -167,11 +167,11 @@ public int getPrecision() {
writer.keyword(getTypeName().getSimple());
}

if (sqlTypeName.allowsPrec() && (precision >= 0)) {
if (sqlTypeName.allowsPrec() && (precision != RelDataType.PRECISION_NOT_SPECIFIED)) {
final SqlWriter.Frame frame =
writer.startList(SqlWriter.FrameTypeEnum.FUN_CALL, "(", ")");
writer.print(precision);
if (sqlTypeName.allowsScale() && (scale >= 0)) {
if (sqlTypeName.allowsScale() && (scale != RelDataType.SCALE_NOT_SPECIFIED)) {
writer.sep(",", true);
writer.print(scale);
}
Expand All @@ -198,10 +198,11 @@ public int getPrecision() {
// NOTE jvs 15-Jan-2009: earlier validation is supposed to
// have caught these, which is why it's OK for them
// to be assertions rather than user-level exceptions.
if ((precision >= 0) && (scale >= 0)) {
if ((precision != RelDataType.PRECISION_NOT_SPECIFIED)
&& (scale != RelDataType.SCALE_NOT_SPECIFIED)) {
assert sqlTypeName.allowsPrecScale(true, true);
type = typeFactory.createSqlType(sqlTypeName, precision, scale);
} else if (precision >= 0) {
} else if (precision != RelDataType.PRECISION_NOT_SPECIFIED) {
assert sqlTypeName.allowsPrecNoScale();
type = typeFactory.createSqlType(sqlTypeName, precision);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ public static int combineStartPrecisionPreservingDefault(

public int getFractionalSecondPrecision(RelDataTypeSystem typeSystem) {
if (fractionalSecondPrecision == RelDataType.PRECISION_NOT_SPECIFIED) {
return typeName().getDefaultScale();
return typeSystem.getDefaultScale(typeName());
} else {
return fractionalSecondPrecision;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@

import static com.google.common.base.Preconditions.checkArgument;

import static org.apache.calcite.util.Static.RESOURCE;

import static java.util.Objects.requireNonNull;

/**
Expand Down Expand Up @@ -59,13 +61,13 @@ public SqlTypeFactoryImpl(RelDataTypeSystem typeSystem) {
@Override public RelDataType createSqlType(
SqlTypeName typeName,
int precision) {
if (typeName.allowsScale()) {
return createSqlType(typeName, precision, typeSystem.getDefaultScale(typeName));
}
final int maxPrecision = typeSystem.getMaxPrecision(typeName);
if (maxPrecision >= 0 && precision > maxPrecision) {
precision = maxPrecision;
}
if (typeName.allowsScale()) {
return createSqlType(typeName, precision, typeName.getDefaultScale());
}
assertBasic(typeName);
assert (precision >= 0)
|| (precision == RelDataType.PRECISION_NOT_SPECIFIED);
Expand All @@ -88,6 +90,15 @@ public SqlTypeFactoryImpl(RelDataTypeSystem typeSystem) {
if (maxPrecision >= 0 && precision > maxPrecision) {
precision = maxPrecision;
}
if (precision != RelDataType.PRECISION_NOT_SPECIFIED
&& precision < typeSystem.getMinPrecision(typeName)) {
throw RESOURCE.invalidPrecisionForDecimalType(precision, maxPrecision).ex();
}
if (scale != RelDataType.SCALE_NOT_SPECIFIED
&& scale < typeSystem.getMinScale(typeName)) {
throw RESOURCE.invalidScaleForDecimalType(scale,
typeSystem.getMinScale(typeName), typeSystem.getMaxNumericScale()).ex();
}
RelDataType newType =
new BasicSqlType(typeSystem, typeName, precision, scale);
newType = SqlTypeUtil.addCharsetAndCollation(newType, this);
Expand Down
Loading

0 comments on commit a4a27e3

Please sign in to comment.