Refuse a group_by naming an aggregate column
A group_by key that repeats a folded column made the column a grouping key and an aggregate at once, and for avg it left the upstream query grouping on a column the rewrite had removed, so the request failed as an opaque 502. - refuse a group_by field that names a folded aggregate or the avg column - cover the function-then-field ordering of the existing clash check
This commit is contained in:
@@ -138,6 +138,9 @@ func parseAggregate(query string) (*aggregateSpec, error) {
|
|||||||
}
|
}
|
||||||
for _, node := range ast[2:] {
|
for _, node := range ast[2:] {
|
||||||
for _, f := range groupByFields(node) {
|
for _, f := range groupByFields(node) {
|
||||||
|
if hasAgg(spec.aggs, f) || (spec.avg && f == avgColumn) {
|
||||||
|
return nil, groupByClash(f)
|
||||||
|
}
|
||||||
spec.keys = appendUnique(spec.keys, f)
|
spec.keys = appendUnique(spec.keys, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -157,6 +160,13 @@ func columnClash(name string) error {
|
|||||||
return fmt.Errorf("extract projects the column %q more than once: openvoxdb returns the repeat as %q, which pdbmux can neither key on nor fold", name, name+"_2")
|
return fmt.Errorf("extract projects the column %q more than once: openvoxdb returns the repeat as %q, which pdbmux can neither key on nor fold", name, name+"_2")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// groupByClash refuses a group_by naming an aggregate column: the same key
|
||||||
|
// cannot both identify a row and be folded across backends. openvoxdb rejects
|
||||||
|
// the shape too, so refusing here is a 400 instead of a failed upstream query.
|
||||||
|
func groupByClash(name string) error {
|
||||||
|
return fmt.Errorf("group_by names the aggregate column %q: pdbmux folds that column across backends, so it cannot also be a grouping key", name)
|
||||||
|
}
|
||||||
|
|
||||||
// rewriteAvg replaces the client's avg column with the sum and count of the same
|
// rewriteAvg replaces the client's avg column with the sum and count of the same
|
||||||
// expression, so the true weighted average can be computed from the shards:
|
// expression, so the true weighted average can be computed from the shards:
|
||||||
// Postgres avg(x) is sum(x)/count(x), and both of those do combine.
|
// Postgres avg(x) is sum(x)/count(x), and both of those do combine.
|
||||||
|
|||||||
@@ -400,6 +400,49 @@ func TestParseAggregate_FieldClashingWithAFunctionIsRefused(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The ordering the sibling case does not cover: the function column comes first
|
||||||
|
// and a later plain field takes the response key it already named.
|
||||||
|
func TestParseAggregate_FunctionClashingWithALaterFieldIsRefused(t *testing.T) {
|
||||||
|
const q = `["extract",[["function","count","certname"],"count"],["=","certname","h1"]]`
|
||||||
|
spec, err := parseAggregate(q)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("parseAggregate(%s) = %+v, want a refusal", q, spec)
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "count") {
|
||||||
|
t.Errorf("error %q does not name the clashing column", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A group_by naming a folded column would make it a grouping key and an
|
||||||
|
// aggregate at once, so it is refused rather than sent upstream to fail.
|
||||||
|
func TestParseAggregate_GroupByOnAnAggregateColumnIsRefused(t *testing.T) {
|
||||||
|
for _, fn := range []string{"count", "sum", "min", "max", "avg"} {
|
||||||
|
q := `["extract",[["function","` + fn + `","line"]],["group_by","` + fn + `"]]`
|
||||||
|
spec, err := parseAggregate(q)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("parseAggregate(%s) = %+v, want a refusal", q, spec)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), fn) {
|
||||||
|
t.Errorf("error %q does not name the clashing column %q", err, fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The refusal is limited to the folded columns: grouping on a plain field or on
|
||||||
|
// a row function's own column stays legitimate.
|
||||||
|
func TestParseAggregate_GroupByOnANonAggregateColumnIsKept(t *testing.T) {
|
||||||
|
for _, q := range []string{
|
||||||
|
`["extract",[["function","count","certname"],"status"],["group_by","status"]]`,
|
||||||
|
`["extract",[["function","to_string","producer_timestamp","FMDD"],["function","count"]],["group_by",["function","to_string","producer_timestamp","FMDD"]]]`,
|
||||||
|
`["extract",[["function","avg","line"],"type"],["group_by","type"]]`,
|
||||||
|
} {
|
||||||
|
if spec, err := parseAggregate(q); err != nil || spec == nil {
|
||||||
|
t.Errorf("parseAggregate(%s) = %+v, %v, want an accepted spec", q, spec, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// A repeated plain field projects the same value twice, so it is no clash: the
|
// A repeated plain field projects the same value twice, so it is no clash: the
|
||||||
// duplicate carries nothing the grouping key has not already got.
|
// duplicate carries nothing the grouping key has not already got.
|
||||||
func TestParseAggregate_RepeatedPlainFieldIsKept(t *testing.T) {
|
func TestParseAggregate_RepeatedPlainFieldIsKept(t *testing.T) {
|
||||||
|
|||||||
Reference in New Issue
Block a user