Skip to content

Commit

Permalink
Add support for Max(List<DateTime>) operator overload.
Browse files Browse the repository at this point in the history
Referenced in #48

PiperOrigin-RevId: 651210308
  • Loading branch information
evan-gordon authored and copybara-github committed Jul 11, 2024
1 parent 68ae108 commit 863e6ce
Show file tree
Hide file tree
Showing 7 changed files with 172 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 @@ -160,6 +160,58 @@ func (i *interpreter) evalCount(m model.IUnaryExpression, operand result.Value)
return result.New(count)
}

// Max(argument List<Date>) Date
// Max(argument List<DateTime>) DateTime
// https://cql.hl7.org/09-b-cqlreference.html#max
func (i *interpreter) evalMaxDateTime(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("Max(%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)
}
minDtVal, err := minValue(lType.ElementType, &i.evaluationTimestamp)
if err != nil {
return result.Value{}, err
}
dt, err := result.ToDateTime(minDtVal)
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 == leftBeforeRight {
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 @@ -517,6 +517,17 @@ func (i *interpreter) unaryOverloads(m model.IUnaryExpression) ([]convert.Overlo
Result: i.evalCount,
},
}, nil
case *model.Max:
return []convert.Overload[evalUnarySignature]{
{
Operands: []types.IType{&types.List{ElementType: types.Date}},
Result: i.evalMaxDateTime,
},
{
Operands: []types.IType{&types.List{ElementType: types.DateTime}},
Result: i.evalMaxDateTime,
},
}, 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 @@ -786,6 +786,12 @@ type Count struct{ *UnaryExpression }

var _ IUnaryExpression = &Count{}

// Max ELM expression from https://cql.hl7.org/09-b-cqlreference.html#max.
// 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 Max 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 @@ -1366,6 +1372,9 @@ func (a *Avg) GetName() string { return "Avg" }
// GetName returns the name of the system operator.
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 *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 @@ -91,6 +91,9 @@ func (v *visitor) resolveFunction(libraryName, funcName string, operands []model
case *model.Avg:
listType := resolved.WrappedOperands[0].GetResultType().(*types.List)
t.Expression = model.ResultType(listType.ElementType)
case *model.Max:
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 @@ -1572,6 +1575,18 @@ func (p *Parser) loadSystemOperators() error {
}
},
},
{
name: "Max",
operands: [][]types.IType{
{&types.List{ElementType: types.Date}},
{&types.List{ElementType: types.DateTime}},
},
model: func() model.IExpression {
return &model.Max{
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 @@ -1192,6 +1192,16 @@ func TestBuiltInFunctions(t *testing.T) {
},
},
},
{
name: "Max",
cql: "Max({@2010, @2011, @2012})",
want: &model.Max{
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
68 changes: 68 additions & 0 deletions tests/enginetests/operator_aggregate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"context"
"strings"
"testing"
"time"

"github.com/google/cql/interpreter"
"github.com/google/cql/model"
Expand Down Expand Up @@ -316,6 +317,73 @@ func TestCount(t *testing.T) {
}
}

func TestMax(t *testing.T) {
tests := []struct {
name string
cql string
wantModel model.IExpression
wantResult result.Value
}{
{
name: "Max({@2010, @2012, @2011})",
cql: "Max({@2010, @2012, @2011})",
wantModel: &model.Max{
UnaryExpression: &model.UnaryExpression{
Operand: model.NewList([]string{"@2010", "@2012", "@2011"}, types.Date),
Expression: model.ResultType(types.Date),
},
},
wantResult: newOrFatal(t, result.Date{Date: time.Date(2012, time.January, 01, 0, 0, 0, 0, defaultEvalTimestamp.Location()), Precision: model.YEAR}),
},
{
name: "Max with null input",
cql: "Max(null as List<Date>)",
wantResult: newOrFatal(t, nil),
},
{
name: "Max({@2012, @2011, null})",
cql: "Max({@2012, @2011, null})",
wantResult: newOrFatal(t, result.Date{Date: time.Date(2012, time.January, 01, 0, 0, 0, 0, defaultEvalTimestamp.Location()), Precision: model.YEAR}),
},
{
name: "Max with empty list",
cql: "Max(List<Date>{})",
wantResult: newOrFatal(t, nil),
},
{
name: "Max with all null list",
cql: "Max({null as Date, null as Date})",
wantResult: newOrFatal(t, nil),
},
{
name: "Max({@2014-01-01T01:01:00.000Z, @2014-01-01T01:03:00.000Z, @2014-01-01T01:02:00.000Z})",
cql: "Max({@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, 3, 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
9 changes: 7 additions & 2 deletions tests/spectests/exclusions/exclusions.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ func XMLTestFileExclusionDefinitions() map[string]XMLTestFileExclusions {
"CqlAggregateFunctionsTest.xml": XMLTestFileExclusions{
GroupExcludes: []string{
// TODO: b/342061715 - unsupported operators.
"Max",
"Median",
"Min",
"Mode",
Expand All @@ -39,7 +38,13 @@ func XMLTestFileExclusionDefinitions() map[string]XMLTestFileExclusions {
"StdDev",
"Variance",
},
NamesExcludes: []string{},
NamesExcludes: []string{
// TODO: b/342061715 - unsupported operators.
// Only Date and DateTime overloads are supported.
"MaxTestInteger",
"MaxTestString",
"MaxTestTime",
},
},
"CqlAggregateTest.xml": XMLTestFileExclusions{
GroupExcludes: []string{},
Expand Down

0 comments on commit 863e6ce

Please sign in to comment.