Add support for HAVING with aggregate queries

This commit is contained in:
Vikram Rangnekar
2019-04-01 01:18:14 -04:00
parent 89d435640b
commit 8ac8088b86
5 changed files with 114 additions and 69 deletions

View File

@ -263,8 +263,9 @@ func (v *selectBlock) renderJoinedColumns(w io.Writer, childIDs []int) error {
func (v *selectBlock) renderBaseSelect(w io.Writer, schema *DBSchema, childCols []*qcode.Column, childIDs []int) error {
var groupBy []int
isNotRoot := (v.parent != nil)
hasFilters := (v.sel.Where != nil)
isNotRoot := v.parent != nil
isFil := v.sel.Where != nil
isAgg := false
io.WriteString(w, " FROM (SELECT ")
@ -277,6 +278,7 @@ func (v *selectBlock) renderBaseSelect(w io.Writer, schema *DBSchema, childCols
if pl == 0 {
continue
}
isAgg = true
fn = cn[0 : pl-1]
cn = cn[pl:]
}
@ -303,28 +305,31 @@ func (v *selectBlock) renderBaseSelect(w io.Writer, schema *DBSchema, childCols
fmt.Fprintf(w, ` FROM "%s"`, v.sel.Table)
if isNotRoot || hasFilters {
if isNotRoot {
v.renderJoinTable(w, schema, childIDs)
}
if isNotRoot {
v.renderJoinTable(w, schema, childIDs)
}
switch {
case isNotRoot:
io.WriteString(w, ` WHERE (`)
if isNotRoot {
v.renderRelationship(w, schema)
}
if hasFilters {
err := v.renderWhere(w)
if err != nil {
v.renderRelationship(w, schema)
if isFil {
io.WriteString(w, ` AND `)
if err := v.renderWhere(w); err != nil {
return err
}
}
io.WriteString(w, `)`)
case isFil && !isAgg:
io.WriteString(w, ` WHERE (`)
if err := v.renderWhere(w); err != nil {
return err
}
io.WriteString(w, `)`)
}
if len(groupBy) != 0 {
if isAgg && len(groupBy) != 0 {
fmt.Fprintf(w, ` GROUP BY `)
for i, id := range groupBy {
@ -334,6 +339,14 @@ func (v *selectBlock) renderBaseSelect(w io.Writer, schema *DBSchema, childCols
io.WriteString(w, ", ")
}
}
if isFil {
io.WriteString(w, ` HAVING (`)
if err := v.renderWhere(w); err != nil {
return err
}
io.WriteString(w, `)`)
}
}
if len(v.sel.Paging.Limit) != 0 {
@ -368,8 +381,6 @@ func (v *selectBlock) renderOrderByColumns(w io.Writer) {
}
func (v *selectBlock) renderRelationship(w io.Writer, schema *DBSchema) {
hasFilters := (v.sel.Where != nil)
k := TTKey{v.sel.Table, v.parent.Table}
rel, ok := schema.RelMap[k]
if !ok {
@ -388,21 +399,15 @@ func (v *selectBlock) renderRelationship(w io.Writer, schema *DBSchema) {
case RelOneToManyThrough:
fmt.Fprintf(w, `(("%s"."%s") = ("%s"."%s"))`,
v.sel.Table, rel.Col1, rel.Through, rel.Col2)
}
if hasFilters {
io.WriteString(w, ` AND `)
}
}
func (v *selectBlock) renderWhere(w io.Writer) error {
st := util.NewStack()
if v.sel.Where == nil {
return nil
if v.sel.Where != nil {
st.Push(v.sel.Where)
}
st.Push(v.sel.Where)
for {
if st.Len() == 0 {

View File

@ -110,7 +110,7 @@ func compileGQLToPSQL(gql string) (string, error) {
return sqlStmt.String(), nil
}
func TestCompileGQLWithComplexArgs(t *testing.T) {
func withComplexArgs(t *testing.T) {
gql := `query {
products(
# returns only 30 items
@ -145,7 +145,7 @@ func TestCompileGQLWithComplexArgs(t *testing.T) {
}
}
func TestCompileGQLWithWhereMultiOr(t *testing.T) {
func withWhereMultiOr(t *testing.T) {
gql := `query {
products(
where: {
@ -173,7 +173,7 @@ func TestCompileGQLWithWhereMultiOr(t *testing.T) {
}
}
func TestCompileGQLWithWhereIsNull(t *testing.T) {
func withWhereIsNull(t *testing.T) {
gql := `query {
products(
where: {
@ -199,7 +199,7 @@ func TestCompileGQLWithWhereIsNull(t *testing.T) {
}
}
func TestCompileGQLWithWhereAndList(t *testing.T) {
func withWhereAndList(t *testing.T) {
gql := `query {
products(
where: {
@ -225,7 +225,7 @@ func TestCompileGQLWithWhereAndList(t *testing.T) {
}
}
func TestCompileGQLOneToMany(t *testing.T) {
func oneToMany(t *testing.T) {
gql := `query {
users {
email
@ -248,7 +248,7 @@ func TestCompileGQLOneToMany(t *testing.T) {
}
}
func TestCompileGQLBelongTo(t *testing.T) {
func belongsTo(t *testing.T) {
gql := `query {
products {
name
@ -271,7 +271,7 @@ func TestCompileGQLBelongTo(t *testing.T) {
}
}
func TestCompileGQLManyToMany(t *testing.T) {
func manyToMany(t *testing.T) {
gql := `query {
products {
name
@ -294,7 +294,7 @@ func TestCompileGQLManyToMany(t *testing.T) {
}
}
func TestCompileGQLManyToManyReverse(t *testing.T) {
func manyToManyReverse(t *testing.T) {
gql := `query {
customers {
email
@ -317,7 +317,7 @@ func TestCompileGQLManyToManyReverse(t *testing.T) {
}
}
func TestCompileGQLAggFunction(t *testing.T) {
func aggFunction(t *testing.T) {
gql := `query {
products {
name
@ -337,6 +337,39 @@ func TestCompileGQLAggFunction(t *testing.T) {
}
}
func aggFunctionWithFilter(t *testing.T) {
gql := `query {
products(where: { id: { gt: 10 } }) {
id
max_price
}
}`
sql := `SELECT json_object_agg('products', products) FROM (SELECT coalesce(json_agg("products"), '[]') AS "products" FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."max_price" AS "max_price") AS "sel_0")) AS "products" FROM (SELECT "products"."id", max("products"."price") AS max_price FROM "products" GROUP BY "products"."id" HAVING ((("products"."id") > (10))) LIMIT ('20') :: integer) AS "products_0" LIMIT ('20') :: integer) AS "products_0") AS "done_1337";`
resSQL, err := compileGQLToPSQL(gql)
if err != nil {
t.Fatal(err)
}
if resSQL != sql {
t.Fatal(errNotExpected)
}
}
func TestCompileGQL(t *testing.T) {
t.Run("withComplexArgs", withComplexArgs)
t.Run("withWhereAndList", withWhereAndList)
t.Run("withWhereIsNull", withWhereIsNull)
t.Run("withWhereMultiOr", withWhereMultiOr)
t.Run("belongsTo", belongsTo)
t.Run("oneToMany", oneToMany)
t.Run("manyToMany", manyToMany)
t.Run("manyToManyReverse", manyToManyReverse)
t.Run("aggFunction", aggFunction)
t.Run("aggFunctionWithFilter", aggFunctionWithFilter)
}
func BenchmarkCompileGQLToSQL(b *testing.B) {
gql := `query {
products(