Range Functions and Operators

This type is only available in EdgeDB 2.0 or later.

Ranges represent some interval of values. The intervals can include or exclude their boundaries or can even omit one or both boundaries. Only some scalar types have corresponding range types:

  • range<int32>

  • range<int64>

  • range<float32>

  • range<float64>

  • range<decimal>

  • range<datetime>

  • range<cal::local_datetime>

  • range<cal::local_date>

Constructing ranges

There’s a special range() constructor function for making range values. This is a little different from how scalars, arrays and tuples are created typically in EdgeDB.

For example:

  1. db>
  1. select range(1, 10);
  1. {range(1, 10, inc_lower := true, inc_upper := false)}
  1. db>
  1. select range(2.2, 3.3);
  1. {range(2.2, 3.3, inc_lower := true, inc_upper := false)}

Broadly there are two kinds of ranges: discrete and contiguous. The discrete ranges are: range<int32>, range<int64>, and range<cal::local_date>. All ranges over discrete types get normalized in a way where the lower bound is included (if present) and the upper bound is excluded:

  1. db>
  1. select range(1, 10) = range(1, 9, inc_upper := true);
  1. {true}
  1. db>
  1. select range(1, 10) = range(0, 10, inc_lower := false);
  1. {true}

Ranges over contiguous types don’t have the same normalization mechanism because the underlying types don’t have granularity which could be used to easily include or exclude a boundary value.

Sometimes a range cannot contain any values, this is called an empty range. These kinds of ranges can arise from performing various operations on them, but they can also be constructed. There are basically two equivalent ways of constructing an empty range. It can be explicitly constructed by providing the same upper and lower bounds and specifying that at least one of them is not inclusive (which is the default for all range constructors):

  1. db>
  1. select range(1, 1);
  1. {range({}, empty := true)}

Alternatively, it’s possible to specify {} as a boundary and also provide the empty := true named-only argument. If the empty set is provided as a literal, it also needs to have a type cast, to specify which type of the range is being constructed:

  1. db>
  1. select range(<int64>{}, empty := true);
  1. {range({}, empty := true)}

Since empty ranges contain no values, they are all considered to be equal to each other (as long as the types are compatible):

  1. db>
  1. select range(1, 1) = range(<int64>{}, empty := true);
  1. {true}
  1. db>
  1. select range(1, 1) = range(42.0, 42.0);
  1. {true}
  1. db>
  1. select range(1, 1) = range(<cal::local_date>{}, empty := true);
  1. error: InvalidTypeError: operator '=' cannot be applied to operands of
  2. type 'range<std::int64>' and 'range<cal::local_date>'
  3. ┌─ query:1:8
  4. 1 select range(1, 1) = range(<cal::local_date>{}, empty := true);
  5. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  6. Consider using an explicit type cast or a conversion function.

JSON representation

Much like arrays and tuples the range types cannot be directly cast to a str, but instead can be cast into a json structure:

  1. db>
  1. select <json>range(1, 10);
  1. {"inc_lower": true, "inc_upper": false, "lower": 1, "upper": 10}

It’s also possible to cast in the other direction - from json to a specific range type:

  1. db>
  2. ...
  3. ...
  4. ...
  5. ...
  6. ...
  1. select <range<int64>>to_json('{
  2. "lower": 1,
  3. "inc_lower": true,
  4. "upper": 10,
  5. "inc_upper": false
  6. }');
  1. {range(1, 10, inc_lower := true, inc_upper := false)}

Empty ranges have a shorthand json representation:

  1. db>
  1. select <json>range(<int64>{}, empty := true);
  1. {"empty": true}

When casting from json to an empty range, all other fields may be omitted, but if they are present, they must be consistent with an empty range:

  1. db>
  1. select <range<int64>>to_json('{"empty": true}');
  1. {range({}, empty := true)}
  1. db>
  2. ...
  3. ...
  4. ...
  5. ...
  6. ...
  1. select <range<int64>>to_json('{
  2. "lower": 1,
  3. "inc_lower": true,
  4. "upper": 1,
  5. "inc_upper": false
  6. }');
  1. {range({}, empty := true)}
  1. db>
  2. ...
  3. ...
  4. ...
  5. ...
  6. ...
  7. ...
  1. select <range<int64>>to_json('{
  2. "lower": 1,
  3. "inc_lower": true,
  4. "upper": 1,
  5. "inc_upper": false,
  6. "empty": true
  7. }');
  1. {range({}, empty := true)}
  1. db>
  2. ...
  3. ...
  4. ...
  5. ...
  6. ...
  7. ...
  1. select <range<int64>>to_json('{
  2. "lower": 1,
  3. "inc_lower": true,
  4. "upper": 2,
  5. "inc_upper": false,
  6. "empty": true
  7. }');
  1. edgedb error: InvalidValueError: conflicting arguments in range
  2. constructor: "empty" is ``true`` while the specified bounds suggest
  3. otherwise

When casting from json to a range the lower and upper fields are optional, but the inclusivity fields inc_lower and inc_upper are mandatory. This is to address the fact that whether the range boundaries are included by default can vary based on system or context and being explicit avoids subtle errors. The only exception to this are empty ranges that can have just the "empty": true field.

Functions and operators

range < range

One range is before the other.

range > range

One range is after the other.

range <= range

One range is before or same as the other.

range >= range

One range is after or same as the other.

range + range

Range union.

range - range

Range subtraction.

range * range

Range intersection.

range()

Construct a range.

range_get_lower()

Return lower bound value.

range_get_upper()

Return upper bound value.

range_is_inclusive_lower()

Check whether lower bound is inclusive.

range_is_inclusive_upper()

Check whether upper bound is inclusive.

range_is_empty()

Check whether a range is empty.

range_unpack()

Return values from a range.

contains()

Check if an element or a range is within another range.

overlaps()

Check whether ranges overlap.

Reference

operator

range < range

Ranges New - 图1

Ranges New - 图2

Ranges New - 图3

range<anypoint> < range<anypoint> -> bool

One range is before the other.

Returns true if the lower bound of the first range is smaller than the lower bound of the second range. The unspecified lower bound is considered to be smaller than any specified lower bound. If the lower bounds are equal then the upper bounds are compared. Unspecified upper bound is considered to be greater than any specified upper bound.

  1. db>
  1. select range(1, 10) < range(2, 5);
  1. {true}
  1. db>
  1. select range(1, 10) < range(1, 15);
  1. {true}
  1. db>
  1. select range(1, 10) < range(1);
  1. {true}
  1. db>
  1. select range(1, 10) < range(<int64>{}, 10);
  1. {false}

An empty range is considered to come before any non-empty range.

  1. db>
  1. select range(1, 10) < range(10, 10);
  1. {false}
  1. db>
  1. select range(1, 10) < range(<int64>{}, empty := true);
  1. {false}

This is also how the order by clauses compares ranges.

operator

range > range

Ranges New - 图4

Ranges New - 图5

Ranges New - 图6

range<anypoint> > range<anypoint> -> bool

One range is after the other.

Returns true if the lower bound of the first range is greater than the lower bound of the second range. The unspecified lower bound is considered to be smaller than any specified lower bound. If the lower bounds are equal then the upper bounds are compared. Unspecified upper bound is considered to be greater than any specified upper bound.

  1. db>
  1. select range(1, 10) > range(2, 5);
  1. {false}
  1. db>
  1. select range(1, 10) > range(1, 5);
  1. {true}
  1. db>
  1. select range(1, 10) > range(1);
  1. {false}
  1. db>
  1. select range(1, 10) > range(<int64>{}, 10);
  1. {true}

An empty range is considered to come before any non-empty range.

  1. db>
  1. select range(1, 10) > range(10, 10);
  1. {true}
  1. db>
  1. select range(1, 10) > range(<int64>{}, empty := true);
  1. {true}

This is also how the order by clauses compares ranges.

operator

range <= range

Ranges New - 图7

Ranges New - 图8

Ranges New - 图9

range<anypoint> <= range<anypoint> -> bool

One range is before or same as the other.

Returns true if the ranges are identical or if the lower bound of the first range is smaller than the lower bound of the second range. The unspecified lower bound is considered to be smaller than any specified lower bound. If the lower bounds are equal then the upper bounds are compared. Unspecified upper bound is considered to be greater than any specified upper bound.

  1. db>
  1. select range(1, 10) <= range(1, 10);
  1. {true}
  1. db>
  1. select range(1, 10) <= range(2, 5);
  1. {true}
  1. db>
  1. select range(1, 10) <= range(1, 15);
  1. {true}
  1. db>
  1. select range(1, 10) <= range(1);
  1. {true}
  1. db>
  1. select range(1, 10) <= range(<int64>{}, 10);
  1. {false}

An empty range is considered to come before any non-empty range.

  1. db>
  1. select range(1, 10) <= range(10, 10);
  1. {false}
  1. db>
  1. select range(1, 1) <= range(10, 10);
  1. {true}
  1. db>
  1. select range(1, 10) <= range(<int64>{}, empty := true);
  1. {false}

This is also how the order by clauses compares ranges.

operator

range >= range

Ranges New - 图10

Ranges New - 图11

Ranges New - 图12

range<anypoint> >= range<anypoint> -> bool

One range is after or same as the other.

Returns true if the ranges are identical or if the lower bound of the first range is greater than the lower bound of the second range. The unspecified lower bound is considered to be smaller than any specified lower bound. If the lower bounds are equal then the upper bounds are compared. Unspecified upper bound is considered to be greater than any specified upper bound.

  1. db>
  1. select range(1, 10) >= range(2, 5);
  1. {false}
  1. db>
  1. select range(1, 10) >= range(1, 10);
  1. {true}
  1. db>
  1. select range(1, 10) >= range(1, 5);
  1. {true}
  1. db>
  1. select range(1, 10) >= range(1);
  1. {false}
  1. db>
  1. select range(1, 10) >= range(<int64>{}, 10);
  1. {true}

An empty range is considered to come before any non-empty range.

  1. db>
  1. select range(1, 10) >= range(10, 10);
  1. {true}
  1. db>
  1. select range(1, 1) >= range(10, 10);
  1. {true}
  1. db>
  1. select range(1, 10) >= range(<int64>{}, empty := true);
  1. {true}

This is also how the order by clauses compares ranges.

operator

range + range

Ranges New - 图13

Ranges New - 图14

Ranges New - 图15

range<anypoint> + range<anypoint> -> range<anypoint>

Range union.

Find the union of two ranges as long as the result is a single range without any discontinuities inside.

  1. db>
  1. select range(1, 10) + range(5, 15);
  1. {range(1, 15, inc_lower := true, inc_upper := false)}
  1. db>
  1. select range(1, 10) + range(5);
  1. {range(1, {}, inc_lower := true, inc_upper := false)}

operator

range - range

Ranges New - 图16

Ranges New - 图17

Ranges New - 图18

range<anypoint> - range<anypoint> -> range<anypoint>

Range subtraction.

Subtract one range from another. This is only valid if the resulting range does not have any discontinuities inside.

  1. db>
  1. select range(1, 10) - range(5, 15);
  1. {range(1, 5, inc_lower := true, inc_upper := false)}
  1. db>
  1. select range(1, 10) - range(<int64>{}, 5);
  1. {range(5, 10, inc_lower := true, inc_upper := false)}
  1. db>
  1. select range(1, 10) - range(0, 15);
  1. {range({}, empty := true)}

operator

range * range

Ranges New - 图19

Ranges New - 图20

Ranges New - 图21

range<anypoint> * range<anypoint> -> range<anypoint>

Range intersection.

Find the intersection of two ranges.

  1. db>
  1. select range(1, 10) * range(5, 15);
  1. {range(5, 10, inc_lower := true, inc_upper := false)}
  1. db>
  1. select range(1, 10) * range(-15, 15);
  1. {range(1, 10, inc_lower := true, inc_upper := false)}
  1. db>
  1. select range(1) * range(-15, 15);
  1. {range(1, 15, inc_lower := true, inc_upper := false)}
  1. db>
  1. select range(10) * range(<int64>{}, 1);
  1. {range({}, empty := true)}

function

range()

Ranges New - 图22

Ranges New - 图23

Ranges New - 图24

std::range(lower: optional std::anypoint = {}, upper: optional std::anypoint = {}, named only inc_lower: bool = true, named only inc_upper: bool = false, named only empty: bool = false) -> range<std::anypoint>

Construct a range.

Either one of lower or upper bounds can be set to {} to indicate an unbounded interval.

By default the lower bound is included and the upper bound is excluded from the range, but this can be controlled explicitly via the inc_lower and inc_upper named-only arguments.

  1. db>
  1. select range(1, 10);
  1. {range(1, 10, inc_lower := true, inc_upper := false)}
  1. db>
  1. select range(1.5, 7.5, inc_lower := false);
  1. {range(1.5, 7.5, inc_lower := false, inc_upper := false)}

Finally, an empty range can be created by using the empty named-only flag. The first argument still needs to be passed as an {} so that the type of the range can be inferred from it.

  1. db>
  1. select range(<int64>{}, empty := true);
  1. {range({}, empty := true)}

function

range_get_lower()

Ranges New - 图25

Ranges New - 图26

Ranges New - 图27

std::range_get_lower(r: range<anypoint>) -> optional anypoint

Return lower bound value.

Return the lower bound of the specified range.

  1. db>
  1. select range_get_lower(range(1, 10));
  1. {1}
  1. db>
  1. select range_get_lower(range(1.5, 7.5));
  1. {1.5}

function

range_is_inclusive_lower()

Ranges New - 图28

Ranges New - 图29

Ranges New - 图30

std::range_is_inclusive_lower(r: range<anypoint>) -> std::bool

Check whether lower bound is inclusive.

Return true if the lower bound is inclusive and false otherwise. If there is no lower bound, then it is never considered inclusive.

  1. db>
  1. select range_is_inclusive_lower(range(1, 10));
  1. {true}
  1. db>
  2. ...
  1. select range_is_inclusive_lower(
  2. range(1.5, 7.5, inc_lower := false));
  1. {false}
  1. db>
  1. select range_is_inclusive_lower(range(<int64>{}, 10));
  1. {false}

function

range_get_upper()

Ranges New - 图31

Ranges New - 图32

Ranges New - 图33

std::range_get_upper(r: range<anypoint>) -> optional anypoint

Return upper bound value.

Return the upper bound of the specified range.

  1. db>
  1. select range_get_upper(range(1, 10));
  1. {10}
  1. db>
  1. select range_get_upper(range(1.5, 7.5));
  1. {7.5}

function

range_is_inclusive_upper()

Ranges New - 图34

Ranges New - 图35

Ranges New - 图36

std::range_is_inclusive_upper(r: range<anypoint>) -> std::bool

Check whether upper bound is inclusive.

Return true if the upper bound is inclusive and false otherwise. If there is no upper bound, then it is never considered inclusive.

  1. db>
  1. select range_is_inclusive_upper(range(1, 10));
  1. {false}
  1. db>
  2. ...
  1. select range_is_inclusive_upper(
  2. range(1.5, 7.5, inc_upper := true));
  1. {true}
  1. db>
  1. select range_is_inclusive_upper(range(1));
  1. {false}

function

range_is_empty()

Ranges New - 图37

Ranges New - 图38

Ranges New - 图39

std::range_is_empty(val: range<anypoint>) -> bool

Check whether a range is empty.

Return true if the range contains no values and false otherwise.

  1. db>
  1. select range_is_empty(range(1, 10));
  1. {false}
  1. db>
  1. select range_is_empty(range(1, 1));
  1. {true}
  1. db>
  1. select range_is_empty(range(<int64>{}, empty := true));
  1. {true}

function

range_unpack()

Ranges New - 图40

Ranges New - 图41

Ranges New - 图42

std::range_unpack(val: range<anydiscrete>) -> set of anydiscretestd::range_unpack(val: range<anypoint>, step: anypoint) -> set of anypoint

Return values from a range.

For a range of discrete values this function when called without indicating a step value simply produces a set of all the values within the range, in order.

  1. db>
  1. select range_unpack(range(1, 10));
  1. {1, 2, 3, 4, 5, 6, 7, 8, 9}
  1. db>
  2. ...
  3. ...
  1. select range_unpack(range(
  2. <cal::local_date>'2022-07-01',
  3. <cal::local_date>'2022-07-10'));
  1. {
  2. <cal::local_date>'2022-07-01',
  3. <cal::local_date>'2022-07-02',
  4. <cal::local_date>'2022-07-03',
  5. <cal::local_date>'2022-07-04',
  6. <cal::local_date>'2022-07-05',
  7. <cal::local_date>'2022-07-06',
  8. <cal::local_date>'2022-07-07',
  9. <cal::local_date>'2022-07-08',
  10. <cal::local_date>'2022-07-09',
  11. }

For any range type a step value can be specified. Then the values will be picked from the range, starting at the lower boundary (skipping the boundary value itself if it’s not included in the range) and then producing the next value by adding the step to the previous one.

  1. db>
  1. select range_unpack(range(1.5, 7.5), 0.7);
  1. {1.5, 2.2, 2.9, 3.6, 4.3, 5, 5.7, 6.4}
  1. db>
  2. ...
  3. ...
  4. ...
  5. ...
  6. ...
  1. select range_unpack(
  2. range(
  3. <cal::local_datetime>'2022-07-01T00:00:00',
  4. <cal::local_datetime>'2022-12-01T00:00:00'
  5. ),
  6. <cal::relative_duration>'25 days 5 hours');
  1. {
  2. <cal::local_datetime>'2022-07-01T00:00:00',
  3. <cal::local_datetime>'2022-07-26T05:00:00',
  4. <cal::local_datetime>'2022-08-20T10:00:00',
  5. <cal::local_datetime>'2022-09-14T15:00:00',
  6. <cal::local_datetime>'2022-10-09T20:00:00',
  7. <cal::local_datetime>'2022-11-04T01:00:00',
  8. }

function

overlaps()

Ranges New - 图43

Ranges New - 图44

Ranges New - 图45

std::overlaps(l: range<anypoint>, r: range<anypoint>) -> std::bool

Check whether ranges overlap.

Return true if the ranges have any elements in common and false otherwise.

  1. db>
  1. select overlaps(range(1, 10), range(5));
  1. {true}
  1. db>
  1. select overlaps(range(1, 10), range(10));
  1. {false}