Range field types

edit

Range field types represent a continuous range of values between an upper and lower bound. For example, a range can represent any date in October or any integer from 0 to 9. They are defined using the operators gt or gte for the lower bound, and lt or lte for the upper bound. They can be used for querying, and have limited support for aggregations. The only supported aggregations are histogram, cardinality.

The following range types are supported:

integer_range

A range of signed 32-bit integers with a minimum value of -231 and maximum of 231-1.

float_range

A range of single-precision 32-bit IEEE 754 floating point values.

long_range

A range of signed 64-bit integers with a minimum value of -263 and maximum of 263-1.

double_range

A range of double-precision 64-bit IEEE 754 floating point values.

date_range

A range of date values. Date ranges support various date formats through the format mapping parameter. Regardless of the format used, date values are parsed into an unsigned 64-bit integer representing milliseconds since the Unix epoch in UTC. Values containing the now date math expression are not supported.

ip_range

A range of ip values supporting either IPv4 or IPv6 (or mixed) addresses.

Below is an example of configuring a mapping with various range fields followed by an example that indexes several range types.

resp = client.indices.create(
    index="range_index",
    settings={
        "number_of_shards": 2
    },
    mappings={
        "properties": {
            "expected_attendees": {
                "type": "integer_range"
            },
            "time_frame": {
                "type": "date_range",
                "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
            }
        }
    },
)
print(resp)

resp1 = client.index(
    index="range_index",
    id="1",
    refresh=True,
    document={
        "expected_attendees": {
            "gte": 10,
            "lt": 20
        },
        "time_frame": {
            "gte": "2015-10-31 12:00:00",
            "lte": "2015-11-01"
        }
    },
)
print(resp1)
response = client.indices.create(
  index: 'range_index',
  body: {
    settings: {
      number_of_shards: 2
    },
    mappings: {
      properties: {
        expected_attendees: {
          type: 'integer_range'
        },
        time_frame: {
          type: 'date_range',
          format: 'yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis'
        }
      }
    }
  }
)
puts response

response = client.index(
  index: 'range_index',
  id: 1,
  refresh: true,
  body: {
    expected_attendees: {
      gte: 10,
      lt: 20
    },
    time_frame: {
      gte: '2015-10-31 12:00:00',
      lte: '2015-11-01'
    }
  }
)
puts response
const response = await client.indices.create({
  index: "range_index",
  settings: {
    number_of_shards: 2,
  },
  mappings: {
    properties: {
      expected_attendees: {
        type: "integer_range",
      },
      time_frame: {
        type: "date_range",
        format: "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis",
      },
    },
  },
});
console.log(response);

const response1 = await client.index({
  index: "range_index",
  id: 1,
  refresh: "true",
  document: {
    expected_attendees: {
      gte: 10,
      lt: 20,
    },
    time_frame: {
      gte: "2015-10-31 12:00:00",
      lte: "2015-11-01",
    },
  },
});
console.log(response1);
PUT range_index
{
  "settings": {
    "number_of_shards": 2
  },
  "mappings": {
    "properties": {
      "expected_attendees": {
        "type": "integer_range"
      },
      "time_frame": {
        "type": "date_range", 
        "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
      }
    }
  }
}

PUT range_index/_doc/1?refresh
{
  "expected_attendees" : { 
    "gte" : 10,
    "lt" : 20
  },
  "time_frame" : {
    "gte" : "2015-10-31 12:00:00", 
    "lte" : "2015-11-01"
  }
}

date_range types accept the same field parameters defined by the date type.

Example indexing a meeting with 10 to 20 attendees, not including 20.

Example date range using date time stamp.

The following is an example of a term query on the integer_range field named "expected_attendees". 12 is a value inside the range, so it will match.

resp = client.search(
    index="range_index",
    query={
        "term": {
            "expected_attendees": {
                "value": 12
            }
        }
    },
)
print(resp)
response = client.search(
  index: 'range_index',
  body: {
    query: {
      term: {
        expected_attendees: {
          value: 12
        }
      }
    }
  }
)
puts response
const response = await client.search({
  index: "range_index",
  query: {
    term: {
      expected_attendees: {
        value: 12,
      },
    },
  },
});
console.log(response);
GET range_index/_search
{
  "query" : {
    "term" : {
      "expected_attendees" : {
        "value": 12
      }
    }
  }
}

The result produced by the above query.

{
  "took": 13,
  "timed_out": false,
  "_shards" : {
    "total": 2,
    "successful": 2,
    "skipped" : 0,
    "failed": 0
  },
  "hits" : {
    "total" : {
        "value": 1,
        "relation": "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "range_index",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "expected_attendees" : {
            "gte" : 10, "lt" : 20
          },
          "time_frame" : {
            "gte" : "2015-10-31 12:00:00", "lte" : "2015-11-01"
          }
        }
      }
    ]
  }
}

The following is an example of a date_range query over the date_range field named "time_frame".

resp = client.search(
    index="range_index",
    query={
        "range": {
            "time_frame": {
                "gte": "2015-10-31",
                "lte": "2015-11-01",
                "relation": "within"
            }
        }
    },
)
print(resp)
response = client.search(
  index: 'range_index',
  body: {
    query: {
      range: {
        time_frame: {
          gte: '2015-10-31',
          lte: '2015-11-01',
          relation: 'within'
        }
      }
    }
  }
)
puts response
const response = await client.search({
  index: "range_index",
  query: {
    range: {
      time_frame: {
        gte: "2015-10-31",
        lte: "2015-11-01",
        relation: "within",
      },
    },
  },
});
console.log(response);
GET range_index/_search
{
  "query" : {
    "range" : {
      "time_frame" : { 
        "gte" : "2015-10-31",
        "lte" : "2015-11-01",
        "relation" : "within" 
      }
    }
  }
}

Range queries work the same as described in range query.

Range queries over range fields support a relation parameter which can be one of WITHIN, CONTAINS, INTERSECTS (default).

This query produces a similar result:

{
  "took": 13,
  "timed_out": false,
  "_shards" : {
    "total": 2,
    "successful": 2,
    "skipped" : 0,
    "failed": 0
  },
  "hits" : {
    "total" : {
        "value": 1,
        "relation": "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "range_index",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "expected_attendees" : {
            "gte" : 10, "lt" : 20
          },
          "time_frame" : {
            "gte" : "2015-10-31 12:00:00", "lte" : "2015-11-01"
          }
        }
      }
    ]
  }
}

IP Range

edit

In addition to the range format above, IP ranges can be provided in CIDR notation:

resp = client.indices.put_mapping(
    index="range_index",
    properties={
        "ip_allowlist": {
            "type": "ip_range"
        }
    },
)
print(resp)

resp1 = client.index(
    index="range_index",
    id="2",
    document={
        "ip_allowlist": "192.168.0.0/16"
    },
)
print(resp1)
response = client.indices.put_mapping(
  index: 'range_index',
  body: {
    properties: {
      ip_allowlist: {
        type: 'ip_range'
      }
    }
  }
)
puts response

response = client.index(
  index: 'range_index',
  id: 2,
  body: {
    ip_allowlist: '192.168.0.0/16'
  }
)
puts response
const response = await client.indices.putMapping({
  index: "range_index",
  properties: {
    ip_allowlist: {
      type: "ip_range",
    },
  },
});
console.log(response);

const response1 = await client.index({
  index: "range_index",
  id: 2,
  document: {
    ip_allowlist: "192.168.0.0/16",
  },
});
console.log(response1);
PUT range_index/_mapping
{
  "properties": {
    "ip_allowlist": {
      "type": "ip_range"
    }
  }
}

PUT range_index/_doc/2
{
  "ip_allowlist" : "192.168.0.0/16"
}

Parameters for range fields

edit

The following parameters are accepted by range types:

coerce

Try to convert strings to numbers and truncate fractions for integers. Accepts true (default) and false.

doc_values

Should the field be stored on disk in a column-stride fashion, so that it can later be used for sorting, aggregations, or scripting? Accepts true (default) or false.

index

Should the field be searchable? Accepts true (default) and false.

store

Whether the field value should be stored and retrievable separately from the _source field. Accepts true or false (default).

Synthetic _source

edit

Synthetic _source is Generally Available only for TSDB indices (indices that have index.mode set to time_series). For other indices synthetic _source is in technical preview. Features in technical preview may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.

range fields support synthetic _source in their default configuration. Synthetic _source cannot be used with doc_values disabled.

Synthetic source always sorts values and removes duplicates for all range fields except ip_range . Ranges are sorted by their lower bound and then by upper bound. For example:

resp = client.indices.create(
    index="idx",
    mappings={
        "_source": {
            "mode": "synthetic"
        },
        "properties": {
            "my_range": {
                "type": "long_range"
            }
        }
    },
)
print(resp)

resp1 = client.index(
    index="idx",
    id="1",
    document={
        "my_range": [
            {
                "gte": 200,
                "lte": 300
            },
            {
                "gte": 1,
                "lte": 100
            },
            {
                "gte": 200,
                "lte": 300
            },
            {
                "gte": 200,
                "lte": 500
            }
        ]
    },
)
print(resp1)
response = client.indices.create(
  index: 'idx',
  body: {
    mappings: {
      _source: {
        mode: 'synthetic'
      },
      properties: {
        my_range: {
          type: 'long_range'
        }
      }
    }
  }
)
puts response

response = client.index(
  index: 'idx',
  id: 1,
  body: {
    my_range: [
      {
        gte: 200,
        lte: 300
      },
      {
        gte: 1,
        lte: 100
      },
      {
        gte: 200,
        lte: 300
      },
      {
        gte: 200,
        lte: 500
      }
    ]
  }
)
puts response
PUT idx
{
  "mappings": {
    "_source": { "mode": "synthetic" },
    "properties": {
      "my_range": { "type": "long_range" }
    }
  }
}

PUT idx/_doc/1
{
  "my_range": [
    {
        "gte": 200,
        "lte": 300
    },
    {
        "gte": 1,
        "lte": 100
    },
    {
        "gte": 200,
        "lte": 300
    },
    {
        "gte": 200,
        "lte": 500
    }
  ]
}

Will become:

{
  "my_range": [
    {
        "gte": 1,
        "lte": 100
    },
    {
        "gte": 200,
        "lte": 300
    },
    {
        "gte": 200,
        "lte": 500
    }
  ]
}

Values of ip_range fields are not sorted but original order is not preserved. Duplicate ranges are removed. If ip_range field value is provided as a CIDR, it will be represented as a range of IP addresses in synthetic source.

For example:

resp = client.indices.create(
    index="idx",
    mappings={
        "_source": {
            "mode": "synthetic"
        },
        "properties": {
            "my_range": {
                "type": "ip_range"
            }
        }
    },
)
print(resp)

resp1 = client.index(
    index="idx",
    id="1",
    document={
        "my_range": [
            "10.0.0.0/24",
            {
                "gte": "10.0.0.0",
                "lte": "10.0.0.255"
            }
        ]
    },
)
print(resp1)
response = client.indices.create(
  index: 'idx',
  body: {
    mappings: {
      _source: {
        mode: 'synthetic'
      },
      properties: {
        my_range: {
          type: 'ip_range'
        }
      }
    }
  }
)
puts response

response = client.index(
  index: 'idx',
  id: 1,
  body: {
    my_range: [
      '10.0.0.0/24',
      {
        gte: '10.0.0.0',
        lte: '10.0.0.255'
      }
    ]
  }
)
puts response
PUT idx
{
  "mappings": {
    "_source": { "mode": "synthetic" },
    "properties": {
      "my_range": { "type": "ip_range" }
    }
  }
}

PUT idx/_doc/1
{
  "my_range": [
    "10.0.0.0/24",
    {
      "gte": "10.0.0.0",
      "lte": "10.0.0.255"
    }
  ]
}

Will become:

{
  "my_range": {
      "gte": "10.0.0.0",
      "lte": "10.0.0.255"
    }

}

Range field vales are always represented as inclusive on both sides with bounds adjusted accordingly. Default values for range bounds are represented as null. This is true even if range bound was explicitly provided. For example:

resp = client.indices.create(
    index="idx",
    mappings={
        "_source": {
            "mode": "synthetic"
        },
        "properties": {
            "my_range": {
                "type": "long_range"
            }
        }
    },
)
print(resp)

resp1 = client.index(
    index="idx",
    id="1",
    document={
        "my_range": {
            "gt": 200,
            "lt": 300
        }
    },
)
print(resp1)
response = client.indices.create(
  index: 'idx',
  body: {
    mappings: {
      _source: {
        mode: 'synthetic'
      },
      properties: {
        my_range: {
          type: 'long_range'
        }
      }
    }
  }
)
puts response

response = client.index(
  index: 'idx',
  id: 1,
  body: {
    my_range: {
      gt: 200,
      lt: 300
    }
  }
)
puts response
PUT idx
{
  "mappings": {
    "_source": { "mode": "synthetic" },
    "properties": {
      "my_range": { "type": "long_range" }
    }
  }
}

PUT idx/_doc/1
{
  "my_range": {
    "gt": 200,
    "lt": 300
  }
}

Will become:

{
  "my_range": {
    "gte": 201,
    "lte": 299
  }
}

Default values for range bounds are represented as null in synthetic source. This is true even if range bound was explicitly provided with default value. For example:

resp = client.indices.create(
    index="idx",
    mappings={
        "_source": {
            "mode": "synthetic"
        },
        "properties": {
            "my_range": {
                "type": "integer_range"
            }
        }
    },
)
print(resp)

resp1 = client.index(
    index="idx",
    id="1",
    document={
        "my_range": {
            "lte": 2147483647
        }
    },
)
print(resp1)
PUT idx
{
  "mappings": {
    "_source": { "mode": "synthetic" },
    "properties": {
      "my_range": { "type": "integer_range" }
    }
  }
}

PUT idx/_doc/1
{
  "my_range": {
    "lte": 2147483647
  }
}

Will become:

{
  "my_range": {
    "gte": null,
    "lte": null
  }
}

date ranges are formatted using provided format or by default using yyyy-MM-dd'T'HH:mm:ss.SSSZ format. For example:

resp = client.indices.create(
    index="idx",
    mappings={
        "_source": {
            "mode": "synthetic"
        },
        "properties": {
            "my_range": {
                "type": "date_range"
            }
        }
    },
)
print(resp)

resp1 = client.index(
    index="idx",
    id="1",
    document={
        "my_range": [
            {
                "gte": 1504224000000,
                "lte": 1504569600000
            },
            {
                "gte": "2017-09-01",
                "lte": "2017-09-10"
            }
        ]
    },
)
print(resp1)
response = client.indices.create(
  index: 'idx',
  body: {
    mappings: {
      _source: {
        mode: 'synthetic'
      },
      properties: {
        my_range: {
          type: 'date_range'
        }
      }
    }
  }
)
puts response

response = client.index(
  index: 'idx',
  id: 1,
  body: {
    my_range: [
      {
        gte: 1_504_224_000_000,
        lte: 1_504_569_600_000
      },
      {
        gte: '2017-09-01',
        lte: '2017-09-10'
      }
    ]
  }
)
puts response
PUT idx
{
  "mappings": {
    "_source": { "mode": "synthetic" },
    "properties": {
      "my_range": { "type": "date_range" }
    }
  }
}

PUT idx/_doc/1
{
  "my_range": [
    {
      "gte": 1504224000000,
      "lte": 1504569600000
    },
    {
      "gte": "2017-09-01",
      "lte": "2017-09-10"
    }
  ]
}

Will become:

{
  "my_range": [
    {
      "gte": "2017-09-01T00:00:00.000Z",
      "lte": "2017-09-05T00:00:00.000Z"
    },
    {
      "gte": "2017-09-01T00:00:00.000Z",
      "lte": "2017-09-10T00:00:00.000Z"
    }
  ]
}