949acd8f

Created Started Cancelled

By: Tom Sydney Kerckhove <syd@cs-syd.eu>

PromQL as a type rather than as text

A query assembled by concatenating strings carries the grammar in its author's
head, and three things go wrong that way, all of them silently: the quoting of
a label's value, the backslashes in a matcher's regular expression, which are
read twice over, and the parentheses around a division, which PromQL will
accept either way and answer differently.

PromQL.Expr is the grammar as a type and PromQL.Render is the one thing that
writes any PromQL, so it is the one thing that has to be right about it.  The
brackets are the renderer's to put in, and so is the layout, which breaks at
the loosest operator first.

promql-e2e is what says any of this is true.  Nothing in a Haskell repository
can decide whether a query is valid PromQL, or whether two spellings of one
mean the same thing, so the check starts a real Prometheus and asks it: every
expression the generators can write parses; leaving out the brackets the
grammar does not need means the same as writing every one of them, which is
how the precedence table is checked against the one Prometheus implements; and
breaking a query across lines means the same as writing it on one.

That check found seven things wrong while it was being written, which is the
argument for having it:

  * vector() with no argument, because arity was a list rather than a shape
  * label names that are not identifiers, which the server rejects by pointing
    inside a brace
  * log2 of a scalar, because PromQL has a type system
  * on() between a vector and a scalar
  * an offset written before a range rather than after it
  * a comparison between two scalars without the bool modifier
  * a lone surrogate in a label's value

The first five were the types and the renderer; the last two were the
generators.  PromQL's type system is still theirs to know rather than the
type's: making it unrepresentable means indexing Expr by what it evaluates to.