Skip to content

Commit a31a7dc

Browse files
evan-gordoncopybara-github
authored andcommitted
Add support for list overload of Union operator.
PiperOrigin-RevId: 767140139
1 parent 59999d4 commit a31a7dc

File tree

6 files changed

+132
-5
lines changed

6 files changed

+132
-5
lines changed

interpreter/operator_dispatcher.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1238,6 +1238,13 @@ func (i *interpreter) binaryOverloads(m model.IBinaryExpression) ([]convert.Over
12381238
Result: evalTake,
12391239
},
12401240
}, nil
1241+
case *model.Union:
1242+
return []convert.Overload[evalBinarySignature]{
1243+
{
1244+
Operands: []types.IType{&types.List{ElementType: types.Any}, &types.List{ElementType: types.Any}},
1245+
Result: evalUnion,
1246+
},
1247+
}, nil
12411248
case *model.EndsWith:
12421249
return []convert.Overload[evalBinarySignature]{
12431250
{

interpreter/operator_list.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,42 @@ func (i *interpreter) evalIndexerList(m model.IBinaryExpression, lObj, rObj resu
500500
return list[idx], nil
501501
}
502502

503+
// Union(left List<T>, right List<T>) List<T>
504+
// https://cql.hl7.org/09-b-cqlreference.html#union-1
505+
func evalUnion(m model.IBinaryExpression, lObj, rObj result.Value) (result.Value, error) {
506+
staticType := &types.List{ElementType: types.Any}
507+
var l []result.Value
508+
var r []result.Value
509+
var err error
510+
if !result.IsNull(lObj) {
511+
staticType = lObj.GolangValue().(result.List).StaticType
512+
l, err = result.ToSlice(lObj)
513+
if err != nil {
514+
return result.Value{}, err
515+
}
516+
}
517+
if !result.IsNull(rObj) {
518+
staticType = rObj.GolangValue().(result.List).StaticType
519+
r, err = result.ToSlice(rObj)
520+
if err != nil {
521+
return result.Value{}, err
522+
}
523+
}
524+
var unionList []result.Value
525+
for _, elemObj := range l {
526+
unionList = append(unionList, elemObj)
527+
}
528+
for _, elemObj := range r {
529+
if !valueInList(elemObj, l) {
530+
unionList = append(unionList, elemObj)
531+
}
532+
}
533+
return result.New(result.List{
534+
Value: unionList,
535+
StaticType: staticType,
536+
})
537+
}
538+
503539
// valueInList returns true if the value is in the list using equality scemantics.
504540
func valueInList(value result.Value, list []result.Value) bool {
505541
for _, elemObj := range list {

parser/operators.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1949,7 +1949,7 @@ func (p *Parser) loadSystemOperators() error {
19491949
},
19501950
{
19511951
name: "Union",
1952-
operands: [][]types.IType{{&types.List{ElementType: types.Any}, &types.List{ElementType: types.Any}}},
1952+
operands: [][]types.IType{{convert.GenericList, convert.GenericList}},
19531953
model: func() model.IExpression {
19541954
return &model.Union{
19551955
BinaryExpression: &model.BinaryExpression{},

parser/operators_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1586,14 +1586,14 @@ func TestBuiltInFunctions(t *testing.T) {
15861586
},
15871587
{
15881588
name: "Union",
1589-
cql: "Union({1}, {'hi'})",
1589+
cql: "Union({1}, {2})",
15901590
want: &model.Union{
15911591
BinaryExpression: &model.BinaryExpression{
15921592
Operands: []model.IExpression{
15931593
model.NewList([]string{"1"}, types.Integer),
1594-
model.NewList([]string{"hi"}, types.String),
1594+
model.NewList([]string{"2"}, types.Integer),
15951595
},
1596-
Expression: model.ResultType(&types.List{ElementType: &types.Choice{ChoiceTypes: []types.IType{types.Integer, types.String}}}),
1596+
Expression: model.ResultType(&types.List{ElementType: types.Integer}),
15971597
},
15981598
},
15991599
},

tests/enginetests/operator_list_test.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2078,3 +2078,88 @@ func TestIndexerList(t *testing.T) {
20782078
})
20792079
}
20802080
}
2081+
2082+
func TestUnionList(t *testing.T) {
2083+
tests := []struct {
2084+
name string
2085+
cql string
2086+
wantModel model.IExpression
2087+
wantResult result.Value
2088+
}{
2089+
{
2090+
name: "Union on two equal lists",
2091+
cql: "{'a', 'b'} union {'a', 'b'}",
2092+
wantModel: &model.Union{
2093+
BinaryExpression: &model.BinaryExpression{
2094+
Expression: model.ResultType(&types.List{ElementType: types.String}),
2095+
Operands: []model.IExpression{
2096+
model.NewList([]string{"a", "b"}, types.String),
2097+
model.NewList([]string{"a", "b"}, types.String),
2098+
},
2099+
},
2100+
},
2101+
wantResult: newOrFatal(t, result.List{Value: []result.Value{newOrFatal(t, "a"), newOrFatal(t, "b")}, StaticType: &types.List{ElementType: types.String}}),
2102+
},
2103+
{
2104+
name: "Union where right is a superset of left",
2105+
cql: "{1} union {1, 2}",
2106+
wantResult: newOrFatal(t, result.List{Value: []result.Value{newOrFatal(t, int32(1)), newOrFatal(t, int32(2))}, StaticType: &types.List{ElementType: types.Integer}}),
2107+
},
2108+
{
2109+
name: "Union where left is a superset of right",
2110+
cql: "{1, 2} union {1}",
2111+
wantResult: newOrFatal(t, result.List{Value: []result.Value{newOrFatal(t, int32(1)), newOrFatal(t, int32(2))}, StaticType: &types.List{ElementType: types.Integer}}),
2112+
},
2113+
{
2114+
name: "Union with both empty lists",
2115+
cql: "{} union {}",
2116+
wantResult: newOrFatal(t, result.List{Value: []result.Value{}, StaticType: &types.List{ElementType: types.Any}}),
2117+
},
2118+
{
2119+
name: "Union with right null",
2120+
cql: "{1, 2} union null",
2121+
wantResult: newOrFatal(t, result.List{Value: []result.Value{newOrFatal(t, int32(1)), newOrFatal(t, int32(2))}, StaticType: &types.List{ElementType: types.Integer}}),
2122+
},
2123+
{
2124+
name: "Union with left null",
2125+
cql: "null union {1, 2}",
2126+
wantResult: newOrFatal(t, result.List{Value: []result.Value{newOrFatal(t, int32(1)), newOrFatal(t, int32(2))}, StaticType: &types.List{ElementType: types.Integer}}),
2127+
},
2128+
{
2129+
name: "Union with both null",
2130+
cql: "null as List<Integer> union null as List<Integer>",
2131+
wantResult: newOrFatal(t, result.List{Value: []result.Value{}, StaticType: &types.List{ElementType: types.Any}}),
2132+
},
2133+
{
2134+
name: "Union with symbolic operator",
2135+
cql: "{1, 2} | {1}",
2136+
wantResult: newOrFatal(t, result.List{Value: []result.Value{newOrFatal(t, int32(1)), newOrFatal(t, int32(2))}, StaticType: &types.List{ElementType: types.Integer}}),
2137+
},
2138+
{
2139+
name: "Union functional form",
2140+
cql: "Union({1, 2}, {1})",
2141+
wantResult: newOrFatal(t, result.List{Value: []result.Value{newOrFatal(t, int32(1)), newOrFatal(t, int32(2))}, StaticType: &types.List{ElementType: types.Integer}}),
2142+
},
2143+
}
2144+
for _, tc := range tests {
2145+
t.Run(tc.name, func(t *testing.T) {
2146+
p := newFHIRParser(t)
2147+
parsedLibs, err := p.Libraries(context.Background(), wrapInLib(t, tc.cql), parser.Config{})
2148+
if err != nil {
2149+
t.Fatalf("Parse returned unexpected error: %v", err)
2150+
}
2151+
if diff := cmp.Diff(tc.wantModel, getTESTRESULTModel(t, parsedLibs)); tc.wantModel != nil && diff != "" {
2152+
t.Errorf("Parse diff (-want +got):\n%s", diff)
2153+
}
2154+
2155+
results, err := interpreter.Eval(context.Background(), parsedLibs, defaultInterpreterConfig(t, p))
2156+
if err != nil {
2157+
t.Fatalf("Eval returned unexpected error: %v", err)
2158+
}
2159+
if diff := cmp.Diff(tc.wantResult, getTESTRESULT(t, results), protocmp.Transform()); diff != "" {
2160+
t.Errorf("Eval diff (-want +got)\n%v", diff)
2161+
}
2162+
2163+
})
2164+
}
2165+
}

tests/spectests/exclusions/exclusions.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,6 @@ func XMLTestFileExclusionDefinitions() map[string]XMLTestFileExclusions {
318318
GroupExcludes: []string{
319319
// TODO: b/342061715 - unsupported operators.
320320
"Descendents",
321-
"Union",
322321
},
323322
NamesExcludes: []string{
324323
// TODO: b/342061715 - unsupported operator.

0 commit comments

Comments
 (0)