-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(type): Add support for interval compound literal (#44)
* feat(type): Add support for interval compound literal * Fix pre-commit errors and rename test case * Address review comments * Address review comments * Address review comments * Address review comments
- Loading branch information
1 parent
597afdb
commit 58e4ba0
Showing
10 changed files
with
800 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
package expr | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
|
||
"github.com/substrait-io/substrait-go/proto" | ||
"github.com/substrait-io/substrait-go/types" | ||
) | ||
|
||
// IntervalCompoundLiteral creates an interval compound literal | ||
type IntervalCompoundLiteral struct { | ||
Years int32 | ||
Months int32 | ||
Days int32 | ||
Seconds int32 | ||
SubSeconds int64 | ||
SubSecondPrecision types.TimePrecision | ||
Nullability types.Nullability | ||
} | ||
|
||
func (m IntervalCompoundLiteral) getType() types.Type { | ||
return types.NewIntervalCompoundType().WithPrecision(m.SubSecondPrecision).WithNullability(m.Nullability) | ||
} | ||
|
||
func (m IntervalCompoundLiteral) ToProtoLiteral() *proto.Expression_Literal { | ||
t := m.getType() | ||
intrCompPB := &proto.Expression_Literal_IntervalCompound{} | ||
|
||
if m.Years != 0 || m.Months != 0 { | ||
yearToMonthProto := &proto.Expression_Literal_IntervalYearToMonth{ | ||
Years: m.Years, | ||
Months: m.Months, | ||
} | ||
intrCompPB.IntervalYearToMonth = yearToMonthProto | ||
} | ||
|
||
if m.Days != 0 || m.Seconds != 0 || m.SubSeconds != 0 { | ||
dayToSecondProto := &proto.Expression_Literal_IntervalDayToSecond{ | ||
Days: m.Days, | ||
Seconds: m.Seconds, | ||
PrecisionMode: &proto.Expression_Literal_IntervalDayToSecond_Precision{Precision: m.SubSecondPrecision.ToProtoVal()}, | ||
Subseconds: m.SubSeconds, | ||
} | ||
intrCompPB.IntervalDayToSecond = dayToSecondProto | ||
} | ||
|
||
return &proto.Expression_Literal{ | ||
LiteralType: &proto.Expression_Literal_IntervalCompound_{IntervalCompound: intrCompPB}, | ||
Nullable: t.GetNullability() == types.NullabilityNullable, | ||
TypeVariationReference: t.GetTypeVariationReference(), | ||
} | ||
} | ||
|
||
func (m IntervalCompoundLiteral) ToProto() *proto.Expression { | ||
return &proto.Expression{RexType: &proto.Expression_Literal_{ | ||
Literal: m.ToProtoLiteral(), | ||
}} | ||
} | ||
|
||
func intervalCompoundLiteralFromProto(l *proto.Expression_Literal) Literal { | ||
icLiteral := IntervalCompoundLiteral{Nullability: getNullability(l.Nullable)} | ||
yearToMonth := l.GetIntervalCompound().GetIntervalYearToMonth() | ||
if yearToMonth != nil { | ||
icLiteral.Years = yearToMonth.Years | ||
icLiteral.Months = yearToMonth.Months | ||
} | ||
dayToSecond := l.GetIntervalCompound().GetIntervalDayToSecond() | ||
if dayToSecond == nil { | ||
// no day to second part | ||
return icLiteral | ||
} | ||
err := validateIntervalDayToSecondProto(dayToSecond) | ||
if err != nil { | ||
return nil | ||
} | ||
|
||
// get subSecond/precision value from proto. To get value it takes care of deprecated microseconds | ||
precision, subSeconds, err := intervalCompoundPrecisionSubSecondsFromProto(dayToSecond) | ||
if err != nil { | ||
return nil | ||
} | ||
icLiteral.Days = dayToSecond.Days | ||
icLiteral.Seconds = dayToSecond.Seconds | ||
icLiteral.SubSeconds = subSeconds | ||
icLiteral.SubSecondPrecision = precision | ||
return icLiteral | ||
} | ||
|
||
func (IntervalCompoundLiteral) isRootRef() {} | ||
func (m IntervalCompoundLiteral) GetType() types.Type { return m.getType() } | ||
func (m IntervalCompoundLiteral) String() string { | ||
return fmt.Sprintf("%s(years:%d,months:%d, days:%d, seconds:%d subseconds:%d)", | ||
m.getType(), m.Years, m.Months, m.Days, m.Seconds, m.SubSeconds) | ||
} | ||
func (m IntervalCompoundLiteral) Equals(rhs Expression) bool { | ||
if other, ok := rhs.(IntervalCompoundLiteral); ok { | ||
return m.getType().Equals(other.GetType()) && (m == other) | ||
} | ||
return false | ||
} | ||
|
||
func (m IntervalCompoundLiteral) ToProtoFuncArg() *proto.FunctionArgument { | ||
return &proto.FunctionArgument{ | ||
ArgType: &proto.FunctionArgument_Value{Value: m.ToProto()}, | ||
} | ||
} | ||
|
||
func (m IntervalCompoundLiteral) Visit(VisitFunc) Expression { return m } | ||
func (IntervalCompoundLiteral) IsScalar() bool { return true } | ||
|
||
func validateIntervalDayToSecondProto(idts *proto.Expression_Literal_IntervalDayToSecond) error { | ||
if idts.PrecisionMode == nil { | ||
// error, precision mode must be set for intervalCompound | ||
return errors.New("missing precision mode for interval compound") | ||
} | ||
if _, ok := idts.PrecisionMode.(*proto.Expression_Literal_IntervalDayToSecond_Microseconds); ok { | ||
// if microsecond precision then subseconds must be set to zero | ||
if idts.Subseconds > 0 { | ||
return errors.New("both deprecated microseconds and subseconds can't be non zero") | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func intervalCompoundPrecisionSubSecondsFromProto(protoVal *proto.Expression_Literal_IntervalDayToSecond) (types.TimePrecision, int64, error) { | ||
var precisionVal int32 | ||
var subSecondVal int64 | ||
switch pmt := protoVal.PrecisionMode.(type) { | ||
case *proto.Expression_Literal_IntervalDayToSecond_Precision: | ||
precisionVal = pmt.Precision | ||
subSecondVal = protoVal.Subseconds | ||
case *proto.Expression_Literal_IntervalDayToSecond_Microseconds: | ||
// deprecated field microsecond is set, treat its value subsecond | ||
precisionVal = types.PrecisionMicroSeconds.ToProtoVal() | ||
subSecondVal = int64(pmt.Microseconds) | ||
} | ||
precision, err := types.ProtoToTimePrecision(precisionVal) | ||
if err != nil { | ||
return types.PrecisionUnknown, 0, err | ||
} | ||
return precision, subSecondVal, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,240 @@ | ||
package expr | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/google/go-cmp/cmp" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/substrait-io/substrait-go/proto" | ||
"github.com/substrait-io/substrait-go/types" | ||
"google.golang.org/protobuf/testing/protocmp" | ||
) | ||
|
||
const ( | ||
yearVal int32 = 1 | ||
monthVal int32 = 10 | ||
dayVal int32 = 100 | ||
secondsVal int32 = 1000 | ||
subSecondsVal int64 = 10000 | ||
negativeInt32Val int32 = -5 | ||
negativeInt64Val int64 = -100000 | ||
) | ||
|
||
func TestIntervalCompoundToProto(t *testing.T) { | ||
// precision and nullability belong to type. In type unit tests they are already tested | ||
// for different values so no need to test for multiple values | ||
precisionVal := types.PrecisionNanoSeconds | ||
nanoSecPrecision := &proto.Expression_Literal_IntervalDayToSecond_Precision{Precision: precisionVal.ToProtoVal()} | ||
nullable := true | ||
nullability := types.NullabilityNullable | ||
|
||
for _, tc := range []struct { | ||
name string | ||
inputLiteral IntervalCompoundLiteral | ||
expectedExpressionLiteral *proto.Expression_Literal_IntervalCompound_ | ||
}{ | ||
{"WithOnlyYearAndMonth", | ||
IntervalCompoundLiteral{Nullability: nullability, Years: yearVal, Months: monthVal}, | ||
&proto.Expression_Literal_IntervalCompound_{IntervalCompound: &proto.Expression_Literal_IntervalCompound{ | ||
IntervalYearToMonth: &proto.Expression_Literal_IntervalYearToMonth{Years: yearVal, Months: monthVal}, | ||
}}, | ||
}, | ||
{"WithOnlyYearAndMonthNegativeVal", | ||
IntervalCompoundLiteral{Nullability: nullability, Years: negativeInt32Val, Months: negativeInt32Val}, | ||
&proto.Expression_Literal_IntervalCompound_{IntervalCompound: &proto.Expression_Literal_IntervalCompound{ | ||
IntervalYearToMonth: &proto.Expression_Literal_IntervalYearToMonth{Years: negativeInt32Val, Months: negativeInt32Val}, | ||
}}, | ||
}, | ||
{"WithOnlyDayToSecond", | ||
IntervalCompoundLiteral{Nullability: nullability, Days: dayVal, Seconds: secondsVal, SubSeconds: subSecondsVal, SubSecondPrecision: precisionVal}, | ||
&proto.Expression_Literal_IntervalCompound_{IntervalCompound: &proto.Expression_Literal_IntervalCompound{ | ||
IntervalDayToSecond: &proto.Expression_Literal_IntervalDayToSecond{ | ||
Days: dayVal, Seconds: secondsVal, PrecisionMode: nanoSecPrecision, Subseconds: subSecondsVal, | ||
}, | ||
}}, | ||
}, | ||
{"WithOnlyDayToSecondNegativeVal", | ||
IntervalCompoundLiteral{Nullability: nullability, Days: negativeInt32Val, Seconds: negativeInt32Val, SubSeconds: negativeInt64Val, SubSecondPrecision: precisionVal}, | ||
&proto.Expression_Literal_IntervalCompound_{IntervalCompound: &proto.Expression_Literal_IntervalCompound{ | ||
IntervalDayToSecond: &proto.Expression_Literal_IntervalDayToSecond{ | ||
Days: negativeInt32Val, Seconds: negativeInt32Val, PrecisionMode: nanoSecPrecision, Subseconds: negativeInt64Val, | ||
}, | ||
}}, | ||
}, | ||
{"WithBothYearToMonthAndDayToSecond", | ||
IntervalCompoundLiteral{Nullability: nullability, Years: yearVal, Months: monthVal, Days: dayVal, Seconds: secondsVal, SubSeconds: subSecondsVal, SubSecondPrecision: precisionVal}, | ||
&proto.Expression_Literal_IntervalCompound_{IntervalCompound: &proto.Expression_Literal_IntervalCompound{ | ||
IntervalYearToMonth: &proto.Expression_Literal_IntervalYearToMonth{Years: yearVal, Months: monthVal}, | ||
IntervalDayToSecond: &proto.Expression_Literal_IntervalDayToSecond{ | ||
Days: dayVal, Seconds: secondsVal, PrecisionMode: nanoSecPrecision, Subseconds: subSecondsVal, | ||
}, | ||
}}, | ||
}, | ||
{"WithBothYearToMonthAndDayToSecondAllNegativeVal", | ||
IntervalCompoundLiteral{Nullability: nullability, Years: negativeInt32Val, Months: negativeInt32Val, Days: negativeInt32Val, Seconds: negativeInt32Val, SubSeconds: negativeInt64Val, SubSecondPrecision: precisionVal}, | ||
&proto.Expression_Literal_IntervalCompound_{IntervalCompound: &proto.Expression_Literal_IntervalCompound{ | ||
IntervalYearToMonth: &proto.Expression_Literal_IntervalYearToMonth{Years: negativeInt32Val, Months: negativeInt32Val}, | ||
IntervalDayToSecond: &proto.Expression_Literal_IntervalDayToSecond{ | ||
Days: negativeInt32Val, Seconds: negativeInt32Val, PrecisionMode: nanoSecPrecision, Subseconds: negativeInt64Val, | ||
}, | ||
}}, | ||
}, | ||
} { | ||
t.Run(tc.name, func(t *testing.T) { | ||
expectedProtoExpression := &proto.Expression{RexType: &proto.Expression_Literal_{Literal: &proto.Expression_Literal{LiteralType: tc.expectedExpressionLiteral, Nullable: nullable}}} | ||
gotExpressionProto := tc.inputLiteral.ToProto() | ||
assert.NotNil(t, gotExpressionProto) | ||
if diff := cmp.Diff(gotExpressionProto, expectedProtoExpression, protocmp.Transform()); diff != "" { | ||
t.Errorf("proto didn't match, diff:\n%v", diff) | ||
} | ||
// verify ToProtoFuncArg | ||
funcArgProto := &proto.FunctionArgument{ | ||
ArgType: &proto.FunctionArgument_Value{Value: gotExpressionProto}, | ||
} | ||
if diff := cmp.Diff(tc.inputLiteral.ToProtoFuncArg(), funcArgProto, protocmp.Transform()); diff != "" { | ||
t.Errorf("expression proto didn't match, diff:\n%v", diff) | ||
} | ||
}) | ||
|
||
} | ||
} | ||
|
||
func TestIntervalCompoundFromProto(t *testing.T) { | ||
precisionNanoVal := types.PrecisionNanoSeconds | ||
nanoSecPrecision := &proto.Expression_Literal_IntervalDayToSecond_Precision{Precision: precisionNanoVal.ToProtoVal()} | ||
|
||
var microSecondVal int32 = 70 | ||
deprecatedMicroSecPrecision := &proto.Expression_Literal_IntervalDayToSecond_Microseconds{ | ||
Microseconds: microSecondVal} | ||
nullable := true | ||
nullability := types.NullabilityNullable | ||
for _, tc := range []struct { | ||
name string | ||
constructedProto *proto.Expression_Literal | ||
expectedLiteral IntervalCompoundLiteral | ||
}{ | ||
{"NoPartsValue", | ||
&proto.Expression_Literal{ | ||
LiteralType: &proto.Expression_Literal_IntervalCompound_{IntervalCompound: &proto.Expression_Literal_IntervalCompound{}}, | ||
Nullable: nullable}, | ||
IntervalCompoundLiteral{Nullability: nullability}, | ||
}, | ||
{"OnlyYearAndMonth", | ||
&proto.Expression_Literal{ | ||
LiteralType: &proto.Expression_Literal_IntervalCompound_{IntervalCompound: &proto.Expression_Literal_IntervalCompound{ | ||
IntervalYearToMonth: &proto.Expression_Literal_IntervalYearToMonth{Years: yearVal, Months: monthVal}, | ||
}}, | ||
Nullable: nullable}, | ||
IntervalCompoundLiteral{Nullability: nullability, Years: yearVal, Months: monthVal}, | ||
}, | ||
{"OnlyYearAndMonthNegativeVal", | ||
&proto.Expression_Literal{ | ||
LiteralType: &proto.Expression_Literal_IntervalCompound_{IntervalCompound: &proto.Expression_Literal_IntervalCompound{ | ||
IntervalYearToMonth: &proto.Expression_Literal_IntervalYearToMonth{Years: negativeInt32Val, Months: negativeInt32Val}, | ||
}}, | ||
Nullable: nullable}, | ||
IntervalCompoundLiteral{Nullability: nullability, Years: negativeInt32Val, Months: negativeInt32Val}, | ||
}, | ||
{"OnlyDayToSecond", | ||
&proto.Expression_Literal{ | ||
LiteralType: &proto.Expression_Literal_IntervalCompound_{IntervalCompound: &proto.Expression_Literal_IntervalCompound{ | ||
IntervalDayToSecond: &proto.Expression_Literal_IntervalDayToSecond{Days: dayVal, Seconds: secondsVal, | ||
PrecisionMode: nanoSecPrecision, Subseconds: subSecondsVal}, | ||
}}, | ||
Nullable: nullable}, | ||
IntervalCompoundLiteral{Nullability: nullability, Days: dayVal, Seconds: secondsVal, SubSeconds: subSecondsVal, SubSecondPrecision: precisionNanoVal}, | ||
}, | ||
{"OnlyDayToSecondNegativeVal", | ||
&proto.Expression_Literal{ | ||
LiteralType: &proto.Expression_Literal_IntervalCompound_{IntervalCompound: &proto.Expression_Literal_IntervalCompound{ | ||
IntervalDayToSecond: &proto.Expression_Literal_IntervalDayToSecond{Days: negativeInt32Val, Seconds: negativeInt32Val, | ||
PrecisionMode: nanoSecPrecision, Subseconds: negativeInt64Val}, | ||
}}, | ||
Nullable: nullable}, | ||
IntervalCompoundLiteral{Nullability: nullability, Days: negativeInt32Val, Seconds: negativeInt32Val, SubSeconds: negativeInt64Val, SubSecondPrecision: precisionNanoVal}, | ||
}, | ||
{"BothYearToMonthAndDayToSecond", | ||
&proto.Expression_Literal{ | ||
LiteralType: &proto.Expression_Literal_IntervalCompound_{IntervalCompound: &proto.Expression_Literal_IntervalCompound{ | ||
IntervalYearToMonth: &proto.Expression_Literal_IntervalYearToMonth{Years: yearVal, Months: monthVal}, | ||
IntervalDayToSecond: &proto.Expression_Literal_IntervalDayToSecond{Days: dayVal, Seconds: secondsVal, | ||
PrecisionMode: nanoSecPrecision, Subseconds: subSecondsVal}, | ||
}}, | ||
Nullable: nullable}, | ||
IntervalCompoundLiteral{Nullability: nullability, Years: yearVal, Months: monthVal, Days: dayVal, Seconds: secondsVal, SubSeconds: subSecondsVal, SubSecondPrecision: precisionNanoVal}, | ||
}, | ||
{"BothYearToMonthAndDayToSecondAllNegVal", | ||
&proto.Expression_Literal{ | ||
LiteralType: &proto.Expression_Literal_IntervalCompound_{IntervalCompound: &proto.Expression_Literal_IntervalCompound{ | ||
IntervalYearToMonth: &proto.Expression_Literal_IntervalYearToMonth{Years: negativeInt32Val, Months: negativeInt32Val}, | ||
IntervalDayToSecond: &proto.Expression_Literal_IntervalDayToSecond{Days: negativeInt32Val, Seconds: negativeInt32Val, | ||
PrecisionMode: nanoSecPrecision, Subseconds: negativeInt64Val}, | ||
}}, | ||
Nullable: nullable}, | ||
IntervalCompoundLiteral{Nullability: nullability, Years: negativeInt32Val, Months: negativeInt32Val, Days: negativeInt32Val, Seconds: negativeInt32Val, SubSeconds: negativeInt64Val, SubSecondPrecision: precisionNanoVal}, | ||
}, | ||
{"WithDeprecatedMicroSecondPrecision", | ||
&proto.Expression_Literal{ | ||
LiteralType: &proto.Expression_Literal_IntervalCompound_{IntervalCompound: &proto.Expression_Literal_IntervalCompound{ | ||
IntervalYearToMonth: &proto.Expression_Literal_IntervalYearToMonth{Years: yearVal, Months: monthVal}, | ||
IntervalDayToSecond: &proto.Expression_Literal_IntervalDayToSecond{Days: dayVal, Seconds: secondsVal, | ||
PrecisionMode: deprecatedMicroSecPrecision}, | ||
}}, | ||
Nullable: nullable}, | ||
IntervalCompoundLiteral{Nullability: nullability, Years: yearVal, Months: monthVal, Days: dayVal, Seconds: secondsVal, SubSeconds: int64(microSecondVal), SubSecondPrecision: types.PrecisionMicroSeconds}, | ||
}, | ||
} { | ||
t.Run(tc.name, func(t *testing.T) { | ||
gotLiteral := LiteralFromProto(tc.constructedProto) | ||
assert.NotNil(t, gotLiteral) | ||
assert.Equal(t, tc.expectedLiteral, gotLiteral) | ||
// verify equal method too returns true | ||
assert.True(t, tc.expectedLiteral.Equals(gotLiteral)) | ||
assert.True(t, gotLiteral.IsScalar()) | ||
// got literal after serialization is different from empty literal | ||
assert.False(t, IntervalCompoundLiteral{}.Equals(gotLiteral)) | ||
}) | ||
|
||
} | ||
} | ||
|
||
func TestIntervalCompoundFromProtoError(t *testing.T) { | ||
// valid precision val is [0, 9] | ||
invalidPrecision := &proto.Expression_Literal_IntervalDayToSecond_Precision{Precision: 10} | ||
deprecatedMicroSecPrecision := &proto.Expression_Literal_IntervalDayToSecond_Microseconds{Microseconds: 70} | ||
for _, tc := range []struct { | ||
name string | ||
constructedProto *proto.Expression_Literal | ||
}{ | ||
{"NoPrecisionMode", | ||
&proto.Expression_Literal{ | ||
LiteralType: &proto.Expression_Literal_IntervalCompound_{IntervalCompound: &proto.Expression_Literal_IntervalCompound{ | ||
IntervalDayToSecond: &proto.Expression_Literal_IntervalDayToSecond{}, | ||
}}}, | ||
}, | ||
{"NoPrecisionModeButSubsecondsPresent", | ||
&proto.Expression_Literal{ | ||
LiteralType: &proto.Expression_Literal_IntervalCompound_{IntervalCompound: &proto.Expression_Literal_IntervalCompound{ | ||
IntervalDayToSecond: &proto.Expression_Literal_IntervalDayToSecond{Subseconds: 123}, | ||
}}}, | ||
}, | ||
{"InvalidPrecisionMode", | ||
&proto.Expression_Literal{ | ||
LiteralType: &proto.Expression_Literal_IntervalCompound_{IntervalCompound: &proto.Expression_Literal_IntervalCompound{ | ||
IntervalDayToSecond: &proto.Expression_Literal_IntervalDayToSecond{PrecisionMode: invalidPrecision}, | ||
}}}, | ||
}, | ||
{"DeprecatedMicrosecondPrecisionWithSubsecondsSet", | ||
&proto.Expression_Literal{ | ||
LiteralType: &proto.Expression_Literal_IntervalCompound_{IntervalCompound: &proto.Expression_Literal_IntervalCompound{ | ||
IntervalDayToSecond: &proto.Expression_Literal_IntervalDayToSecond{PrecisionMode: deprecatedMicroSecPrecision, Subseconds: 70}, | ||
}}}, | ||
}, | ||
} { | ||
t.Run(tc.name, func(t *testing.T) { | ||
gotLiteral := LiteralFromProto(tc.constructedProto) | ||
assert.Nil(t, gotLiteral) | ||
}) | ||
|
||
} | ||
} |
Oops, something went wrong.