Skip to content

Commit

Permalink
Add support for Min(List<DateTime>) operator overload support.
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 651475805
  • Loading branch information
evan-gordon authored and copybara-github committed Jul 11, 2024
1 parent 9e71c66 commit d0fe346
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 2 deletions.
52 changes: 52 additions & 0 deletions interpreter/operator_aggregate.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,58 @@ func (i *interpreter) evalMaxDateTime(m model.IUnaryExpression, operand result.V
return result.New(dt)
}

// Min(argument List<Date>) Date
// Min(argument List<DateTime>) DateTime
// https://cql.hl7.org/09-b-cqlreference.html#min
func (i *interpreter) evalMinDateTime(m model.IUnaryExpression, operand result.Value) (result.Value, error) {
if result.IsNull(operand) {
return result.New(nil)
}
l, err := result.ToSlice(operand)
if err != nil {
return result.Value{}, err
}
if len(l) == 0 {
return result.New(nil)
}
lType, ok := operand.RuntimeType().(*types.List)
if !ok {
return result.Value{}, fmt.Errorf("Min(%v) operand is not a list", m.GetName())
}
// Special case for handling lists that contain only null runtime values.
if lType.ElementType == types.Any {
return result.New(nil)
}
maxDtVal, err := maxValue(lType.ElementType, &i.evaluationTimestamp)
if err != nil {
return result.Value{}, err
}
dt, err := result.ToDateTime(maxDtVal)
if err != nil {
return result.Value{}, err
}
for _, elem := range l {
if result.IsNull(elem) {
continue
}
v, err := result.ToDateTime(elem)
if err != nil {
return result.Value{}, err
}
compareResult, err := compareDateTime(dt, v)
if err != nil {
return result.Value{}, err
}
if compareResult == leftAfterRight {
dt = v
}
}
if m.GetResultType() == types.Date {
return result.New(result.Date(dt))
}
return result.New(dt)
}

// Sum(argument List<Decimal>) Decimal
// Sum(argument List<Integer>) Integer
// Sum(argument List<Long>) Long
Expand Down
11 changes: 11 additions & 0 deletions interpreter/operator_dispatcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,17 @@ func (i *interpreter) unaryOverloads(m model.IUnaryExpression) ([]convert.Overlo
Result: i.evalMaxDateTime,
},
}, nil
case *model.Min:
return []convert.Overload[evalUnarySignature]{
{
Operands: []types.IType{&types.List{ElementType: types.Date}},
Result: i.evalMinDateTime,
},
{
Operands: []types.IType{&types.List{ElementType: types.DateTime}},
Result: i.evalMinDateTime,
},
}, nil
case *model.Sum:
return []convert.Overload[evalUnarySignature]{
{
Expand Down
9 changes: 9 additions & 0 deletions model/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,12 @@ var _ IUnaryExpression = &Count{}
// far as we can tell.
type Max struct{ *UnaryExpression }

// Min ELM expression from https://cql.hl7.org/09-b-cqlreference.html#min.
// TODO: b/347346351 - In ELM it's modeled as an AggregateExpression, but for now we model it as an
// UnaryExpression since there is no way to set the AggregateExpression's "path" property for CQL as
// far as we can tell.
type Min struct{ *UnaryExpression }

// Sum ELM expression from https://cql.hl7.org/09-b-cqlreference.html#sum.
// TODO: b/347346351 - In ELM it's modeled as an AggregateExpression, but for now we model it as an
// UnaryExpression since there is no way to set the AggregateExpression's "path" property for CQL as
Expand Down Expand Up @@ -1375,6 +1381,9 @@ func (c *Count) GetName() string { return "Count" }
// GetName returns the name of the system operator.
func (a *Max) GetName() string { return "Max" }

// GetName returns the name of the system operator.
func (a *Min) GetName() string { return "Min" }

// GetName returns the name of the system operator.
func (a *Sum) GetName() string { return "Sum" }

Expand Down
15 changes: 15 additions & 0 deletions parser/operators.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ func (v *visitor) resolveFunction(libraryName, funcName string, operands []model
case *model.Max:
listType := resolved.WrappedOperands[0].GetResultType().(*types.List)
t.Expression = model.ResultType(listType.ElementType)
case *model.Min:
listType := resolved.WrappedOperands[0].GetResultType().(*types.List)
t.Expression = model.ResultType(listType.ElementType)
case *model.Sum:
listType := resolved.WrappedOperands[0].GetResultType().(*types.List)
t.Expression = model.ResultType(listType.ElementType)
Expand Down Expand Up @@ -1598,6 +1601,18 @@ func (p *Parser) loadSystemOperators() error {
}
},
},
{
name: "Min",
operands: [][]types.IType{
{&types.List{ElementType: types.Date}},
{&types.List{ElementType: types.DateTime}},
},
model: func() model.IExpression {
return &model.Min{
UnaryExpression: &model.UnaryExpression{},
}
},
},
{
name: "Sum",
operands: [][]types.IType{
Expand Down
10 changes: 10 additions & 0 deletions parser/operators_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1212,6 +1212,16 @@ func TestBuiltInFunctions(t *testing.T) {
},
},
},
{
name: "Min",
cql: "Min({@2010, @2011, @2012})",
want: &model.Min{
UnaryExpression: &model.UnaryExpression{
Operand: model.NewList([]string{"@2010", "@2011", "@2012"}, types.Date),
Expression: model.ResultType(types.Date),
},
},
},
{
name: "Sum",
cql: "Sum({1, 2, 3})",
Expand Down
67 changes: 67 additions & 0 deletions tests/enginetests/operator_aggregate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,73 @@ func TestMax(t *testing.T) {
}
}

func TestMin(t *testing.T) {
tests := []struct {
name string
cql string
wantModel model.IExpression
wantResult result.Value
}{
{
name: "Min({@2012, @2010, @2011})",
cql: "Min({@2012, @2010, @2011})",
wantModel: &model.Min{
UnaryExpression: &model.UnaryExpression{
Operand: model.NewList([]string{"@2012", "@2010", "@2011"}, types.Date),
Expression: model.ResultType(types.Date),
},
},
wantResult: newOrFatal(t, result.Date{Date: time.Date(2010, time.January, 01, 0, 0, 0, 0, defaultEvalTimestamp.Location()), Precision: model.YEAR}),
},
{
name: "Min with null input",
cql: "Min(null as List<Date>)",
wantResult: newOrFatal(t, nil),
},
{
name: "Min({@2012, @2011, null})",
cql: "Min({@2012, @2011, null})",
wantResult: newOrFatal(t, result.Date{Date: time.Date(2011, time.January, 01, 0, 0, 0, 0, defaultEvalTimestamp.Location()), Precision: model.YEAR}),
},
{
name: "Min with empty list",
cql: "Min(List<Date>{})",
wantResult: newOrFatal(t, nil),
},
{
name: "Min with all null list",
cql: "Min({null as Date, null as Date})",
wantResult: newOrFatal(t, nil),
},
{
name: "Min({@2014-01-01T01:01:00.000Z, @2014-01-01T01:03:00.000Z, @2014-01-01T01:02:00.000Z})",
cql: "Min({@2014-01-01T01:01:00.000Z, @2014-01-01T01:03:00.000Z, @2014-01-01T01:02:00.000Z})",
wantResult: newOrFatal(t, result.DateTime{Date: time.Date(2014, time.January, 01, 1, 1, 0, 0, time.UTC), Precision: model.MILLISECOND}),
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
p := newFHIRParser(t)
parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{})
if err != nil {
t.Fatalf("Parse returned unexpected error: %v", err)
}
if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" {
t.Errorf("Parse diff (-want +got):\n%s", diff)
}

results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p))
if err != nil {
t.Fatalf("Eval returned unexpected error: %v", err)
}
if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" {
t.Errorf("Eval diff (-want +got)\n%v", diff)
}
})
}
}

func TestSum(t *testing.T) {
tests := []struct {
name string
Expand Down
6 changes: 4 additions & 2 deletions tests/spectests/exclusions/exclusions.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ func XMLTestFileExclusionDefinitions() map[string]XMLTestFileExclusions {
GroupExcludes: []string{
// TODO: b/342061715 - unsupported operators.
"Median",
"Min",
"Mode",
"PopulationStdDev",
"PopulationVariance",
Expand All @@ -40,10 +39,13 @@ func XMLTestFileExclusionDefinitions() map[string]XMLTestFileExclusions {
},
NamesExcludes: []string{
// TODO: b/342061715 - unsupported operators.
// Only Date and DateTime overloads are supported.
// Only Date and DateTime overloads are supported for max/min.
"MaxTestInteger",
"MaxTestString",
"MaxTestTime",
"MinTestInteger",
"MinTestString",
"MinTestTime",
},
},
"CqlAggregateTest.xml": XMLTestFileExclusions{
Expand Down

0 comments on commit d0fe346

Please sign in to comment.