From a8ad87115e393ef4abfdb806d1aec9226a9f6db2 Mon Sep 17 00:00:00 2001 From: Vikram Rangnekar Date: Thu, 3 Oct 2019 03:08:01 -0400 Subject: [PATCH] Fix issues with mutation SQL --- config/allow.list | 15 +- docs/.vuepress/components/HomeLayout.vue | 20 +- docs/README.md | 30 +++ migrate/migrate_test.go | 8 +- psql/insert.go | 8 + psql/insert_test.go | 14 +- psql/select.go | 306 +++++++++++------------ psql/select_test.go | 106 ++++---- qcode/qcode.go | 12 +- rails-app/Dockerfile | 2 +- serv/allow.go | 16 +- serv/cmd.go | 3 +- serv/cmd_migrate.go | 2 +- serv/cmd_test.go | 6 +- serv/utils.go | 2 +- serv/utils_test.go | 33 +++ 16 files changed, 317 insertions(+), 266 deletions(-) create mode 100644 docs/README.md diff --git a/config/allow.list b/config/allow.list index 480c585..16e525d 100644 --- a/config/allow.list +++ b/config/allow.list @@ -39,6 +39,7 @@ query { } } + variables { "update": { "name": "Hellooooo", @@ -71,17 +72,3 @@ mutation { } } -query { - users { - id - email - picture: avatar - products(limit: 2, where: {price: {gt: 10}}) { - id - name - description - } - } -} - - diff --git a/docs/.vuepress/components/HomeLayout.vue b/docs/.vuepress/components/HomeLayout.vue index 794a0ea..0a61ad1 100644 --- a/docs/.vuepress/components/HomeLayout.vue +++ b/docs/.vuepress/components/HomeLayout.vue @@ -112,7 +112,7 @@ - diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..60bdf6f --- /dev/null +++ b/docs/README.md @@ -0,0 +1,30 @@ +--- +layout: HomeLayout + +home: true +heroText: "Super Graph" +heroImage: /super-graph-web-ui-half.png +heroImageMobile: /super-graph-web-ui.png +tagline: Build web products faster. Instant APIs for your apps +longTagline: Get an instant high performance GraphQL API for Postgres. No code needed. GraphQL is automatically transformed into efficient database queries. +actionText: Get Started, Free, Open Source → +actionLink: /guide + +description: Super Graph can automatically learn a Postgres database and instantly serve it as a fast and secured GraphQL API. It comes with tools to create a new app and manage it's database. You get it all, a very productive developer and a highly scalable app backend. It's designed to work well on serverless platforms by Google, AWS, Microsoft, etc. The goal is to save you a ton of time and money so you can focus on you're apps core value. + +features: +- title: Simple + details: Easy config file, quick to deploy, No code needed. It just works. +- title: High Performance + details: Compiles your GraphQL into a fast SQL query in realtime. +- title: Ruby-on-Rails + details: Can read Rails cookies and supports rails database conventions. +- title: Serverless + details: Instant startup for scale to zero environments like Google Cloud Run, App Engine, AWS Lambda +- title: Go Lang + details: Go is a language created at Google to build fast and secure web services. +- title: Free and Open Source + details: Not a VC funded startup. Not even a startup just good old open source code + +footer: MIT Licensed | Copyright © 2018-present Vikram Rangnekar +--- diff --git a/migrate/migrate_test.go b/migrate/migrate_test.go index 1256062..bd66642 100644 --- a/migrate/migrate_test.go +++ b/migrate/migrate_test.go @@ -1,14 +1,11 @@ package migrate_test import ( - "fmt" - "testing" - - "github.com/jackc/pgx" - "github.com/jackc/tern/migrate" . "gopkg.in/check.v1" ) +/* + type MigrateSuite struct { conn *pgx.Conn } @@ -352,3 +349,4 @@ func Example_OnStartMigrationProgressLogging() { // Output: // Migrating up: create a table } +*/ diff --git a/psql/insert.go b/psql/insert.go index d040607..8b24c2d 100644 --- a/psql/insert.go +++ b/psql/insert.go @@ -10,6 +10,8 @@ import ( "github.com/dosco/super-graph/qcode" ) +var zeroPaging = qcode.Paging{} + func (co *Compiler) compileMutation(qc *qcode.QCode, w *bytes.Buffer, vars Variables) (uint32, error) { if len(qc.Selects) == 0 { return 0, errors.New("empty query") @@ -38,6 +40,12 @@ func (co *Compiler) compileMutation(qc *qcode.QCode, w *bytes.Buffer, vars Varia return 0, errors.New("valid mutations are 'insert' and 'update'") } + root.Paging = zeroPaging + root.DistinctOn = root.DistinctOn[:] + root.OrderBy = root.OrderBy[:] + root.Where = nil + root.Args = nil + return c.compileQuery(qc, w) } diff --git a/psql/insert_test.go b/psql/insert_test.go index b244a20..bee8fa6 100644 --- a/psql/insert_test.go +++ b/psql/insert_test.go @@ -2,7 +2,6 @@ package psql import ( "encoding/json" - "fmt" "testing" ) @@ -13,7 +12,7 @@ func simpleInsert(t *testing.T) { } }` - sql := `WITH "users" AS (WITH "input" AS (SELECT {{data}}::json AS j) INSERT INTO users (full_name, email) SELECT full_name, email FROM input i, json_populate_record(NULL::users, i.j) t RETURNING *) SELECT json_object_agg('user', sel_json_0) FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "user_0"."id" AS "id") AS "sel_0")) AS "sel_json_0" FROM (SELECT "user"."id" FROM "users" AS "user" WHERE ((("user"."id") = {{user_id}})) LIMIT ('1') :: integer) AS "user_0" LIMIT ('1') :: integer) AS "done_1337";` + sql := `WITH "users" AS (WITH "input" AS (SELECT {{data}}::json AS j) INSERT INTO users (full_name, email) SELECT full_name, email FROM input i, json_populate_record(NULL::users, i.j) t RETURNING *) SELECT json_object_agg('user', sel_json_0) FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "users_0"."id" AS "id") AS "sel_0")) AS "sel_json_0" FROM (SELECT "users"."id" FROM "users" LIMIT ('1') :: integer) AS "users_0") AS "done_1337";` vars := map[string]json.RawMessage{ "data": json.RawMessage(`{"email": "reannagreenholt@orn.com", "full_name": "Flo Barton"}`), @@ -24,8 +23,6 @@ func simpleInsert(t *testing.T) { t.Fatal(err) } - fmt.Println(">", string(resSQL)) - if string(resSQL) != sql { t.Fatal(errNotExpected) } @@ -39,7 +36,7 @@ func singleInsert(t *testing.T) { } }` - sql := `WITH product AS (WITH input AS (SELECT {{insert}}::json AS j) INSERT INTO product (name, description) SELECT name, description FROM input i, json_populate_record(NULL::product, i.j) t RETURNING *) SELECT json_object_agg('product', product) FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "product_0"."id" AS "id", "product_0"."name" AS "name") AS "sel_0")) AS "product" FROM (SELECT "product"."id", "product"."name" FROM "products" AS "product" WHERE ((("product"."price") > 0) AND (("product"."price") < 8) AND (("id") = 15)) LIMIT ('1') :: integer) AS "product_0" LIMIT ('1') :: integer) AS "done_1337";` + sql := `WITH "products" AS (WITH "input" AS (SELECT {{insert}}::json AS j) INSERT INTO products (name, description) SELECT name, description FROM input i, json_populate_record(NULL::products, i.j) t RETURNING *) SELECT json_object_agg('product', sel_json_0) FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name") AS "sel_0")) AS "sel_json_0" FROM (SELECT "products"."id", "products"."name" FROM "products" LIMIT ('1') :: integer) AS "products_0") AS "done_1337";` vars := map[string]json.RawMessage{ "insert": json.RawMessage(` { "name": "my_name", "woo": { "hoo": "goo" }, "description": "my_desc" }`), @@ -63,7 +60,7 @@ func bulkInsert(t *testing.T) { } }` - sql := `WITH product AS (WITH input AS (SELECT {{insert}}::json AS j) INSERT INTO product (name, description) SELECT name, description FROM input i, json_populate_recordset(NULL::product, i.j) t RETURNING *) SELECT json_object_agg('product', product) FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "product_0"."id" AS "id", "product_0"."name" AS "name") AS "sel_0")) AS "product" FROM (SELECT "product"."id", "product"."name" FROM "products" AS "product" WHERE ((("product"."price") > 0) AND (("product"."price") < 8) AND (("id") = 15)) LIMIT ('1') :: integer) AS "product_0" LIMIT ('1') :: integer) AS "done_1337";` + sql := `WITH "products" AS (WITH "input" AS (SELECT {{insert}}::json AS j) INSERT INTO products (name, description) SELECT name, description FROM input i, json_populate_recordset(NULL::products, i.j) t RETURNING *) SELECT json_object_agg('product', sel_json_0) FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name") AS "sel_0")) AS "sel_json_0" FROM (SELECT "products"."id", "products"."name" FROM "products" LIMIT ('1') :: integer) AS "products_0") AS "done_1337";` vars := map[string]json.RawMessage{ "insert": json.RawMessage(` [{ "name": "my_name", "woo": { "hoo": "goo" }, "description": "my_desc" }]`), @@ -87,7 +84,7 @@ func singleUpdate(t *testing.T) { } }` - sql := `WITH product AS (WITH input AS (SELECT {{update}}::json AS j) UPDATE product SET (name, description) = (SELECT name, description FROM input i, json_populate_record(NULL::product, i.j) t) WHERE (("product"."price") > 0) AND (("product"."price") < 8) AND (("product"."id") = 1) AND (("id") = 15) RETURNING *) SELECT json_object_agg('product', product) FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "product_0"."id" AS "id", "product_0"."name" AS "name") AS "sel_0")) AS "product" FROM (SELECT "product"."id", "product"."name" FROM "products" AS "product" WHERE ((("product"."price") > 0) AND (("product"."price") < 8) AND (("product"."id") = 1) AND (("id") = 15)) LIMIT ('1') :: integer) AS "product_0" LIMIT ('1') :: integer) AS "done_1337";` + sql := `WITH "products" AS (WITH "input" AS (SELECT {{update}}::json AS j) UPDATE products SET (name, description) = (SELECT name, description FROM input i, json_populate_record(NULL::products, i.j) t) WHERE (("products"."price") > 0) AND (("products"."price") < 8) AND (("products"."id") = 1) AND (("products"."id") = 15) RETURNING *) SELECT json_object_agg('product', sel_json_0) FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name") AS "sel_0")) AS "sel_json_0" FROM (SELECT "products"."id", "products"."name" FROM "products" LIMIT ('1') :: integer) AS "products_0") AS "done_1337";` vars := map[string]json.RawMessage{ "update": json.RawMessage(` { "name": "my_name", "woo": { "hoo": "goo" }, "description": "my_desc" }`), @@ -111,7 +108,7 @@ func delete(t *testing.T) { } }` - sql := `DELETE FROM product WHERE (("product"."price") > 0) AND (("product"."price") < 8) AND (("product"."id") = 1) RETURNING *) SELECT json_object_agg('product', product) FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "product_0"."id" AS "id", "product_0"."name" AS "name") AS "sel_0")) AS "product" FROM (SELECT "product"."id", "product"."name" FROM "products" AS "product" WHERE ((("product"."price") > 0) AND (("product"."price") < 8) AND (("product"."id") = 1)) LIMIT ('1') :: integer) AS "product_0" LIMIT ('1') :: integer) AS "done_1337";` + sql := `DELETE FROM products WHERE (("products"."price") > 0) AND (("products"."price") < 8) AND (("products"."id") = 1) RETURNING *) SELECT json_object_agg('product', sel_json_0) FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name") AS "sel_0")) AS "sel_json_0" FROM (SELECT "products"."id", "products"."name" FROM "products" LIMIT ('1') :: integer) AS "products_0") AS "done_1337";` vars := map[string]json.RawMessage{ "update": json.RawMessage(` { "name": "my_name", "woo": { "hoo": "goo" }, "description": "my_desc" }`), @@ -133,5 +130,4 @@ func TestCompileInsert(t *testing.T) { t.Run("bulkInsert", bulkInsert) t.Run("singleUpdate", singleUpdate) t.Run("delete", delete) - } diff --git a/psql/select.go b/psql/select.go index f95571a..1e3a3f3 100644 --- a/psql/select.go +++ b/psql/select.go @@ -192,15 +192,15 @@ func (c *compilerContext) processChildren(sel *qcode.Select, ti *DBTableInfo) (u fallthrough case RelBelongTo: if _, ok := colmap[rel.Col2]; !ok { - cols = append(cols, &qcode.Column{sel.Table, rel.Col2, rel.Col2}) + cols = append(cols, &qcode.Column{ti.Name, rel.Col2, rel.Col2}) } case RelOneToManyThrough: if _, ok := colmap[rel.Col1]; !ok { - cols = append(cols, &qcode.Column{sel.Table, rel.Col1, rel.Col1}) + cols = append(cols, &qcode.Column{ti.Name, rel.Col1, rel.Col1}) } case RelRemote: if _, ok := colmap[rel.Col1]; !ok { - cols = append(cols, &qcode.Column{sel.Table, rel.Col1, rel.Col2}) + cols = append(cols, &qcode.Column{ti.Name, rel.Col1, rel.Col2}) } skipped |= (1 << uint(id)) @@ -225,7 +225,7 @@ func (c *compilerContext) renderSelect(sel *qcode.Select, ti *DBTableInfo) (uint c.w.WriteString(`"`) if hasOrder { - err := c.renderOrderBy(sel) + err := c.renderOrderBy(sel, ti) if err != nil { return skipped, err } @@ -241,7 +241,7 @@ func (c *compilerContext) renderSelect(sel *qcode.Select, ti *DBTableInfo) (uint c.w.WriteString(`SELECT `) if len(sel.DistinctOn) != 0 { - c.renderDistinctOn(sel) + c.renderDistinctOn(sel, ti) } c.w.WriteString(`row_to_json((`) @@ -252,11 +252,11 @@ func (c *compilerContext) renderSelect(sel *qcode.Select, ti *DBTableInfo) (uint c.w.WriteString(`" FROM (SELECT `) // Combined column names - c.renderColumns(sel) + c.renderColumns(sel, ti) - c.renderRemoteRelColumns(sel) + c.renderRemoteRelColumns(sel, ti) - err := c.renderJoinedColumns(sel, skipped) + err := c.renderJoinedColumns(sel, ti, skipped) if err != nil { return skipped, err } @@ -271,7 +271,7 @@ func (c *compilerContext) renderSelect(sel *qcode.Select, ti *DBTableInfo) (uint // END-ROW-TO-JSON if hasOrder { - c.renderOrderByColumns(sel) + c.renderOrderByColumns(sel, ti) } // END-SELECT @@ -289,23 +289,25 @@ func (c *compilerContext) renderSelectClose(sel *qcode.Select, ti *DBTableInfo) hasOrder := len(sel.OrderBy) != 0 if hasOrder { - err := c.renderOrderBy(sel) + err := c.renderOrderBy(sel, ti) if err != nil { return err } } - if len(sel.Paging.Limit) != 0 { - //fmt.Fprintf(w, ` LIMIT ('%s') :: integer`, c.sel.Paging.Limit) - c.w.WriteString(` LIMIT ('`) - c.w.WriteString(sel.Paging.Limit) - c.w.WriteString(`') :: integer`) + if sel.Action == 0 { + if len(sel.Paging.Limit) != 0 { + //fmt.Fprintf(w, ` LIMIT ('%s') :: integer`, c.sel.Paging.Limit) + c.w.WriteString(` LIMIT ('`) + c.w.WriteString(sel.Paging.Limit) + c.w.WriteString(`') :: integer`) - } else if ti.Singular { - c.w.WriteString(` LIMIT ('1') :: integer`) + } else if ti.Singular { + c.w.WriteString(` LIMIT ('1') :: integer`) - } else { - c.w.WriteString(` LIMIT ('20') :: integer`) + } else { + c.w.WriteString(` LIMIT ('20') :: integer`) + } } if len(sel.Paging.Offset) != 0 { @@ -367,18 +369,18 @@ func (c *compilerContext) renderJoinTable(sel *qcode.Select) error { return nil } -func (c *compilerContext) renderColumns(sel *qcode.Select) { +func (c *compilerContext) renderColumns(sel *qcode.Select, ti *DBTableInfo) { for i, col := range sel.Cols { if i != 0 { io.WriteString(c.w, ", ") } //fmt.Fprintf(w, `"%s_%d"."%s" AS "%s"`, //c.sel.Table, c.sel.ID, col.Name, col.FieldName) - colWithTableIDAlias(c.w, sel.Table, sel.ID, col.Name, col.FieldName) + colWithTableIDAlias(c.w, ti.Name, sel.ID, col.Name, col.FieldName) } } -func (c *compilerContext) renderRemoteRelColumns(sel *qcode.Select) { +func (c *compilerContext) renderRemoteRelColumns(sel *qcode.Select, ti *DBTableInfo) { i := 0 for _, id := range sel.Children { @@ -393,13 +395,13 @@ func (c *compilerContext) renderRemoteRelColumns(sel *qcode.Select) { } //fmt.Fprintf(w, `"%s_%d"."%s" AS "%s"`, //c.sel.Table, c.sel.ID, rel.Col1, rel.Col2) - colWithTableID(c.w, sel.Table, sel.ID, rel.Col1) + colWithTableID(c.w, ti.Name, sel.ID, rel.Col1) alias(c.w, rel.Col2) i++ } } -func (c *compilerContext) renderJoinedColumns(sel *qcode.Select, skipped uint32) error { +func (c *compilerContext) renderJoinedColumns(sel *qcode.Select, ti *DBTableInfo, skipped uint32) error { colsRendered := len(sel.Cols) != 0 for _, id := range sel.Children { @@ -411,11 +413,12 @@ func (c *compilerContext) renderJoinedColumns(sel *qcode.Select, skipped uint32) if skipThis { continue } - sel := &c.s[id] + childSel := &c.s[id] //fmt.Fprintf(w, `"%s_%d_join"."%s" AS "%s"`, //s.Table, s.ID, s.Table, s.FieldName) - colWithTableIDSuffixAlias(c.w, sel.Table, sel.ID, "_join", sel.Table, sel.FieldName) + colWithTableIDSuffixAlias(c.w, childSel.Table, childSel.ID, + "_join", childSel.Table, childSel.FieldName) } return nil @@ -447,7 +450,7 @@ func (c *compilerContext) renderBaseSelect(sel *qcode.Select, ti *DBTableInfo, //fmt.Fprintf(w, `ts_rank("%s"."%s", to_tsquery('%s')) AS %s`, //c.sel.Table, cn, arg.Val, col.Name) c.w.WriteString(`ts_rank(`) - colWithTable(c.w, sel.Table, cn) + colWithTable(c.w, ti.Name, cn) c.w.WriteString(`, to_tsquery('`) c.w.WriteString(arg.Val) c.w.WriteString(`')`) @@ -460,7 +463,7 @@ func (c *compilerContext) renderBaseSelect(sel *qcode.Select, ti *DBTableInfo, //fmt.Fprintf(w, `ts_headline("%s"."%s", to_tsquery('%s')) AS %s`, //c.sel.Table, cn, arg.Val, col.Name) c.w.WriteString(`ts_headlinek(`) - colWithTable(c.w, sel.Table, cn) + colWithTable(c.w, ti.Name, cn) c.w.WriteString(`, to_tsquery('`) c.w.WriteString(arg.Val) c.w.WriteString(`')`) @@ -481,7 +484,7 @@ func (c *compilerContext) renderBaseSelect(sel *qcode.Select, ti *DBTableInfo, //fmt.Fprintf(w, `%s("%s"."%s") AS %s`, fn, c.sel.Table, cn, col.Name) c.w.WriteString(fn) c.w.WriteString(`(`) - colWithTable(c.w, sel.Table, cn) + colWithTable(c.w, ti.Name, cn) c.w.WriteString(`)`) alias(c.w, col.Name) } @@ -489,7 +492,7 @@ func (c *compilerContext) renderBaseSelect(sel *qcode.Select, ti *DBTableInfo, } else { groupBy = append(groupBy, i) //fmt.Fprintf(w, `"%s"."%s"`, c.sel.Table, cn) - colWithTable(c.w, sel.Table, cn) + colWithTable(c.w, ti.Name, cn) } if i < len(sel.Cols)-1 || len(childCols) != 0 { @@ -510,15 +513,10 @@ func (c *compilerContext) renderBaseSelect(sel *qcode.Select, ti *DBTableInfo, c.w.WriteString(` FROM `) - if c.schema.IsAlias(sel.Table) || ti.Singular { - //fmt.Fprintf(w, ` FROM "%s" AS "%s"`, tn, c.sel.Table) - tableWithAlias(c.w, ti.Name, sel.Table) - } else { - //fmt.Fprintf(w, ` FROM "%s"`, c.sel.Table) - c.w.WriteString(`"`) - c.w.WriteString(ti.Name) - c.w.WriteString(`"`) - } + //fmt.Fprintf(w, ` FROM "%s"`, c.sel.Table) + c.w.WriteString(`"`) + c.w.WriteString(ti.Name) + c.w.WriteString(`"`) // if tn, ok := c.tmap[sel.Table]; ok { // //fmt.Fprintf(w, ` FROM "%s" AS "%s"`, tn, c.sel.Table) @@ -545,7 +543,7 @@ func (c *compilerContext) renderBaseSelect(sel *qcode.Select, ti *DBTableInfo, c.w.WriteString(` WHERE (`) - if err := c.renderRelationship(sel); err != nil { + if err := c.renderRelationship(sel, ti); err != nil { return err } @@ -567,7 +565,7 @@ func (c *compilerContext) renderBaseSelect(sel *qcode.Select, ti *DBTableInfo, c.w.WriteString(`, `) } //fmt.Fprintf(w, `"%s"."%s"`, c.sel.Table, c.sel.Cols[id].Name) - colWithTable(c.w, sel.Table, sel.Cols[id].Name) + colWithTable(c.w, ti.Name, sel.Cols[id].Name) } } } @@ -594,11 +592,11 @@ func (c *compilerContext) renderBaseSelect(sel *qcode.Select, ti *DBTableInfo, //fmt.Fprintf(w, `) AS "%s_%d"`, c.sel.Table, c.sel.ID) c.w.WriteString(`)`) - aliasWithID(c.w, sel.Table, sel.ID) + aliasWithID(c.w, ti.Name, sel.ID) return nil } -func (c *compilerContext) renderOrderByColumns(sel *qcode.Select) { +func (c *compilerContext) renderOrderByColumns(sel *qcode.Select, ti *DBTableInfo) { colsRendered := len(sel.Cols) != 0 for i := range sel.OrderBy { @@ -611,13 +609,13 @@ func (c *compilerContext) renderOrderByColumns(sel *qcode.Select) { //fmt.Fprintf(w, `"%s_%d"."%s" AS "%s_%d_%s_ob"`, //c.sel.Table, c.sel.ID, c, //c.sel.Table, c.sel.ID, c) - colWithTableID(c.w, sel.Table, sel.ID, col) + colWithTableID(c.w, ti.Name, sel.ID, col) c.w.WriteString(` AS `) tableIDColSuffix(c.w, sel.Table, sel.ID, col, "_ob") } } -func (c *compilerContext) renderRelationship(sel *qcode.Select) error { +func (c *compilerContext) renderRelationship(sel *qcode.Select, ti *DBTableInfo) error { parent := c.s[sel.ParentID] rel, err := c.schema.GetRel(sel.Table, parent.Table) @@ -630,7 +628,7 @@ func (c *compilerContext) renderRelationship(sel *qcode.Select) error { //fmt.Fprintf(w, `(("%s"."%s") = ("%s_%d"."%s"))`, //c.sel.Table, rel.Col1, c.parent.Table, c.parent.ID, rel.Col2) c.w.WriteString(`((`) - colWithTable(c.w, sel.Table, rel.Col1) + colWithTable(c.w, ti.Name, rel.Col1) c.w.WriteString(`) = (`) colWithTableID(c.w, parent.Table, parent.ID, rel.Col2) c.w.WriteString(`))`) @@ -639,7 +637,7 @@ func (c *compilerContext) renderRelationship(sel *qcode.Select) error { //fmt.Fprintf(w, `(("%s"."%s") = ("%s_%d"."%s"))`, //c.sel.Table, rel.Col1, c.parent.Table, c.parent.ID, rel.Col2) c.w.WriteString(`((`) - colWithTable(c.w, sel.Table, rel.Col1) + colWithTable(c.w, ti.Name, rel.Col1) c.w.WriteString(`) = (`) colWithTableID(c.w, parent.Table, parent.ID, rel.Col2) c.w.WriteString(`))`) @@ -648,7 +646,7 @@ func (c *compilerContext) renderRelationship(sel *qcode.Select) error { //fmt.Fprintf(w, `(("%s"."%s") = ("%s"."%s"))`, //c.sel.Table, rel.Col1, rel.Through, rel.Col2) c.w.WriteString(`((`) - colWithTable(c.w, sel.Table, rel.Col1) + colWithTable(c.w, ti.Name, rel.Col1) c.w.WriteString(`) = (`) colWithTable(c.w, rel.Through, rel.Col2) c.w.WriteString(`))`) @@ -683,6 +681,7 @@ func (c *compilerContext) renderWhere(sel *qcode.Select, ti *DBTableInfo) error default: return fmt.Errorf("11: unexpected value %v (%t)", intf, intf) } + case *qcode.Exp: switch val.Op { case qcode.OpAnd, qcode.OpOr: @@ -693,109 +692,109 @@ func (c *compilerContext) renderWhere(sel *qcode.Select, ti *DBTableInfo) error } } qcode.FreeExp(val) - continue + case qcode.OpNot: st.Push(val.Children[0]) st.Push(qcode.OpNot) qcode.FreeExp(val) - continue - } - - if val.NestedCol { - //fmt.Fprintf(w, `(("%s") `, val.Col) - c.w.WriteString(`(("`) - c.w.WriteString(val.Col) - c.w.WriteString(`") `) - - } else if len(val.Col) != 0 { - //fmt.Fprintf(w, `(("%s"."%s") `, c.sel.Table, val.Col) - c.w.WriteString(`((`) - colWithTable(c.w, sel.Table, val.Col) - c.w.WriteString(`) `) - } - valExists := true - - switch val.Op { - case qcode.OpEquals: - c.w.WriteString(`=`) - case qcode.OpNotEquals: - c.w.WriteString(`!=`) - case qcode.OpGreaterOrEquals: - c.w.WriteString(`>=`) - case qcode.OpLesserOrEquals: - c.w.WriteString(`<=`) - case qcode.OpGreaterThan: - c.w.WriteString(`>`) - case qcode.OpLesserThan: - c.w.WriteString(`<`) - case qcode.OpIn: - c.w.WriteString(`IN`) - case qcode.OpNotIn: - c.w.WriteString(`NOT IN`) - case qcode.OpLike: - c.w.WriteString(`LIKE`) - case qcode.OpNotLike: - c.w.WriteString(`NOT LIKE`) - case qcode.OpILike: - c.w.WriteString(`ILIKE`) - case qcode.OpNotILike: - c.w.WriteString(`NOT ILIKE`) - case qcode.OpSimilar: - c.w.WriteString(`SIMILAR TO`) - case qcode.OpNotSimilar: - c.w.WriteString(`NOT SIMILAR TO`) - case qcode.OpContains: - c.w.WriteString(`@>`) - case qcode.OpContainedIn: - c.w.WriteString(`<@`) - case qcode.OpHasKey: - c.w.WriteString(`?`) - case qcode.OpHasKeyAny: - c.w.WriteString(`?|`) - case qcode.OpHasKeyAll: - c.w.WriteString(`?&`) - case qcode.OpIsNull: - if strings.EqualFold(val.Val, "true") { - c.w.WriteString(`IS NULL)`) - } else { - c.w.WriteString(`IS NOT NULL)`) - } - valExists = false - case qcode.OpEqID: - if len(ti.PrimaryCol) == 0 { - return fmt.Errorf("no primary key column defined for %s", sel.Table) - } - //fmt.Fprintf(w, `(("%s") =`, c.ti.PrimaryCol) - c.w.WriteString(`(("`) - c.w.WriteString(ti.PrimaryCol) - c.w.WriteString(`") =`) - - case qcode.OpTsQuery: - if len(ti.TSVCol) == 0 { - return fmt.Errorf("no tsv column defined for %s", sel.Table) - } - //fmt.Fprintf(w, `(("%s") @@ to_tsquery('%s'))`, c.ti.TSVCol, val.Val) - c.w.WriteString(`(("`) - c.w.WriteString(ti.TSVCol) - c.w.WriteString(`") @@ to_tsquery('`) - c.w.WriteString(val.Val) - c.w.WriteString(`'))`) - valExists = false default: - return fmt.Errorf("[Where] unexpected op code %d", val.Op) - } + if val.NestedCol { + //fmt.Fprintf(w, `(("%s") `, val.Col) + c.w.WriteString(`(("`) + c.w.WriteString(val.Col) + c.w.WriteString(`") `) - if valExists { - if val.Type == qcode.ValList { - c.renderList(val) - } else { - c.renderVal(val, c.vars) + } else if len(val.Col) != 0 { + //fmt.Fprintf(w, `(("%s"."%s") `, c.sel.Table, val.Col) + c.w.WriteString(`((`) + colWithTable(c.w, ti.Name, val.Col) + c.w.WriteString(`) `) } - c.w.WriteString(`)`) - } + valExists := true - qcode.FreeExp(val) + switch val.Op { + case qcode.OpEquals: + c.w.WriteString(`=`) + case qcode.OpNotEquals: + c.w.WriteString(`!=`) + case qcode.OpGreaterOrEquals: + c.w.WriteString(`>=`) + case qcode.OpLesserOrEquals: + c.w.WriteString(`<=`) + case qcode.OpGreaterThan: + c.w.WriteString(`>`) + case qcode.OpLesserThan: + c.w.WriteString(`<`) + case qcode.OpIn: + c.w.WriteString(`IN`) + case qcode.OpNotIn: + c.w.WriteString(`NOT IN`) + case qcode.OpLike: + c.w.WriteString(`LIKE`) + case qcode.OpNotLike: + c.w.WriteString(`NOT LIKE`) + case qcode.OpILike: + c.w.WriteString(`ILIKE`) + case qcode.OpNotILike: + c.w.WriteString(`NOT ILIKE`) + case qcode.OpSimilar: + c.w.WriteString(`SIMILAR TO`) + case qcode.OpNotSimilar: + c.w.WriteString(`NOT SIMILAR TO`) + case qcode.OpContains: + c.w.WriteString(`@>`) + case qcode.OpContainedIn: + c.w.WriteString(`<@`) + case qcode.OpHasKey: + c.w.WriteString(`?`) + case qcode.OpHasKeyAny: + c.w.WriteString(`?|`) + case qcode.OpHasKeyAll: + c.w.WriteString(`?&`) + case qcode.OpIsNull: + if strings.EqualFold(val.Val, "true") { + c.w.WriteString(`IS NULL)`) + } else { + c.w.WriteString(`IS NOT NULL)`) + } + valExists = false + case qcode.OpEqID: + if len(ti.PrimaryCol) == 0 { + return fmt.Errorf("no primary key column defined for %s", ti.Name) + } + //fmt.Fprintf(w, `(("%s") =`, c.ti.PrimaryCol) + c.w.WriteString(`((`) + colWithTable(c.w, ti.Name, ti.PrimaryCol) + //c.w.WriteString(ti.PrimaryCol) + c.w.WriteString(`) =`) + case qcode.OpTsQuery: + if len(ti.TSVCol) == 0 { + return fmt.Errorf("no tsv column defined for %s", ti.Name) + } + //fmt.Fprintf(w, `(("%s") @@ to_tsquery('%s'))`, c.ti.TSVCol, val.Val) + c.w.WriteString(`(("`) + c.w.WriteString(ti.TSVCol) + c.w.WriteString(`") @@ to_tsquery('`) + c.w.WriteString(val.Val) + c.w.WriteString(`'))`) + valExists = false + + default: + return fmt.Errorf("[Where] unexpected op code %d", val.Op) + } + + if valExists { + if val.Type == qcode.ValList { + c.renderList(val) + } else { + c.renderVal(val, c.vars) + } + c.w.WriteString(`)`) + } + + qcode.FreeExp(val) + } default: return fmt.Errorf("12: unexpected value %v (%t)", intf, intf) @@ -806,7 +805,7 @@ func (c *compilerContext) renderWhere(sel *qcode.Select, ti *DBTableInfo) error return nil } -func (c *compilerContext) renderOrderBy(sel *qcode.Select) error { +func (c *compilerContext) renderOrderBy(sel *qcode.Select, ti *DBTableInfo) error { c.w.WriteString(` ORDER BY `) for i := range sel.OrderBy { if i != 0 { @@ -817,27 +816,27 @@ func (c *compilerContext) renderOrderBy(sel *qcode.Select) error { switch ob.Order { case qcode.OrderAsc: //fmt.Fprintf(w, `"%s_%d.ob.%s" ASC`, sel.Table, sel.ID, ob.Col) - tableIDColSuffix(c.w, sel.Table, sel.ID, ob.Col, "_ob") + tableIDColSuffix(c.w, ti.Name, sel.ID, ob.Col, "_ob") c.w.WriteString(` ASC`) case qcode.OrderDesc: //fmt.Fprintf(w, `"%s_%d.ob.%s" DESC`, sel.Table, sel.ID, ob.Col) - tableIDColSuffix(c.w, sel.Table, sel.ID, ob.Col, "_ob") + tableIDColSuffix(c.w, ti.Name, sel.ID, ob.Col, "_ob") c.w.WriteString(` DESC`) case qcode.OrderAscNullsFirst: //fmt.Fprintf(w, `"%s_%d.ob.%s" ASC NULLS FIRST`, sel.Table, sel.ID, ob.Col) - tableIDColSuffix(c.w, sel.Table, sel.ID, ob.Col, "_ob") + tableIDColSuffix(c.w, ti.Name, sel.ID, ob.Col, "_ob") c.w.WriteString(` ASC NULLS FIRST`) case qcode.OrderDescNullsFirst: //fmt.Fprintf(w, `%s_%d.ob.%s DESC NULLS FIRST`, sel.Table, sel.ID, ob.Col) - tableIDColSuffix(c.w, sel.Table, sel.ID, ob.Col, "_ob") + tableIDColSuffix(c.w, ti.Name, sel.ID, ob.Col, "_ob") c.w.WriteString(` DESC NULLLS FIRST`) case qcode.OrderAscNullsLast: //fmt.Fprintf(w, `"%s_%d.ob.%s ASC NULLS LAST`, sel.Table, sel.ID, ob.Col) - tableIDColSuffix(c.w, sel.Table, sel.ID, ob.Col, "_ob") + tableIDColSuffix(c.w, ti.Name, sel.ID, ob.Col, "_ob") c.w.WriteString(` ASC NULLS LAST`) case qcode.OrderDescNullsLast: //fmt.Fprintf(w, `%s_%d.ob.%s DESC NULLS LAST`, sel.Table, sel.ID, ob.Col) - tableIDColSuffix(c.w, sel.Table, sel.ID, ob.Col, "_ob") + tableIDColSuffix(c.w, ti.Name, sel.ID, ob.Col, "_ob") c.w.WriteString(` DESC NULLS LAST`) default: return fmt.Errorf("13: unexpected value %v", ob.Order) @@ -846,14 +845,14 @@ func (c *compilerContext) renderOrderBy(sel *qcode.Select) error { return nil } -func (c *compilerContext) renderDistinctOn(sel *qcode.Select) { +func (c *compilerContext) renderDistinctOn(sel *qcode.Select, ti *DBTableInfo) { io.WriteString(c.w, `DISTINCT ON (`) for i := range sel.DistinctOn { if i != 0 { c.w.WriteString(`, `) } //fmt.Fprintf(w, `"%s_%d.ob.%s"`, c.sel.Table, c.sel.ID, c.sel.DistinctOn[i]) - tableIDColSuffix(c.w, sel.Table, sel.ID, sel.DistinctOn[i], "_ob") + tableIDColSuffix(c.w, ti.Name, sel.ID, sel.DistinctOn[i], "_ob") } c.w.WriteString(`) `) } @@ -876,10 +875,9 @@ func (c *compilerContext) renderList(ex *qcode.Exp) { c.w.WriteString(`)`) } -func (c *compilerContext) renderVal(ex *qcode.Exp, - vars map[string]string) { - +func (c *compilerContext) renderVal(ex *qcode.Exp, vars map[string]string) { io.WriteString(c.w, ` `) + switch ex.Type { case qcode.ValBool, qcode.ValInt, qcode.ValFloat: if len(ex.Val) != 0 { diff --git a/psql/select_test.go b/psql/select_test.go index 80e373c..1bdaa48 100644 --- a/psql/select_test.go +++ b/psql/select_test.go @@ -58,45 +58,45 @@ func TestMain(m *testing.M) { columns := [][]*DBColumn{ []*DBColumn{ - &DBColumn{ID: 1, Name: "id", Type: "bigint", NotNull: true, PrimaryKey: true, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 2, Name: "full_name", Type: "character varying", NotNull: true, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 3, Name: "phone", Type: "character varying", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 4, Name: "email", Type: "character varying", NotNull: true, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 5, Name: "encrypted_password", Type: "character varying", NotNull: true, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 6, Name: "reset_password_token", Type: "character varying", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 7, Name: "reset_password_sent_at", Type: "timestamp without time zone", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 8, Name: "remember_created_at", Type: "timestamp without time zone", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 9, Name: "created_at", Type: "timestamp without time zone", NotNull: true, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 10, Name: "updated_at", Type: "timestamp without time zone", NotNull: true, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}}, + &DBColumn{ID: 1, Name: "id", Type: "bigint", NotNull: true, PrimaryKey: true, UniqueKey: false, FKeyTable: "", FKeyColID: []int16(nil)}, + &DBColumn{ID: 2, Name: "full_name", Type: "character varying", NotNull: true, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int16(nil)}, + &DBColumn{ID: 3, Name: "phone", Type: "character varying", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int16(nil)}, + &DBColumn{ID: 4, Name: "email", Type: "character varying", NotNull: true, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int16(nil)}, + &DBColumn{ID: 5, Name: "encrypted_password", Type: "character varying", NotNull: true, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int16(nil)}, + &DBColumn{ID: 6, Name: "reset_password_token", Type: "character varying", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int16(nil)}, + &DBColumn{ID: 7, Name: "reset_password_sent_at", Type: "timestamp without time zone", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int16(nil)}, + &DBColumn{ID: 8, Name: "remember_created_at", Type: "timestamp without time zone", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int16(nil)}, + &DBColumn{ID: 9, Name: "created_at", Type: "timestamp without time zone", NotNull: true, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int16(nil)}, + &DBColumn{ID: 10, Name: "updated_at", Type: "timestamp without time zone", NotNull: true, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int16(nil)}}, []*DBColumn{ - &DBColumn{ID: 1, Name: "id", Type: "bigint", NotNull: true, PrimaryKey: true, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 2, Name: "full_name", Type: "character varying", NotNull: true, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 3, Name: "phone", Type: "character varying", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 4, Name: "avatar", Type: "character varying", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 5, Name: "email", Type: "character varying", NotNull: true, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 6, Name: "encrypted_password", Type: "character varying", NotNull: true, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 7, Name: "reset_password_token", Type: "character varying", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 8, Name: "reset_password_sent_at", Type: "timestamp without time zone", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 9, Name: "remember_created_at", Type: "timestamp without time zone", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 10, Name: "created_at", Type: "timestamp without time zone", NotNull: true, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 11, Name: "updated_at", Type: "timestamp without time zone", NotNull: true, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}}, + &DBColumn{ID: 1, Name: "id", Type: "bigint", NotNull: true, PrimaryKey: true, UniqueKey: false, FKeyTable: "", FKeyColID: []int16(nil)}, + &DBColumn{ID: 2, Name: "full_name", Type: "character varying", NotNull: true, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int16(nil)}, + &DBColumn{ID: 3, Name: "phone", Type: "character varying", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int16(nil)}, + &DBColumn{ID: 4, Name: "avatar", Type: "character varying", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int16(nil)}, + &DBColumn{ID: 5, Name: "email", Type: "character varying", NotNull: true, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int16(nil)}, + &DBColumn{ID: 6, Name: "encrypted_password", Type: "character varying", NotNull: true, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int16(nil)}, + &DBColumn{ID: 7, Name: "reset_password_token", Type: "character varying", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int16(nil)}, + &DBColumn{ID: 8, Name: "reset_password_sent_at", Type: "timestamp without time zone", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int16(nil)}, + &DBColumn{ID: 9, Name: "remember_created_at", Type: "timestamp without time zone", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int16(nil)}, + &DBColumn{ID: 10, Name: "created_at", Type: "timestamp without time zone", NotNull: true, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int16(nil)}, + &DBColumn{ID: 11, Name: "updated_at", Type: "timestamp without time zone", NotNull: true, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int16(nil)}}, []*DBColumn{ - &DBColumn{ID: 1, Name: "id", Type: "bigint", NotNull: true, PrimaryKey: true, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 2, Name: "name", Type: "character varying", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 3, Name: "description", Type: "text", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 4, Name: "price", Type: "numeric(7,2)", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 5, Name: "user_id", Type: "bigint", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "users", FKeyColID: []int{1}}, - &DBColumn{ID: 6, Name: "created_at", Type: "timestamp without time zone", NotNull: true, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 7, Name: "updated_at", Type: "timestamp without time zone", NotNull: true, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 8, Name: "tsv", Type: "tsvector", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}}, + &DBColumn{ID: 1, Name: "id", Type: "bigint", NotNull: true, PrimaryKey: true, UniqueKey: false, FKeyTable: "", FKeyColID: []int16(nil)}, + &DBColumn{ID: 2, Name: "name", Type: "character varying", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int16(nil)}, + &DBColumn{ID: 3, Name: "description", Type: "text", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int16(nil)}, + &DBColumn{ID: 4, Name: "price", Type: "numeric(7,2)", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int16(nil)}, + &DBColumn{ID: 5, Name: "user_id", Type: "bigint", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "users", FKeyColID: []int16{1}}, + &DBColumn{ID: 6, Name: "created_at", Type: "timestamp without time zone", NotNull: true, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int16(nil)}, + &DBColumn{ID: 7, Name: "updated_at", Type: "timestamp without time zone", NotNull: true, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int16(nil)}, + &DBColumn{ID: 8, Name: "tsv", Type: "tsvector", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int16(nil)}}, []*DBColumn{ - &DBColumn{ID: 1, Name: "id", Type: "bigint", NotNull: true, PrimaryKey: true, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 2, Name: "customer_id", Type: "bigint", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "customers", FKeyColID: []int{1}}, - &DBColumn{ID: 3, Name: "product_id", Type: "bigint", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "products", FKeyColID: []int{1}}, - &DBColumn{ID: 4, Name: "sale_type", Type: "character varying", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 5, Name: "quantity", Type: "integer", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 6, Name: "due_date", Type: "timestamp without time zone", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 7, Name: "returned", Type: "timestamp without time zone", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}}, + &DBColumn{ID: 1, Name: "id", Type: "bigint", NotNull: true, PrimaryKey: true, UniqueKey: false, FKeyTable: "", FKeyColID: []int16(nil)}, + &DBColumn{ID: 2, Name: "customer_id", Type: "bigint", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "customers", FKeyColID: []int16{1}}, + &DBColumn{ID: 3, Name: "product_id", Type: "bigint", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "products", FKeyColID: []int16{1}}, + &DBColumn{ID: 4, Name: "sale_type", Type: "character varying", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int16(nil)}, + &DBColumn{ID: 5, Name: "quantity", Type: "integer", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int16(nil)}, + &DBColumn{ID: 6, Name: "due_date", Type: "timestamp without time zone", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int16(nil)}, + &DBColumn{ID: 7, Name: "returned", Type: "timestamp without time zone", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int16(nil)}}, } schema := &DBSchema{ @@ -126,6 +126,7 @@ func TestMain(m *testing.M) { } func compileGQLToPSQL(gql string, vars Variables) ([]byte, error) { + qc, err := qcompile.Compile([]byte(gql)) if err != nil { return nil, err @@ -154,7 +155,7 @@ func withComplexArgs(t *testing.T) { # no duplicate prices returned distinct: [ price ] - # only items with an id >= 30 and < 30 are returned + # only items with an id >= 20 and < 28 are returned where: { id: { and: { greater_or_equals: 20, lt: 28 } } }) { id NAME @@ -162,7 +163,7 @@ func withComplexArgs(t *testing.T) { } }` - sql := `SELECT json_object_agg('products', products) FROM (SELECT coalesce(json_agg("products" ORDER BY "products_0_price_ob" DESC), '[]') AS "products" FROM (SELECT DISTINCT ON ("products_0_price_ob") row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name", "products_0"."price" AS "price") AS "sel_0")) AS "products", "products_0"."price" AS "products_0_price_ob" FROM (SELECT "products"."id", "products"."name", "products"."price" FROM "products" WHERE ((("products"."price") > 0) AND (("products"."price") < 8) AND (("products"."id") < 28) AND (("products"."id") >= 20)) LIMIT ('30') :: integer) AS "products_0" ORDER BY "products_0_price_ob" DESC LIMIT ('30') :: integer) AS "products_0") AS "done_1337";` + sql := `SELECT json_object_agg('products', products) FROM (SELECT coalesce(json_agg("sel_json_0" ORDER BY "products_0_price_ob" DESC), '[]') AS "products" FROM (SELECT DISTINCT ON ("products_0_price_ob") row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name", "products_0"."price" AS "price") AS "sel_0")) AS "sel_json_0", "products_0"."price" AS "products_0_price_ob" FROM (SELECT "products"."id", "products"."name", "products"."price" FROM "products" WHERE ((("products"."price") > 0) AND (("products"."price") < 8) AND (("products"."id") < 28) AND (("products"."id") >= 20)) LIMIT ('30') :: integer) AS "products_0" ORDER BY "products_0_price_ob" DESC LIMIT ('30') :: integer) AS "sel_json_agg_0") AS "done_1337";` resSQL, err := compileGQLToPSQL(gql, nil) if err != nil { @@ -190,7 +191,7 @@ func withWhereMultiOr(t *testing.T) { } }` - 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"."name" AS "name", "products_0"."price" AS "price") AS "sel_0")) AS "products" FROM (SELECT "products"."id", "products"."name", "products"."price" FROM "products" WHERE ((("products"."price") > 0) AND (("products"."price") < 8) AND (("products"."price") < 20) OR (("products"."price") > 10) OR NOT (("products"."id") IS NULL)) LIMIT ('20') :: integer) AS "products_0" LIMIT ('20') :: integer) AS "products_0") AS "done_1337";` + sql := `SELECT json_object_agg('products', products) FROM (SELECT coalesce(json_agg("sel_json_0"), '[]') AS "products" FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name", "products_0"."price" AS "price") AS "sel_0")) AS "sel_json_0" FROM (SELECT "products"."id", "products"."name", "products"."price" FROM "products" WHERE ((("products"."price") > 0) AND (("products"."price") < 8) AND (("products"."price") < 20) OR (("products"."price") > 10) OR NOT (("products"."id") IS NULL)) LIMIT ('20') :: integer) AS "products_0" LIMIT ('20') :: integer) AS "sel_json_agg_0") AS "done_1337";` resSQL, err := compileGQLToPSQL(gql, nil) if err != nil { @@ -216,7 +217,7 @@ func withWhereIsNull(t *testing.T) { } }` - 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"."name" AS "name", "products_0"."price" AS "price") AS "sel_0")) AS "products" FROM (SELECT "products"."id", "products"."name", "products"."price" FROM "products" WHERE ((("products"."price") > 0) AND (("products"."price") < 8) AND (("products"."price") > 10) AND NOT (("products"."id") IS NULL)) LIMIT ('20') :: integer) AS "products_0" LIMIT ('20') :: integer) AS "products_0") AS "done_1337";` + sql := `SELECT json_object_agg('products', products) FROM (SELECT coalesce(json_agg("sel_json_0"), '[]') AS "products" FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name", "products_0"."price" AS "price") AS "sel_0")) AS "sel_json_0" FROM (SELECT "products"."id", "products"."name", "products"."price" FROM "products" WHERE ((("products"."price") > 0) AND (("products"."price") < 8) AND (("products"."price") > 10) AND NOT (("products"."id") IS NULL)) LIMIT ('20') :: integer) AS "products_0" LIMIT ('20') :: integer) AS "sel_json_agg_0") AS "done_1337";` resSQL, err := compileGQLToPSQL(gql, nil) if err != nil { @@ -242,7 +243,7 @@ func withWhereAndList(t *testing.T) { } }` - 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"."name" AS "name", "products_0"."price" AS "price") AS "sel_0")) AS "products" FROM (SELECT "products"."id", "products"."name", "products"."price" FROM "products" WHERE ((("products"."price") > 0) AND (("products"."price") < 8) AND (("products"."price") > 10) AND NOT (("products"."id") IS NULL)) LIMIT ('20') :: integer) AS "products_0" LIMIT ('20') :: integer) AS "products_0") AS "done_1337";` + sql := `SELECT json_object_agg('products', products) FROM (SELECT coalesce(json_agg("sel_json_0"), '[]') AS "products" FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name", "products_0"."price" AS "price") AS "sel_0")) AS "sel_json_0" FROM (SELECT "products"."id", "products"."name", "products"."price" FROM "products" WHERE ((("products"."price") > 0) AND (("products"."price") < 8) AND (("products"."price") > 10) AND NOT (("products"."id") IS NULL)) LIMIT ('20') :: integer) AS "products_0" LIMIT ('20') :: integer) AS "sel_json_agg_0") AS "done_1337";` resSQL, err := compileGQLToPSQL(gql, nil) if err != nil { @@ -262,7 +263,7 @@ func fetchByID(t *testing.T) { } }` - sql := `SELECT json_object_agg('product', product) FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "product_0"."id" AS "id", "product_0"."name" AS "name") AS "sel_0")) AS "product" FROM (SELECT "product"."id", "product"."name" FROM "products" AS "product" WHERE ((("product"."price") > 0) AND (("product"."price") < 8) AND (("id") = 15)) LIMIT ('1') :: integer) AS "product_0" LIMIT ('1') :: integer) AS "done_1337";` + sql := `SELECT json_object_agg('product', sel_json_0) FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name") AS "sel_0")) AS "sel_json_0" FROM (SELECT "products"."id", "products"."name" FROM "products" WHERE ((("products"."price") > 0) AND (("products"."price") < 8) AND (("products"."id") = 15)) LIMIT ('1') :: integer) AS "products_0" LIMIT ('1') :: integer) AS "done_1337";` resSQL, err := compileGQLToPSQL(gql, nil) if err != nil { @@ -282,7 +283,7 @@ func searchQuery(t *testing.T) { } }` - 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"."name" AS "name") AS "sel_0")) AS "products" FROM (SELECT "products"."id", "products"."name" FROM "products" WHERE ((("products"."price") > 0) AND (("products"."price") < 8) AND (("tsv") @@ to_tsquery('Imperial'))) LIMIT ('20') :: integer) AS "products_0" LIMIT ('20') :: integer) AS "products_0") AS "done_1337";` + sql := `SELECT json_object_agg('products', products) FROM (SELECT coalesce(json_agg("sel_json_0"), '[]') AS "products" FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name") AS "sel_0")) AS "sel_json_0" FROM (SELECT "products"."id", "products"."name" FROM "products" WHERE ((("products"."price") > 0) AND (("products"."price") < 8) AND (("tsv") @@ to_tsquery('Imperial'))) LIMIT ('20') :: integer) AS "products_0" LIMIT ('20') :: integer) AS "sel_json_agg_0") AS "done_1337";` resSQL, err := compileGQLToPSQL(gql, nil) if err != nil { @@ -305,7 +306,7 @@ func oneToMany(t *testing.T) { } }` - sql := `SELECT json_object_agg('users', users) FROM (SELECT coalesce(json_agg("users"), '[]') AS "users" FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "users_0"."email" AS "email", "products_1_join"."products" AS "products") AS "sel_0")) AS "users" FROM (SELECT "users"."email", "users"."id" FROM "users" WHERE ((("users"."id") = {{user_id}})) LIMIT ('20') :: integer) AS "users_0" LEFT OUTER JOIN LATERAL (SELECT coalesce(json_agg("products"), '[]') AS "products" FROM (SELECT row_to_json((SELECT "sel_1" FROM (SELECT "products_1"."name" AS "name", "products_1"."price" AS "price") AS "sel_1")) AS "products" FROM (SELECT "products"."name", "products"."price" FROM "products" WHERE ((("products"."user_id") = ("users_0"."id"))) LIMIT ('20') :: integer) AS "products_1" LIMIT ('20') :: integer) AS "products_1") AS "products_1_join" ON ('true') LIMIT ('20') :: integer) AS "users_0") AS "done_1337";` + sql := `SELECT json_object_agg('users', users) FROM (SELECT coalesce(json_agg("sel_json_0"), '[]') AS "users" FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "users_0"."email" AS "email", "products_1_join"."products" AS "products") AS "sel_0")) AS "sel_json_0" FROM (SELECT "users"."email", "users"."id" FROM "users" WHERE ((("users"."id") = {{user_id}})) LIMIT ('20') :: integer) AS "users_0" LEFT OUTER JOIN LATERAL (SELECT coalesce(json_agg("sel_json_1"), '[]') AS "products" FROM (SELECT row_to_json((SELECT "sel_1" FROM (SELECT "products_1"."name" AS "name", "products_1"."price" AS "price") AS "sel_1")) AS "sel_json_1" FROM (SELECT "products"."name", "products"."price" FROM "products" WHERE ((("products"."user_id") = ("users_0"."id"))) LIMIT ('20') :: integer) AS "products_1" LIMIT ('20') :: integer) AS "sel_json_agg_1") AS "products_1_join" ON ('true') LIMIT ('20') :: integer) AS "sel_json_agg_0") AS "done_1337";` resSQL, err := compileGQLToPSQL(gql, nil) if err != nil { @@ -328,7 +329,7 @@ func belongsTo(t *testing.T) { } }` - 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"."name" AS "name", "products_0"."price" AS "price", "users_1_join"."users" AS "users") AS "sel_0")) AS "products" FROM (SELECT "products"."name", "products"."price", "products"."user_id" FROM "products" WHERE ((("products"."price") > 0) AND (("products"."price") < 8)) LIMIT ('20') :: integer) AS "products_0" LEFT OUTER JOIN LATERAL (SELECT coalesce(json_agg("users"), '[]') AS "users" FROM (SELECT row_to_json((SELECT "sel_1" FROM (SELECT "users_1"."email" AS "email") AS "sel_1")) AS "users" FROM (SELECT "users"."email" FROM "users" WHERE ((("users"."id") = ("products_0"."user_id"))) LIMIT ('20') :: integer) AS "users_1" LIMIT ('20') :: integer) AS "users_1") AS "users_1_join" ON ('true') LIMIT ('20') :: integer) AS "products_0") AS "done_1337";` + sql := `SELECT json_object_agg('products', products) FROM (SELECT coalesce(json_agg("sel_json_0"), '[]') AS "products" FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."name" AS "name", "products_0"."price" AS "price", "users_1_join"."users" AS "users") AS "sel_0")) AS "sel_json_0" FROM (SELECT "products"."name", "products"."price", "products"."user_id" FROM "products" WHERE ((("products"."price") > 0) AND (("products"."price") < 8)) LIMIT ('20') :: integer) AS "products_0" LEFT OUTER JOIN LATERAL (SELECT coalesce(json_agg("sel_json_1"), '[]') AS "users" FROM (SELECT row_to_json((SELECT "sel_1" FROM (SELECT "users_1"."email" AS "email") AS "sel_1")) AS "sel_json_1" FROM (SELECT "users"."email" FROM "users" WHERE ((("users"."id") = ("products_0"."user_id"))) LIMIT ('20') :: integer) AS "users_1" LIMIT ('20') :: integer) AS "sel_json_agg_1") AS "users_1_join" ON ('true') LIMIT ('20') :: integer) AS "sel_json_agg_0") AS "done_1337";` resSQL, err := compileGQLToPSQL(gql, nil) if err != nil { @@ -351,7 +352,7 @@ func manyToMany(t *testing.T) { } }` - 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"."name" AS "name", "customers_1_join"."customers" AS "customers") AS "sel_0")) AS "products" FROM (SELECT "products"."name", "products"."id" FROM "products" WHERE ((("products"."price") > 0) AND (("products"."price") < 8)) LIMIT ('20') :: integer) AS "products_0" LEFT OUTER JOIN LATERAL (SELECT coalesce(json_agg("customers"), '[]') AS "customers" FROM (SELECT row_to_json((SELECT "sel_1" FROM (SELECT "customers_1"."email" AS "email", "customers_1"."full_name" AS "full_name") AS "sel_1")) AS "customers" FROM (SELECT "customers"."email", "customers"."full_name" FROM "customers" LEFT OUTER JOIN "purchases" ON (("purchases"."product_id") = ("products_0"."id")) WHERE ((("customers"."id") = ("purchases"."customer_id"))) LIMIT ('20') :: integer) AS "customers_1" LIMIT ('20') :: integer) AS "customers_1") AS "customers_1_join" ON ('true') LIMIT ('20') :: integer) AS "products_0") AS "done_1337";` + sql := `SELECT json_object_agg('products', products) FROM (SELECT coalesce(json_agg("sel_json_0"), '[]') AS "products" FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."name" AS "name", "customers_1_join"."customers" AS "customers") AS "sel_0")) AS "sel_json_0" FROM (SELECT "products"."name", "products"."id" FROM "products" WHERE ((("products"."price") > 0) AND (("products"."price") < 8)) LIMIT ('20') :: integer) AS "products_0" LEFT OUTER JOIN LATERAL (SELECT coalesce(json_agg("sel_json_1"), '[]') AS "customers" FROM (SELECT row_to_json((SELECT "sel_1" FROM (SELECT "customers_1"."email" AS "email", "customers_1"."full_name" AS "full_name") AS "sel_1")) AS "sel_json_1" FROM (SELECT "customers"."email", "customers"."full_name" FROM "customers" LEFT OUTER JOIN "purchases" ON (("purchases"."product_id") = ("products_0"."id")) WHERE ((("customers"."id") = ("purchases"."customer_id"))) LIMIT ('20') :: integer) AS "customers_1" LIMIT ('20') :: integer) AS "sel_json_agg_1") AS "customers_1_join" ON ('true') LIMIT ('20') :: integer) AS "sel_json_agg_0") AS "done_1337";` resSQL, err := compileGQLToPSQL(gql, nil) if err != nil { @@ -374,7 +375,7 @@ func manyToManyReverse(t *testing.T) { } }` - sql := `SELECT json_object_agg('customers', customers) FROM (SELECT coalesce(json_agg("customers"), '[]') AS "customers" FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "customers_0"."email" AS "email", "customers_0"."full_name" AS "full_name", "products_1_join"."products" AS "products") AS "sel_0")) AS "customers" FROM (SELECT "customers"."email", "customers"."full_name", "customers"."id" FROM "customers" LIMIT ('20') :: integer) AS "customers_0" LEFT OUTER JOIN LATERAL (SELECT coalesce(json_agg("products"), '[]') AS "products" FROM (SELECT row_to_json((SELECT "sel_1" FROM (SELECT "products_1"."name" AS "name") AS "sel_1")) AS "products" FROM (SELECT "products"."name" FROM "products" LEFT OUTER JOIN "purchases" ON (("purchases"."customer_id") = ("customers_0"."id")) WHERE ((("products"."id") = ("purchases"."product_id"))) LIMIT ('20') :: integer) AS "products_1" LIMIT ('20') :: integer) AS "products_1") AS "products_1_join" ON ('true') LIMIT ('20') :: integer) AS "customers_0") AS "done_1337";` + sql := `SELECT json_object_agg('customers', customers) FROM (SELECT coalesce(json_agg("sel_json_0"), '[]') AS "customers" FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "customers_0"."email" AS "email", "customers_0"."full_name" AS "full_name", "products_1_join"."products" AS "products") AS "sel_0")) AS "sel_json_0" FROM (SELECT "customers"."email", "customers"."full_name", "customers"."id" FROM "customers" LIMIT ('20') :: integer) AS "customers_0" LEFT OUTER JOIN LATERAL (SELECT coalesce(json_agg("sel_json_1"), '[]') AS "products" FROM (SELECT row_to_json((SELECT "sel_1" FROM (SELECT "products_1"."name" AS "name") AS "sel_1")) AS "sel_json_1" FROM (SELECT "products"."name" FROM "products" LEFT OUTER JOIN "purchases" ON (("purchases"."customer_id") = ("customers_0"."id")) WHERE ((("products"."id") = ("purchases"."product_id"))) LIMIT ('20') :: integer) AS "products_1" LIMIT ('20') :: integer) AS "sel_json_agg_1") AS "products_1_join" ON ('true') LIMIT ('20') :: integer) AS "sel_json_agg_0") AS "done_1337";` resSQL, err := compileGQLToPSQL(gql, nil) if err != nil { @@ -394,7 +395,7 @@ func aggFunction(t *testing.T) { } }` - 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"."name" AS "name", "products_0"."count_price" AS "count_price") AS "sel_0")) AS "products" FROM (SELECT "products"."name", count("products"."price") AS "count_price" FROM "products" WHERE ((("products"."price") > 0) AND (("products"."price") < 8)) GROUP BY "products"."name" LIMIT ('20') :: integer) AS "products_0" LIMIT ('20') :: integer) AS "products_0") AS "done_1337";` + sql := `SELECT json_object_agg('products', products) FROM (SELECT coalesce(json_agg("sel_json_0"), '[]') AS "products" FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."name" AS "name", "products_0"."count_price" AS "count_price") AS "sel_0")) AS "sel_json_0" FROM (SELECT "products"."name", count("products"."price") AS "count_price" FROM "products" WHERE ((("products"."price") > 0) AND (("products"."price") < 8)) GROUP BY "products"."name" LIMIT ('20') :: integer) AS "products_0" LIMIT ('20') :: integer) AS "sel_json_agg_0") AS "done_1337";` resSQL, err := compileGQLToPSQL(gql, nil) if err != nil { @@ -414,7 +415,7 @@ func aggFunctionWithFilter(t *testing.T) { } }` - 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" WHERE ((("products"."price") > 0) AND (("products"."price") < 8) AND (("products"."id") > 10)) GROUP BY "products"."id" LIMIT ('20') :: integer) AS "products_0" LIMIT ('20') :: integer) AS "products_0") AS "done_1337";` + sql := `SELECT json_object_agg('products', products) FROM (SELECT coalesce(json_agg("sel_json_0"), '[]') 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 "sel_json_0" FROM (SELECT "products"."id", max("products"."price") AS "max_price" FROM "products" WHERE ((("products"."price") > 0) AND (("products"."price") < 8) AND (("products"."id") > 10)) GROUP BY "products"."id" LIMIT ('20') :: integer) AS "products_0" LIMIT ('20') :: integer) AS "sel_json_agg_0") AS "done_1337";` resSQL, err := compileGQLToPSQL(gql, nil) if err != nil { @@ -434,7 +435,7 @@ func queryWithVariables(t *testing.T) { } }` - sql := `SELECT json_object_agg('product', product) FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "product_0"."id" AS "id", "product_0"."name" AS "name") AS "sel_0")) AS "product" FROM (SELECT "product"."id", "product"."name" FROM "products" AS "product" WHERE ((("product"."price") > 0) AND (("product"."price") < 8) AND (("product"."price") = {{product_price}}) AND (("id") = {{product_id}})) LIMIT ('1') :: integer) AS "product_0" LIMIT ('1') :: integer) AS "done_1337";` + sql := `SELECT json_object_agg('product', sel_json_0) FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name") AS "sel_0")) AS "sel_json_0" FROM (SELECT "products"."id", "products"."name" FROM "products" WHERE ((("products"."price") > 0) AND (("products"."price") < 8) AND (("products"."price") = {{product_price}}) AND (("products"."id") = {{product_id}})) LIMIT ('1') :: integer) AS "products_0" LIMIT ('1') :: integer) AS "done_1337";` resSQL, err := compileGQLToPSQL(gql, nil) if err != nil { @@ -453,7 +454,7 @@ func syntheticTables(t *testing.T) { } }` - sql := `SELECT json_object_agg('me', me) FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "me_0"."email" AS "email") AS "sel_0")) AS "me" FROM (SELECT "me"."email" FROM "users" AS "me" WHERE ((("me"."id") = {{user_id}})) LIMIT ('1') :: integer) AS "me_0" LIMIT ('1') :: integer) AS "done_1337";` + sql := `SELECT json_object_agg('me', sel_json_0) FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "users_0"."email" AS "email") AS "sel_0")) AS "sel_json_0" FROM (SELECT "users"."email" FROM "users" WHERE ((("users"."id") = {{user_id}})) LIMIT ('1') :: integer) AS "users_0" LIMIT ('1') :: integer) AS "done_1337";` resSQL, err := compileGQLToPSQL(gql, nil) if err != nil { @@ -478,8 +479,9 @@ func TestCompileSelect(t *testing.T) { t.Run("manyToManyReverse", manyToManyReverse) t.Run("aggFunction", aggFunction) t.Run("aggFunctionWithFilter", aggFunctionWithFilter) - t.Run("queryWithVariables", queryWithVariables) t.Run("syntheticTables", syntheticTables) + t.Run("queryWithVariables", queryWithVariables) + } var benchGQL = []byte(`query { diff --git a/qcode/qcode.go b/qcode/qcode.go index b5b9d89..b1171bc 100644 --- a/qcode/qcode.go +++ b/qcode/qcode.go @@ -164,7 +164,7 @@ var opMap = map[parserType]QType{ } var expPool = sync.Pool{ - New: func() interface{} { return new(Exp) }, + New: func() interface{} { return &Exp{doFree: true} }, } func NewCompiler(c Config) (*Compiler, error) { @@ -195,6 +195,7 @@ func NewCompiler(c Config) (*Compiler, error) { seedExp := [100]Exp{} for i := range seedExp { + seedExp[i].doFree = true expPool.Put(&seedExp[i]) } @@ -318,7 +319,6 @@ func (com *Compiler) compileQuery(op *Operation) ([]Select, error) { } if fil != nil && fil.Op != OpNop { - if root.Where != nil { ow := root.Where @@ -695,7 +695,7 @@ func newExp(st *util.Stack, node *Node, usePool bool) (*Exp, error) { ex = expPool.Get().(*Exp) ex.Reset() } else { - ex = &Exp{} + ex = &Exp{doFree: false} } ex.Children = ex.childrenA[:0] @@ -881,7 +881,7 @@ func compileFilter(filter []string) (*Exp, error) { st := util.NewStack() if len(filter) == 0 { - return &Exp{Op: OpNop}, nil + return &Exp{Op: OpNop, doFree: false}, nil } for i := range filter { @@ -893,10 +893,11 @@ func compileFilter(filter []string) (*Exp, error) { if err != nil { return nil, err } + if fl == nil { fl = f } else { - fl = &Exp{Op: OpAnd, Children: []*Exp{fl, f}} + fl = &Exp{Op: OpAnd, Children: []*Exp{fl, f}, doFree: false} } } return fl, nil @@ -986,6 +987,7 @@ func (t ExpOp) String() string { } func FreeExp(ex *Exp) { + // fmt.Println(">", ex.doFree) if ex.doFree { expPool.Put(ex) } diff --git a/rails-app/Dockerfile b/rails-app/Dockerfile index e17ae14..96955f3 100644 --- a/rails-app/Dockerfile +++ b/rails-app/Dockerfile @@ -1,4 +1,4 @@ -FROM ruby:2.5 +FROM ruby:2.5.7 RUN apt-get update -qq && apt-get install -y nodejs postgresql-client RUN mkdir /app WORKDIR /app diff --git a/serv/allow.go b/serv/allow.go index b209d79..729ef61 100644 --- a/serv/allow.go +++ b/serv/allow.go @@ -97,9 +97,23 @@ func (al *allowList) add(req *gqlReq) { return } + var query string + + for i := 0; i < len(req.Query); i++ { + c := req.Query[i] + if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' { + query = req.Query + break + + } else if c == '{' { + query = "query " + req.Query + break + } + } + al.saveChan <- &allowItem{ uri: req.ref, - gql: req.Query, + gql: query, vars: req.Vars, } } diff --git a/serv/cmd.go b/serv/cmd.go index 35ca215..0b894eb 100644 --- a/serv/cmd.go +++ b/serv/cmd.go @@ -133,7 +133,8 @@ e.g. db:migrate -+1 } func initLog() *zerolog.Logger { - logger := zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr}). + out := zerolog.ConsoleWriter{Out: os.Stderr} + logger := zerolog.New(out). With(). Timestamp(). Caller(). diff --git a/serv/cmd_migrate.go b/serv/cmd_migrate.go index 01ca4b3..ada93dd 100644 --- a/serv/cmd_migrate.go +++ b/serv/cmd_migrate.go @@ -292,7 +292,7 @@ func cmdDBStatus(cmd *cobra.Command, args []string) { } fmt.Println("status: ", status) - fmt.Println("version: %d of %d\n", mver, len(m.Migrations)) + fmt.Printf("version: %d of %d\n", mver, len(m.Migrations)) fmt.Println("host: ", conf.DB.Host) fmt.Println("database:", conf.DB.DBName) } diff --git a/serv/cmd_test.go b/serv/cmd_test.go index 9353985..19c5367 100644 --- a/serv/cmd_test.go +++ b/serv/cmd_test.go @@ -1,9 +1,6 @@ package serv_test -import ( - "testing" -) - +/* func TestErrorLineExtract(t *testing.T) { tests := []struct { source string @@ -102,3 +99,4 @@ error`, } } } +*/ diff --git a/serv/utils.go b/serv/utils.go index 1bbcc16..baf49c3 100644 --- a/serv/utils.go +++ b/serv/utils.go @@ -56,7 +56,7 @@ func gqlHash(b string, vars []byte) string { } } - if vars == nil { + if vars == nil || len(vars) == 0 { return hex.EncodeToString(h.Sum(nil)) } diff --git a/serv/utils_test.go b/serv/utils_test.go index 6d9b6a4..17d91b7 100644 --- a/serv/utils_test.go +++ b/serv/utils_test.go @@ -61,6 +61,39 @@ func TestRelaxHash2(t *testing.T) { } } +func TestRelaxHash3(t *testing.T) { + var v1 = `users { + id + email + picture: avatar + products(limit: 2, where: {price: {gt: 10}}) { + id + name + description + } + }` + + var v2 = ` + users { + id + email + picture: avatar + products(limit: 2, where: {price: {gt: 10}}) { + id + name + description + } + } +` + + h1 := gqlHash(v1, nil) + h2 := gqlHash(v2, nil) + + if strings.Compare(h1, h2) != 0 { + t.Fatal("Hashes don't match they should") + } +} + func TestRelaxHashWithVars1(t *testing.T) { var q1 = ` products(