Caffeine Query Language
CQL is a very simple arithmetic language that is used to express SLIs. We expect it to grow over time, but for it to never be Turing complete.
The following operators are supported:
/: Division*: Multiplication+: Addition-: Subtraction(and): Parentheses
Grammar:
CQL_QUERY ::= EXP
EXP ::= ADD
ADD ::= MUL (('+' | '-') MUL)*
MUL ::= PRIMARY (('*' | '/') PRIMARY)*
PRIMARY ::= WORD | '(' EXP ')'
WORD ::= [A-Za-z_]+
Query Primitives
While I’ve already placed a fairly rigid constraint on CQL by saying it’s never going to be Turing complete, I’ll further constrain by saying it must map to a query primitive. A query primitive is:
A query form or structure that comes shipped with caffeine; a sort of standard library for queries.
For example, the first primitive we will support is good and valid over total. We map to this by ensuring our query reduces to a numerator over a denominator.
Phase to Phase
(1): Parsing
In this step, we parse the CQL query into its parse tree.
Input:
(A + B) / C
Output:
let query = CQL_QUERY
└─ EXP
└─ ADD
└─ MUL
├─ PRIMARY
│ └─ "(" EXP ")"
│ └─ ADD
│ ├─ MUL
│ │ └─ PRIMARY
│ │ └─ WORD("A")
│ ├─ "+"
│ └─ MUL
│ └─ PRIMARY
│ └─ WORD("B")
├─ "/"
└─ PRIMARY
└─ WORD("C")
UnresolvedQueryTemplateType(
...
query: query
...
)
(2): Linking
Linking in the caffeine compiler is mostly about resolving references between structures from different files. Since the query is all in one file, it isn’t modified at all in this step (it’s just moved from one object to another).
Input:
UnresolvedQueryTemplateType(
...
query: query
...
)
Output:
QueryTemplateType(
...
query: query
...
)
(3): Semantic Analysis
Here we perform a series of checks including, but not limited to:
- ensuring the types of query values are compatible
We do not modify the parse tree. We simply traverse to ensure its validity.
Input:
QueryTemplateType(
...
query: query
...
)
Output:
QueryTemplateType(
...
query: query
...
)
(4): Resolution
In this step, we attempt to reduce our CQL query to a query primitive. Then we resolve the query template variables. The final result is fairly close to what the backend generator will use
Input:
QueryTemplateType(
...
query: query
...
)
Output:
QueryTemplateType(
...
query: QUERY_PRIMITIVE(
numerator = "(SOME_QUERY_A + SOME_QUERY_B)"
denominator = "SOME_QUERY_C""
...
)
(5): Generation
Given a query primitive, we generator reliability artifact(s).
Input:
QueryTemplateType(
...
query: QUERY_PRIMITIVE(
numerator = "(SOME_QUERY_A + SOME_QUERY_B)"
denominator = "SOME_QUERY_C""
...
Output:
...
query {
numerator: "(SOME_QUERY_A + SOME_QUERY_B)"
denominator: "SOME_QUERY_C"
}
A Full Example
Specification
basic_types.yaml
basic_types:
- attribute_name: some_value
attribute_type: String
query_template_types.yaml
query_template_types:
- name: "valid_over_total"
query: "numerator / denominator"
specification_of_query_templates: ["numerator", "denominator"]
sli_types.yaml
types:
- name: success_rate
query_template_type: valid_over_total
typed_instatiation_of_query_templates:
numerator: "sum:hits_and_errors{status:success env:$$some_value$$}"
denominator: "sum:hits_and_errors{env:$$some_value$$}"
specification_of_query_templatized_variables:
- some_value
services.yaml
services:
- name: reliable_service
sli_types:
- success_rate
Instantiation
company/platform/reliable_service.yaml
slos:
- sli_type: "success_rate"
typed_instatiation_of_query_templatized_variables:
"some_value": "foobar"
threshold: 99.5
window_in_days: 30