Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions interpreter/operator_dispatcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -1217,6 +1217,35 @@ func (i *interpreter) binaryOverloads(m model.IBinaryExpression) ([]convert.Over
Operands: []types.IType{&types.List{ElementType: types.Any}, &types.List{ElementType: types.Any}},
Result: evalExcept,
},
// Interval overloads
{
Operands: []types.IType{&types.Interval{PointType: types.Integer}, &types.Interval{PointType: types.Integer}},
Result: i.evalExceptInterval,
},
{
Operands: []types.IType{&types.Interval{PointType: types.Long}, &types.Interval{PointType: types.Long}},
Result: i.evalExceptInterval,
},
{
Operands: []types.IType{&types.Interval{PointType: types.Decimal}, &types.Interval{PointType: types.Decimal}},
Result: i.evalExceptInterval,
},
{
Operands: []types.IType{&types.Interval{PointType: types.Date}, &types.Interval{PointType: types.Date}},
Result: i.evalExceptInterval,
},
{
Operands: []types.IType{&types.Interval{PointType: types.DateTime}, &types.Interval{PointType: types.DateTime}},
Result: i.evalExceptInterval,
},
{
Operands: []types.IType{&types.Interval{PointType: types.Time}, &types.Interval{PointType: types.Time}},
Result: i.evalExceptInterval,
},
{
Operands: []types.IType{&types.Interval{PointType: types.Quantity}, &types.Interval{PointType: types.Quantity}},
Result: i.evalExceptInterval,
},
}, nil
case *model.Intersect:
return []convert.Overload[evalBinarySignature]{
Expand Down
138 changes: 138 additions & 0 deletions interpreter/operator_interval.go
Original file line number Diff line number Diff line change
Expand Up @@ -641,3 +641,141 @@ func evalWidthInterval(m model.IUnaryExpression, intervalObj result.Value) (resu
}
return result.Value{}, fmt.Errorf("internal error - unsupported point type in evalWidthInterval: %v", start.RuntimeType())
}

// except(left Interval<T>, right Interval<T>) Interval<T>
// https://cql.hl7.org/09-b-cqlreference.html#except-1
// Returns null if the result is empty, if right is properly contained within left, or if the
// bounds cannot be compared (uncertain DateTime precision, incomparable Quantity units).
func (i *interpreter) evalExceptInterval(m model.IBinaryExpression, lObj, rObj result.Value) (result.Value, error) {
if result.IsNull(lObj) || result.IsNull(rObj) {
return result.New(nil)
}
left, err := result.ToInterval(lObj)
if err != nil {
return result.Value{}, err
}
ls, le, err := startAndEnd(lObj, &i.evaluationTimestamp)
if err != nil {
return result.Value{}, err
}
rs, re, err := startAndEnd(rObj, &i.evaluationTimestamp)
if err != nil {
return result.Value{}, err
}
if result.IsNull(ls) || result.IsNull(le) || result.IsNull(rs) || result.IsNull(re) {
return result.New(nil)
}

rsToLe, err := compareIntervalPoints(rs, le)
if err != nil {
return result.Value{}, err
}
reToLs, err := compareIntervalPoints(re, ls)
if err != nil {
return result.Value{}, err
}
lsToRs, err := compareIntervalPoints(ls, rs)
if err != nil {
return result.Value{}, err
}
leToRe, err := compareIntervalPoints(le, re)
if err != nil {
return result.Value{}, err
}
for _, c := range []comparison{rsToLe, reToLs, lsToRs, leToRe} {
if c != leftBeforeRight && c != leftEqualRight && c != leftAfterRight {
return result.New(nil)
}
}

if rsToLe == leftAfterRight || reToLs == leftBeforeRight {
return lObj, nil
}
if lsToRs == leftBeforeRight && leToRe != leftAfterRight {
rs, err := toUnitOf(rs, le)
if err != nil {
return result.Value{}, err
}
high, err := predecessor(rs, &i.evaluationTimestamp)
if err != nil {
return result.Value{}, err
}
return result.New(result.Interval{Low: left.Low, High: high, LowInclusive: left.LowInclusive, HighInclusive: true, StaticType: left.StaticType})
}
if leToRe == leftAfterRight && lsToRs != leftBeforeRight {
re, err := toUnitOf(re, ls)
if err != nil {
return result.Value{}, err
}
low, err := successor(re, &i.evaluationTimestamp)
if err != nil {
return result.Value{}, err
}
return result.New(result.Interval{Low: low, High: left.High, LowInclusive: true, HighInclusive: left.HighInclusive, StaticType: left.StaticType})
}
return result.New(nil)
}

// compareIntervalPoints compares two non-null interval points of the same type. Quantities with
// different units are compared after unit conversion; unconvertible units return unsetComparison.
func compareIntervalPoints(a, b result.Value) (comparison, error) {
switch a.RuntimeType() {
case types.Integer:
l, r, err := applyToValues(a, b, result.ToInt32)
if err != nil {
return unsetComparison, err
}
return compareNumeral(l, r), nil
case types.Long:
l, r, err := applyToValues(a, b, result.ToInt64)
if err != nil {
return unsetComparison, err
}
return compareNumeral(l, r), nil
case types.Decimal:
l, r, err := applyToValues(a, b, result.ToFloat64)
if err != nil {
return unsetComparison, err
}
return compareNumeral(l, r), nil
case types.Quantity:
l, r, err := applyToValues(a, b, result.ToQuantity)
if err != nil {
return unsetComparison, err
}
lv := l.Value
if l.Unit != r.Unit {
if lv, err = ucum.ConvertUnit(l.Value, string(l.Unit), string(r.Unit)); err != nil {
return unsetComparison, nil
}
}
return compareNumeral(lv, r.Value), nil
case types.Date, types.DateTime, types.Time:
l, r, err := applyToValues(a, b, result.ToDateTime)
if err != nil {
return unsetComparison, err
}
return compareDateTime(l, r)
default:
return unsetComparison, fmt.Errorf("internal error - unsupported point type in except: %v", a.RuntimeType())
}
}

// toUnitOf converts a Quantity v to the unit of Quantity target. Other types are returned as is.
func toUnitOf(v, target result.Value) (result.Value, error) {
if v.RuntimeType() != types.Quantity {
return v, nil
}
q, t, err := applyToValues(v, target, result.ToQuantity)
if err != nil {
return result.Value{}, err
}
if q.Unit == t.Unit {
return v, nil
}
converted, err := ucum.ConvertUnit(q.Value, string(q.Unit), string(t.Unit))
if err != nil {
return result.Value{}, err
}
return result.New(result.Quantity{Value: converted, Unit: t.Unit})
}
14 changes: 12 additions & 2 deletions parser/operators.go
Original file line number Diff line number Diff line change
Expand Up @@ -1766,8 +1766,18 @@ func (p *Parser) loadSystemOperators() error {
},
// LIST OPERATORS - https://cql.hl7.org/09-b-cqlreference.html#list-operators-2
{
name: "Except",
operands: [][]types.IType{{&types.List{ElementType: types.Any}, &types.List{ElementType: types.Any}}},
name: "Except",
operands: [][]types.IType{
{&types.List{ElementType: types.Any}, &types.List{ElementType: types.Any}},
// INTERVAL OPERATORS - https://cql.hl7.org/09-b-cqlreference.html#interval-operators-3
{&types.Interval{PointType: types.Integer}, &types.Interval{PointType: types.Integer}},
{&types.Interval{PointType: types.Long}, &types.Interval{PointType: types.Long}},
{&types.Interval{PointType: types.Decimal}, &types.Interval{PointType: types.Decimal}},
{&types.Interval{PointType: types.Date}, &types.Interval{PointType: types.Date}},
{&types.Interval{PointType: types.DateTime}, &types.Interval{PointType: types.DateTime}},
{&types.Interval{PointType: types.Time}, &types.Interval{PointType: types.Time}},
{&types.Interval{PointType: types.Quantity}, &types.Interval{PointType: types.Quantity}},
},
model: func() model.IExpression {
return &model.Except{
BinaryExpression: &model.BinaryExpression{},
Expand Down
Loading
Loading