Page
Library
Module
Module type
Parameter
Class
Class type
Source
Install dependencies with opam install --deps-only .
Build with make
esgg takes as an input an ES mapping (schema description) and actual query (with syntax for variables, described below). Often additional information is needed to map ES fields into proper OCaml types, this is achieved by attaching _meta annotation object to the affected field (ES only supports _meta at root level, so these annotations make it impossible to store extended mapping back into ES which is a pity), as follows:
"counts": {
"_meta": {
"optional": true
},
"properties": {
"hash": {
"type": "long",
"_meta": { "repr": "int64" }
},
"value": {
"type": "long"
}
}
},Supported _meta attributes:
{"list":true} - property is an array (mapped to list){"list":"sometimes"} - property is either an array or single element (mapped to json with custom ocaml module wrap that will need to be provided in scope){"optional":<true|false>} - property may be missing (mapped to option){"ignore":true} - skip property altogether{"fields_default_optional":true} - any subfield may be missing (can be overriden by per-field optional:false){"repr":"int64"} - override ES type, currently the only possible value is "int64" to ensure no bits are lost (by default long is mapped to OCaml int)Generated code allows to use application types for any fields. This is achieved by referencing specific type for each field in generated code, instead of the primitive type from the mapping, allowing consumer of the code to map it onto custom type etc. For example the field hash in example above will have type Counts.Hash.t in generated code. In order to compile the generated code this type must be present in scope and mapped to something useful. Default mapping (which just maps everything to corresponding primitive types) can be generated with esgg reflect <mapping name> <mapping.json>, e.g.:
esgg reflect hello_world src/mappings/hello_world.json >> src/mapping.mlwill generate the following, which should be edited manually as needed, e.g. by making Hash a module with an abstract type
module Counts = struct
module Hash = Id_(Int64_)
module Value = Id_(Long_)
endSyntax for variables in template json files is as follows:
$var for regular required variable$var? for optional variable (minimal surrounding scope is conditionally expunged)$(var:list) for list variable$(var:<type>) for typed variable with explicit type annotation (see below)When you need the same aggregation query against different fields, use a typed variable instead of duplicating the query template:
"aggregations": {
"grouped_keywords": {
"terms": {
"field": $(group_by:keyword),
"size": 10
}
}
}The type annotation determines the output bucket key type. The variable itself becomes a string input parameter — the caller passes the ES field name at runtime (e.g., "keyword_en").
Type can be any ES type - keyword, text, long, double, float, boolean, date, int64, ip, murmur3.
_esggThe _esgg field can be added to query templates to configure code generation behavior. This field is automatically filtered out before sending queries to Elasticsearch.
Supported configuration options:
{"matched_queries": true} - Include matched_queries field in output types even when _name is not explicitly present in the query template. This is useful when _name is defined inside query variables.{"inner_hits": [ ... ]} - Declare inner hits to include in output types even if the corresponding nested queries are provided via base/shared queries. Each entry describes one nested path.Example:
{
"_esgg": {
"matched_queries": true
},
"query": $query,
"size": 10
}_esgg.inner_hits specificationWhen inner hits are defined inside a base/shared query (not visible in this template), declare them explicitly so esgg can generate typed inner_hits in the output:
{
"_esgg": {
"inner_hits": [
{
"path": "comments", // required: nested path in the mapping
"name": "comments", // optional: key under inner_hits (defaults to path)
"size": 100, // optional
"from": 0, // optional
"_source": ["fieldA","fieldB"],// optional: standard ES source filtering for inner hits
"stored_fields": ["storedA"], // optional
"highlight": { // optional: ES highlight shape; fields keys are collected
"fields": { "comments.text": {} }
}
}
]
},
"query": $query
}To reuse shared definitions using the -shared <file.atd> option, the atd file must have the <esgg from="..."> annotation at the top of the file. The value of the annotation must correspond to the OCaml module containing the shared definitions.
Example:
# file.atd
<esgg from="Your_ocaml_module_name">
...atd type definitions...TODO document what is supported
Some notes follow:
The following aggregation types are supported:
composite - Composite aggregation for pagination
terms sourcehistogram sourcedate_histogram sourcegeotile_grid sourceDynamic (defined at runtime) filters are supported, as follows { "filters": { "filters": $x } }. In this case corresponding part of output will be quite untyped. $x is assumed to be a dictionary and result will be represented with dictionaries. For anonymous filters (ie array of filters) use $(x:list).
key_as_string is returned in output only when format is explicitly specified, to discourage fragile code.
Keyed aggregation expects explicit key for each range. from/to fields in response are not extracted.
Same as for range aggregation.
Specifying aggregation as variable ($var) will lead to an untyped json in place of aggregation output, this can be used as temporary workaround for unsupported aggregation types or for truly dynamic usecase (aggregation built at run-time).
Scripts are opaque, ie no type information is extracted and result is json.
make test runs regression tests in test/ verifying that input and output atd generated from query stays unchanged. Once there is an expected change in generated query - it should be committed. Tests are easy to add and fast to run.
TODO tests to verify that:
Copyright (c) 2018 Ahrefs github@ahrefs.com
This project is distributed under the terms of GPL Version 2. See LICENSE file for full license text.
NB the output of esgg, i.e. the generated code, is all yours of course :)