diff --git a/config/dev.yml b/config/dev.yml
index 3bf2481..e0a7263 100644
--- a/config/dev.yml
+++ b/config/dev.yml
@@ -9,7 +9,7 @@ log_level: "debug"
# from the allow list are permitted.
# When it's 'false' all queries are saved to the
# the allow list in ./config/allow.list
-production: true
+production: false
# Throw a 401 on auth failure for queries that need auth
auth_fail_block: false
diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js
index f7f2667..436c885 100644
--- a/docs/.vuepress/config.js
+++ b/docs/.vuepress/config.js
@@ -26,7 +26,7 @@ module.exports = {
['meta', { prefix: ogprefix, property: 'og:title', content: title }],
['meta', { prefix: ogprefix, property: 'twitter:title', content: title }],
['meta', { prefix: ogprefix, property: 'og:type', content: 'website' }],
- ['meta', { prefix: ogprefix, property: 'og:url', content: 'https://supergraph.dev }],
+ ['meta', { prefix: ogprefix, property: 'og:url', content: 'https://supergraph.dev' }],
['meta', { prefix: ogprefix, property: 'og:description', content: description }],
//['meta', { prefix: ogprefix, property: 'og:image', content: 'https://wireupyourfrontend.com/assets/logo.png' }],
// ['meta', { name: 'apple-mobile-web-app-capable', content: 'yes' }],
diff --git a/docs/guide.md b/docs/guide.md
index dc278f3..b816e4d 100644
--- a/docs/guide.md
+++ b/docs/guide.md
@@ -476,6 +476,21 @@ query {
}
```
+Multiple tables can also be fetched using a single GraphQL query. This is very fast since the entire query is converted into a single SQL query which the database can efficiently run.
+
+```graphql
+query {
+ user {
+ full_name
+ email
+ }
+ products {
+ name
+ description
+ }
+}
+```
+
### Fetching data
To fetch a specific `product` by it's ID you can use the `id` argument. The real name id field will be resolved automatically so this query will work even if your id column is named something like `product_id`.
@@ -908,6 +923,40 @@ class AddSearchColumn < ActiveRecord::Migration[5.1]
end
```
+## GraphQL with React
+
+This is a quick simple example using `graphql.js` [https://github.com/f/graphql.js/](https://github.com/f/graphql.js/)
+
+```js
+import React, { useState, useEffect } from 'react'
+import graphql from 'graphql.js'
+
+// Create a GraphQL client pointing to Super Graph
+var graph = graphql("http://localhost:3000/api/v1/graphql", { asJSON: true })
+
+const App = () => {
+ const [user, setUser] = useState(null)
+
+ useEffect(() => {
+ async function action() {
+ // Use the GraphQL client to execute a graphQL query
+ // The second argument to the client are the variables you need to pass
+ const result = await graph(`{ user { id first_name last_name picture_url } }`)()
+ setUser(result)
+ }
+ action()
+ }, []);
+
+ return (
+
+
{ JSON.stringify(user) }
+
+ );
+}
+```
+
+export default App;
+
## Authentication
You can only have one type of auth enabled. You can either pick Rails or JWT.
diff --git a/jsn/json_test.go b/jsn/json_test.go
index 7b08a37..08fb1fe 100644
--- a/jsn/json_test.go
+++ b/jsn/json_test.go
@@ -374,10 +374,6 @@ func TestKeys2(t *testing.T) {
"id", "posts", "title", "description", "full_name", "email", "books", "name", "description",
}
- // for i := range fields {
- // fmt.Println("-->", string(fields[i]))
- // }
-
if len(exp) != len(fields) {
t.Errorf("Expected %d fields %d", len(exp), len(fields))
}
diff --git a/psql/mutate_test.go b/psql/mutate_test.go
index b719255..2c53b99 100644
--- a/psql/mutate_test.go
+++ b/psql/mutate_test.go
@@ -12,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 "users_0"."id" AS "id") AS "sel_0")) AS "sel_json_0" FROM (SELECT "users"."id" FROM "users") AS "users_0") 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', json_0) FROM (SELECT row_to_json((SELECT "json_row_0" FROM (SELECT "users_0"."id" AS "id") AS "json_row_0")) AS "json_0" FROM (SELECT "users"."id" FROM "users" LIMIT ('1') :: integer) AS "users_0" LIMIT ('1') :: integer) AS "sel_0"`
vars := map[string]json.RawMessage{
"data": json.RawMessage(`{"email": "reannagreenholt@orn.com", "full_name": "Flo Barton"}`),
@@ -36,7 +36,7 @@ func singleInsert(t *testing.T) {
}
}`
- sql := `WITH "products" AS (WITH "input" AS (SELECT {{insert}}::json AS j) INSERT INTO "products" ("name", "description", "user_id") SELECT "name", "description", "user_id" 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") AS "products_0") AS "done_1337"`
+ sql := `WITH "products" AS (WITH "input" AS (SELECT {{insert}}::json AS j) INSERT INTO "products" ("name", "description", "user_id") SELECT "name", "description", "user_id" FROM input i, json_populate_record(NULL::products, i.j) t RETURNING *) SELECT json_object_agg('product', json_0) FROM (SELECT row_to_json((SELECT "json_row_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name") AS "json_row_0")) AS "json_0" FROM (SELECT "products"."id", "products"."name" FROM "products" LIMIT ('1') :: integer) AS "products_0" LIMIT ('1') :: integer) AS "sel_0"`
vars := map[string]json.RawMessage{
"insert": json.RawMessage(` { "name": "my_name", "woo": { "hoo": "goo" }, "description": "my_desc", "user_id": 5 }`),
@@ -60,7 +60,7 @@ func bulkInsert(t *testing.T) {
}
}`
- 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") AS "products_0") 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', json_0) FROM (SELECT row_to_json((SELECT "json_row_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name") AS "json_row_0")) AS "json_0" FROM (SELECT "products"."id", "products"."name" FROM "products" LIMIT ('1') :: integer) AS "products_0" LIMIT ('1') :: integer) AS "sel_0"`
vars := map[string]json.RawMessage{
"insert": json.RawMessage(` [{ "name": "my_name", "woo": { "hoo": "goo" }, "description": "my_desc" }]`),
@@ -84,7 +84,7 @@ func singleUpsert(t *testing.T) {
}
}`
- sql := `WITH "products" AS (WITH "input" AS (SELECT {{upsert}}::json AS j) INSERT INTO "products" ("name", "description") SELECT "name", "description" FROM input i, json_populate_record(NULL::products, i.j) t ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, description = EXCLUDED.description 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") AS "products_0") AS "done_1337"`
+ sql := `WITH "products" AS (WITH "input" AS (SELECT {{upsert}}::json AS j) INSERT INTO "products" ("name", "description") SELECT "name", "description" FROM input i, json_populate_record(NULL::products, i.j) t ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, description = EXCLUDED.description RETURNING *) SELECT json_object_agg('product', json_0) FROM (SELECT row_to_json((SELECT "json_row_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name") AS "json_row_0")) AS "json_0" FROM (SELECT "products"."id", "products"."name" FROM "products" LIMIT ('1') :: integer) AS "products_0" LIMIT ('1') :: integer) AS "sel_0"`
vars := map[string]json.RawMessage{
"upsert": json.RawMessage(` { "name": "my_name", "woo": { "hoo": "goo" }, "description": "my_desc" }`),
@@ -108,7 +108,7 @@ func singleUpsertWhere(t *testing.T) {
}
}`
- sql := `WITH "products" AS (WITH "input" AS (SELECT {{upsert}}::json AS j) INSERT INTO "products" ("name", "description") SELECT "name", "description" FROM input i, json_populate_record(NULL::products, i.j) t ON CONFLICT (id) WHERE (("products"."price") > 3) DO UPDATE SET name = EXCLUDED.name, description = EXCLUDED.description 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") AS "products_0") AS "done_1337"`
+ sql := `WITH "products" AS (WITH "input" AS (SELECT {{upsert}}::json AS j) INSERT INTO "products" ("name", "description") SELECT "name", "description" FROM input i, json_populate_record(NULL::products, i.j) t ON CONFLICT (id) WHERE (("products"."price") > 3) DO UPDATE SET name = EXCLUDED.name, description = EXCLUDED.description RETURNING *) SELECT json_object_agg('product', json_0) FROM (SELECT row_to_json((SELECT "json_row_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name") AS "json_row_0")) AS "json_0" FROM (SELECT "products"."id", "products"."name" FROM "products" LIMIT ('1') :: integer) AS "products_0" LIMIT ('1') :: integer) AS "sel_0"`
vars := map[string]json.RawMessage{
"upsert": json.RawMessage(` { "name": "my_name", "woo": { "hoo": "goo" }, "description": "my_desc" }`),
@@ -132,7 +132,7 @@ func bulkUpsert(t *testing.T) {
}
}`
- sql := `WITH "products" AS (WITH "input" AS (SELECT {{upsert}}::json AS j) INSERT INTO "products" ("name", "description") SELECT "name", "description" FROM input i, json_populate_recordset(NULL::products, i.j) t ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, description = EXCLUDED.description 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") AS "products_0") AS "done_1337"`
+ sql := `WITH "products" AS (WITH "input" AS (SELECT {{upsert}}::json AS j) INSERT INTO "products" ("name", "description") SELECT "name", "description" FROM input i, json_populate_recordset(NULL::products, i.j) t ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, description = EXCLUDED.description RETURNING *) SELECT json_object_agg('product', json_0) FROM (SELECT row_to_json((SELECT "json_row_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name") AS "json_row_0")) AS "json_0" FROM (SELECT "products"."id", "products"."name" FROM "products" LIMIT ('1') :: integer) AS "products_0" LIMIT ('1') :: integer) AS "sel_0"`
vars := map[string]json.RawMessage{
"upsert": json.RawMessage(` [{ "name": "my_name", "woo": { "hoo": "goo" }, "description": "my_desc" }]`),
@@ -156,7 +156,7 @@ func singleUpdate(t *testing.T) {
}
}`
- 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"."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") AS "products_0") 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"."id") = 1) AND (("products"."id") = 15) RETURNING *) SELECT json_object_agg('product', json_0) FROM (SELECT row_to_json((SELECT "json_row_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name") AS "json_row_0")) AS "json_0" FROM (SELECT "products"."id", "products"."name" FROM "products" LIMIT ('1') :: integer) AS "products_0" LIMIT ('1') :: integer) AS "sel_0"`
vars := map[string]json.RawMessage{
"update": json.RawMessage(` { "name": "my_name", "woo": { "hoo": "goo" }, "description": "my_desc" }`),
@@ -180,7 +180,7 @@ func delete(t *testing.T) {
}
}`
- sql := `WITH "products" AS (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") AS "products_0") AS "done_1337"`
+ sql := `WITH "products" AS (DELETE FROM "products" WHERE (("products"."price") > 0) AND (("products"."price") < 8) AND (("products"."id") = 1) RETURNING *) SELECT json_object_agg('product', json_0) FROM (SELECT row_to_json((SELECT "json_row_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name") AS "json_row_0")) AS "json_0" FROM (SELECT "products"."id", "products"."name" FROM "products" LIMIT ('1') :: integer) AS "products_0" LIMIT ('1') :: integer) AS "sel_0"`
vars := map[string]json.RawMessage{
"update": json.RawMessage(` { "name": "my_name", "woo": { "hoo": "goo" }, "description": "my_desc" }`),
@@ -203,7 +203,7 @@ func blockedInsert(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 WHERE false 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") AS "users_0") 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 WHERE false RETURNING *) SELECT json_object_agg('user', json_0) FROM (SELECT row_to_json((SELECT "json_row_0" FROM (SELECT "users_0"."id" AS "id") AS "json_row_0")) AS "json_0" FROM (SELECT "users"."id" FROM "users" LIMIT ('1') :: integer) AS "users_0" LIMIT ('1') :: integer) AS "sel_0"`
vars := map[string]json.RawMessage{
"data": json.RawMessage(`{"email": "reannagreenholt@orn.com", "full_name": "Flo Barton"}`),
@@ -227,7 +227,7 @@ func blockedUpdate(t *testing.T) {
}
}`
- sql := `WITH "users" AS (WITH "input" AS (SELECT {{data}}::json AS j) UPDATE "users" SET ("full_name", "email") = (SELECT "full_name", "email" FROM input i, json_populate_record(NULL::users, i.j) t) WHERE false RETURNING *) SELECT json_object_agg('user', sel_json_0) FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "users_0"."id" AS "id", "users_0"."email" AS "email") AS "sel_0")) AS "sel_json_0" FROM (SELECT "users"."id", "users"."email" FROM "users") AS "users_0") AS "done_1337"`
+ sql := `WITH "users" AS (WITH "input" AS (SELECT {{data}}::json AS j) UPDATE "users" SET ("full_name", "email") = (SELECT "full_name", "email" FROM input i, json_populate_record(NULL::users, i.j) t) WHERE false RETURNING *) SELECT json_object_agg('user', json_0) FROM (SELECT row_to_json((SELECT "json_row_0" FROM (SELECT "users_0"."id" AS "id", "users_0"."email" AS "email") AS "json_row_0")) AS "json_0" FROM (SELECT "users"."id", "users"."email" FROM "users" LIMIT ('1') :: integer) AS "users_0" LIMIT ('1') :: integer) AS "sel_0"`
vars := map[string]json.RawMessage{
"data": json.RawMessage(`{"email": "reannagreenholt@orn.com", "full_name": "Flo Barton"}`),
@@ -250,7 +250,7 @@ func simpleInsertWithPresets(t *testing.T) {
}
}`
- sql := `WITH "products" AS (WITH "input" AS (SELECT {{data}}::json AS j) INSERT INTO "products" ("name", "price", "created_at", "updated_at", "user_id") SELECT "name", "price", 'now' :: timestamp without time zone, 'now' :: timestamp without time zone, '$user_id' :: bigint 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") AS "sel_0")) AS "sel_json_0" FROM (SELECT "products"."id" FROM "products") AS "products_0") AS "done_1337"`
+ sql := `WITH "products" AS (WITH "input" AS (SELECT {{data}}::json AS j) INSERT INTO "products" ("name", "price", "created_at", "updated_at", "user_id") SELECT "name", "price", 'now' :: timestamp without time zone, 'now' :: timestamp without time zone, '$user_id' :: bigint FROM input i, json_populate_record(NULL::products, i.j) t RETURNING *) SELECT json_object_agg('product', json_0) FROM (SELECT row_to_json((SELECT "json_row_0" FROM (SELECT "products_0"."id" AS "id") AS "json_row_0")) AS "json_0" FROM (SELECT "products"."id" FROM "products" LIMIT ('1') :: integer) AS "products_0" LIMIT ('1') :: integer) AS "sel_0"`
vars := map[string]json.RawMessage{
"data": json.RawMessage(`{"name": "Tomato", "price": 5.76}`),
@@ -273,7 +273,7 @@ func simpleUpdateWithPresets(t *testing.T) {
}
}`
- sql := `WITH "products" AS (WITH "input" AS (SELECT {{data}}::json AS j) UPDATE "products" SET ("name", "price", "updated_at") = (SELECT "name", "price", 'now' :: timestamp without time zone FROM input i, json_populate_record(NULL::products, i.j) t) WHERE (("products"."user_id") = {{user_id}}) RETURNING *) SELECT json_object_agg('product', sel_json_0) FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."id" AS "id") AS "sel_0")) AS "sel_json_0" FROM (SELECT "products"."id" FROM "products") AS "products_0") AS "done_1337"`
+ sql := `WITH "products" AS (WITH "input" AS (SELECT {{data}}::json AS j) UPDATE "products" SET ("name", "price", "updated_at") = (SELECT "name", "price", 'now' :: timestamp without time zone FROM input i, json_populate_record(NULL::products, i.j) t) WHERE (("products"."user_id") = {{user_id}}) RETURNING *) SELECT json_object_agg('product', json_0) FROM (SELECT row_to_json((SELECT "json_row_0" FROM (SELECT "products_0"."id" AS "id") AS "json_row_0")) AS "json_0" FROM (SELECT "products"."id" FROM "products" LIMIT ('1') :: integer) AS "products_0" LIMIT ('1') :: integer) AS "sel_0"`
vars := map[string]json.RawMessage{
"data": json.RawMessage(`{"name": "Apple", "price": 1.25}`),
diff --git a/psql/query.go b/psql/query.go
index 92fa009..772a03f 100644
--- a/psql/query.go
+++ b/psql/query.go
@@ -77,30 +77,49 @@ func (co *Compiler) compileQuery(qc *qcode.QCode, w io.Writer) (uint32, error) {
}
c := &compilerContext{w, qc.Selects, co}
- root := &qc.Selects[0]
-
- ti, err := c.schema.GetTable(root.Table)
- if err != nil {
- return 0, err
- }
+ multiRoot := (len(qc.Roots) > 1)
st := NewStack()
- st.Push(root.ID + closeBlock)
- st.Push(root.ID)
- //fmt.Fprintf(w, `SELECT json_object_agg('%s', %s) FROM (`,
- //root.FieldName, root.Table)
- io.WriteString(c.w, `SELECT json_object_agg('`)
- io.WriteString(c.w, root.FieldName)
- io.WriteString(c.w, `', `)
+ if multiRoot {
+ io.WriteString(c.w, `SELECT row_to_json("json_root") FROM (SELECT `)
+
+ for i, id := range qc.Roots {
+ root := qc.Selects[id]
+
+ st.Push(root.ID + closeBlock)
+ st.Push(root.ID)
+
+ if i != 0 {
+ io.WriteString(c.w, `, `)
+ }
+
+ io.WriteString(c.w, `"sel_`)
+ int2string(c.w, root.ID)
+ io.WriteString(c.w, `"."json_`)
+ int2string(c.w, root.ID)
+ io.WriteString(c.w, `"`)
+
+ alias(c.w, root.FieldName)
+ }
+
+ io.WriteString(c.w, ` FROM `)
- if ti.Singular == false {
- io.WriteString(c.w, root.Table)
} else {
- io.WriteString(c.w, "sel_json_")
+ root := qc.Selects[0]
+
+ io.WriteString(c.w, `SELECT json_object_agg(`)
+ io.WriteString(c.w, `'`)
+ io.WriteString(c.w, root.FieldName)
+ io.WriteString(c.w, `', `)
+ io.WriteString(c.w, `json_`)
int2string(c.w, root.ID)
+
+ st.Push(root.ID + closeBlock)
+ st.Push(root.ID)
+
+ io.WriteString(c.w, `) FROM `)
}
- io.WriteString(c.w, `) FROM (`)
var ignored uint32
@@ -114,16 +133,21 @@ func (co *Compiler) compileQuery(qc *qcode.QCode, w io.Writer) (uint32, error) {
if id < closeBlock {
sel := &c.s[id]
+ if sel.ParentID == -1 {
+ io.WriteString(c.w, `(`)
+ }
+
ti, err := c.schema.GetTable(sel.Table)
if err != nil {
return 0, err
}
- if sel.ID != 0 {
+ if sel.ParentID != -1 {
if err = c.renderLateralJoin(sel); err != nil {
return 0, err
}
}
+
skipped, err := c.renderSelect(sel, ti)
if err != nil {
return 0, err
@@ -153,16 +177,25 @@ func (co *Compiler) compileQuery(qc *qcode.QCode, w io.Writer) (uint32, error) {
return 0, err
}
- if sel.ID != 0 {
+ if sel.ParentID != -1 {
if err = c.renderLateralJoinClose(sel); err != nil {
return 0, err
}
+ } else {
+ io.WriteString(c.w, `)`)
+ aliasWithID(c.w, `sel`, sel.ID)
+
+ if st.Len() != 0 {
+ io.WriteString(c.w, `, `)
+ }
}
+
}
}
- io.WriteString(c.w, `)`)
- alias(c.w, `done_1337`)
+ if multiRoot {
+ io.WriteString(c.w, `) AS "json_root"`)
+ }
return ignored, nil
}
@@ -219,7 +252,7 @@ func (c *compilerContext) renderSelect(sel *qcode.Select, ti *DBTableInfo) (uint
if ti.Singular == false {
//fmt.Fprintf(w, `SELECT coalesce(json_agg("%s"`, c.sel.Table)
io.WriteString(c.w, `SELECT coalesce(json_agg("`)
- io.WriteString(c.w, "sel_json_")
+ io.WriteString(c.w, "json_")
int2string(c.w, sel.ID)
io.WriteString(c.w, `"`)
@@ -232,7 +265,7 @@ func (c *compilerContext) renderSelect(sel *qcode.Select, ti *DBTableInfo) (uint
//fmt.Fprintf(w, `), '[]') AS "%s" FROM (`, c.sel.Table)
io.WriteString(c.w, `), '[]')`)
- alias(c.w, sel.Table)
+ aliasWithID(c.w, "json", sel.ID)
io.WriteString(c.w, ` FROM (`)
}
@@ -245,8 +278,8 @@ func (c *compilerContext) renderSelect(sel *qcode.Select, ti *DBTableInfo) (uint
io.WriteString(c.w, `row_to_json((`)
- //fmt.Fprintf(w, `SELECT "sel_%d" FROM (SELECT `, c.sel.ID)
- io.WriteString(c.w, `SELECT "sel_`)
+ //fmt.Fprintf(w, `SELECT "%d" FROM (SELECT `, c.sel.ID)
+ io.WriteString(c.w, `SELECT "json_row_`)
int2string(c.w, sel.ID)
io.WriteString(c.w, `" FROM (SELECT `)
@@ -260,13 +293,13 @@ func (c *compilerContext) renderSelect(sel *qcode.Select, ti *DBTableInfo) (uint
return skipped, err
}
- //fmt.Fprintf(w, `) AS "sel_%d"`, c.sel.ID)
+ //fmt.Fprintf(w, `) AS "%d"`, c.sel.ID)
io.WriteString(c.w, `)`)
- aliasWithID(c.w, "sel", sel.ID)
+ aliasWithID(c.w, "json_row", sel.ID)
//fmt.Fprintf(w, `)) AS "%s"`, c.sel.Table)
io.WriteString(c.w, `))`)
- aliasWithID(c.w, "sel_json", sel.ID)
+ aliasWithID(c.w, "json", sel.ID)
// END-ROW-TO-JSON
if hasOrder {
@@ -295,8 +328,8 @@ func (c *compilerContext) renderSelectClose(sel *qcode.Select, ti *DBTableInfo)
}
switch {
- case sel.Paging.NoLimit:
- break
+ case ti.Singular:
+ io.WriteString(c.w, ` LIMIT ('1') :: integer`)
case len(sel.Paging.Limit) != 0:
//fmt.Fprintf(w, ` LIMIT ('%s') :: integer`, c.sel.Paging.Limit)
@@ -304,8 +337,8 @@ func (c *compilerContext) renderSelectClose(sel *qcode.Select, ti *DBTableInfo)
io.WriteString(c.w, sel.Paging.Limit)
io.WriteString(c.w, `') :: integer`)
- case ti.Singular:
- io.WriteString(c.w, ` LIMIT ('1') :: integer`)
+ case sel.Paging.NoLimit:
+ break
default:
io.WriteString(c.w, ` LIMIT ('20') :: integer`)
@@ -319,9 +352,9 @@ func (c *compilerContext) renderSelectClose(sel *qcode.Select, ti *DBTableInfo)
}
if ti.Singular == false {
- //fmt.Fprintf(w, `) AS "sel_json_agg_%d"`, c.sel.ID)
+ //fmt.Fprintf(w, `) AS "json_agg_%d"`, c.sel.ID)
io.WriteString(c.w, `)`)
- aliasWithID(c.w, "sel_json_agg", sel.ID)
+ aliasWithID(c.w, "json_agg", sel.ID)
}
return nil
@@ -445,24 +478,18 @@ func (c *compilerContext) renderJoinedColumns(sel *qcode.Select, ti *DBTableInfo
}
childSel := &c.s[id]
- cti, err := c.schema.GetTable(childSel.Table)
- if err != nil {
- return err
- }
-
//fmt.Fprintf(w, `"%s_%d_join"."%s" AS "%s"`,
//s.Table, s.ID, s.Table, s.FieldName)
- if cti.Singular {
- io.WriteString(c.w, `"sel_json_`)
- int2string(c.w, childSel.ID)
- io.WriteString(c.w, `" AS "`)
- io.WriteString(c.w, childSel.FieldName)
- io.WriteString(c.w, `"`)
-
- } else {
- colWithTableIDSuffixAlias(c.w, childSel.Table, childSel.ID,
- "_join", childSel.Table, childSel.FieldName)
- }
+ //if cti.Singular {
+ io.WriteString(c.w, `"`)
+ io.WriteString(c.w, childSel.Table)
+ io.WriteString(c.w, `_`)
+ int2string(c.w, childSel.ID)
+ io.WriteString(c.w, `_join"."json_`)
+ int2string(c.w, childSel.ID)
+ io.WriteString(c.w, `" AS "`)
+ io.WriteString(c.w, childSel.FieldName)
+ io.WriteString(c.w, `"`)
}
return nil
@@ -472,7 +499,7 @@ func (c *compilerContext) renderBaseSelect(sel *qcode.Select, ti *DBTableInfo,
childCols []*qcode.Column, skipped uint32) error {
var groupBy []int
- isRoot := sel.ID == 0
+ isRoot := sel.ParentID == -1
isFil := sel.Where != nil
isSearch := sel.Args["search"] != nil
isAgg := false
@@ -641,8 +668,8 @@ func (c *compilerContext) renderBaseSelect(sel *qcode.Select, ti *DBTableInfo,
}
switch {
- case sel.Paging.NoLimit:
- break
+ case ti.Singular:
+ io.WriteString(c.w, ` LIMIT ('1') :: integer`)
case len(sel.Paging.Limit) != 0:
//fmt.Fprintf(w, ` LIMIT ('%s') :: integer`, c.sel.Paging.Limit)
@@ -650,8 +677,8 @@ func (c *compilerContext) renderBaseSelect(sel *qcode.Select, ti *DBTableInfo,
io.WriteString(c.w, sel.Paging.Limit)
io.WriteString(c.w, `') :: integer`)
- case ti.Singular:
- io.WriteString(c.w, ` LIMIT ('1') :: integer`)
+ case sel.Paging.NoLimit:
+ break
default:
io.WriteString(c.w, ` LIMIT ('20') :: integer`)
diff --git a/psql/query_test.go b/psql/query_test.go
index ebabeea..bd00060 100644
--- a/psql/query_test.go
+++ b/psql/query_test.go
@@ -28,7 +28,7 @@ func withComplexArgs(t *testing.T) {
}
}`
- 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"`
+ sql := `SELECT json_object_agg('products', json_0) FROM (SELECT coalesce(json_agg("json_0" ORDER BY "products_0_price_ob" DESC), '[]') AS "json_0" FROM (SELECT DISTINCT ON ("products_0_price_ob") row_to_json((SELECT "json_row_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name", "products_0"."price" AS "price") AS "json_row_0")) AS "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 "json_agg_0") AS "sel_0"`
resSQL, err := compileGQLToPSQL(gql, nil, "user")
if err != nil {
@@ -56,7 +56,7 @@ func withWhereMultiOr(t *testing.T) {
}
}`
- 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"`
+ sql := `SELECT json_object_agg('products', json_0) FROM (SELECT coalesce(json_agg("json_0"), '[]') AS "json_0" FROM (SELECT row_to_json((SELECT "json_row_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name", "products_0"."price" AS "price") AS "json_row_0")) AS "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 "json_agg_0") AS "sel_0"`
resSQL, err := compileGQLToPSQL(gql, nil, "user")
if err != nil {
@@ -82,7 +82,7 @@ func withWhereIsNull(t *testing.T) {
}
}`
- 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"`
+ sql := `SELECT json_object_agg('products', json_0) FROM (SELECT coalesce(json_agg("json_0"), '[]') AS "json_0" FROM (SELECT row_to_json((SELECT "json_row_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name", "products_0"."price" AS "price") AS "json_row_0")) AS "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 "json_agg_0") AS "sel_0"`
resSQL, err := compileGQLToPSQL(gql, nil, "user")
if err != nil {
@@ -108,7 +108,7 @@ func withWhereAndList(t *testing.T) {
}
}`
- 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"`
+ sql := `SELECT json_object_agg('products', json_0) FROM (SELECT coalesce(json_agg("json_0"), '[]') AS "json_0" FROM (SELECT row_to_json((SELECT "json_row_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name", "products_0"."price" AS "price") AS "json_row_0")) AS "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 "json_agg_0") AS "sel_0"`
resSQL, err := compileGQLToPSQL(gql, nil, "user")
if err != nil {
@@ -128,7 +128,7 @@ func fetchByID(t *testing.T) {
}
}`
- 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"`
+ sql := `SELECT json_object_agg('product', json_0) FROM (SELECT row_to_json((SELECT "json_row_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name") AS "json_row_0")) AS "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 "sel_0"`
resSQL, err := compileGQLToPSQL(gql, nil, "user")
if err != nil {
@@ -148,7 +148,7 @@ func searchQuery(t *testing.T) {
}
}`
- 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"`
+ sql := `SELECT json_object_agg('products', json_0) FROM (SELECT coalesce(json_agg("json_0"), '[]') AS "json_0" FROM (SELECT row_to_json((SELECT "json_row_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name") AS "json_row_0")) AS "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 "json_agg_0") AS "sel_0"`
resSQL, err := compileGQLToPSQL(gql, nil, "user")
if err != nil {
@@ -171,7 +171,7 @@ func oneToMany(t *testing.T) {
}
}`
- 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" 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"`
+ sql := `SELECT json_object_agg('users', json_0) FROM (SELECT coalesce(json_agg("json_0"), '[]') AS "json_0" FROM (SELECT row_to_json((SELECT "json_row_0" FROM (SELECT "users_0"."email" AS "email", "products_1_join"."json_1" AS "products") AS "json_row_0")) AS "json_0" FROM (SELECT "users"."email", "users"."id" FROM "users" LIMIT ('20') :: integer) AS "users_0" LEFT OUTER JOIN LATERAL (SELECT coalesce(json_agg("json_1"), '[]') AS "json_1" FROM (SELECT row_to_json((SELECT "json_row_1" FROM (SELECT "products_1"."name" AS "name", "products_1"."price" AS "price") AS "json_row_1")) AS "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 "json_agg_1") AS "products_1_join" ON ('true') LIMIT ('20') :: integer) AS "json_agg_0") AS "sel_0"`
resSQL, err := compileGQLToPSQL(gql, nil, "user")
if err != nil {
@@ -194,7 +194,7 @@ func belongsTo(t *testing.T) {
}
}`
- 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"`
+ sql := `SELECT json_object_agg('products', json_0) FROM (SELECT coalesce(json_agg("json_0"), '[]') AS "json_0" FROM (SELECT row_to_json((SELECT "json_row_0" FROM (SELECT "products_0"."name" AS "name", "products_0"."price" AS "price", "users_1_join"."json_1" AS "users") AS "json_row_0")) AS "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("json_1"), '[]') AS "json_1" FROM (SELECT row_to_json((SELECT "json_row_1" FROM (SELECT "users_1"."email" AS "email") AS "json_row_1")) AS "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 "json_agg_1") AS "users_1_join" ON ('true') LIMIT ('20') :: integer) AS "json_agg_0") AS "sel_0"`
resSQL, err := compileGQLToPSQL(gql, nil, "user")
if err != nil {
@@ -217,7 +217,7 @@ func manyToMany(t *testing.T) {
}
}`
- 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"`
+ sql := `SELECT json_object_agg('products', json_0) FROM (SELECT coalesce(json_agg("json_0"), '[]') AS "json_0" FROM (SELECT row_to_json((SELECT "json_row_0" FROM (SELECT "products_0"."name" AS "name", "customers_1_join"."json_1" AS "customers") AS "json_row_0")) AS "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("json_1"), '[]') AS "json_1" FROM (SELECT row_to_json((SELECT "json_row_1" FROM (SELECT "customers_1"."email" AS "email", "customers_1"."full_name" AS "full_name") AS "json_row_1")) AS "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 "json_agg_1") AS "customers_1_join" ON ('true') LIMIT ('20') :: integer) AS "json_agg_0") AS "sel_0"`
resSQL, err := compileGQLToPSQL(gql, nil, "user")
if err != nil {
@@ -240,7 +240,7 @@ func manyToManyReverse(t *testing.T) {
}
}`
- 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"`
+ sql := `SELECT json_object_agg('customers', json_0) FROM (SELECT coalesce(json_agg("json_0"), '[]') AS "json_0" FROM (SELECT row_to_json((SELECT "json_row_0" FROM (SELECT "customers_0"."email" AS "email", "customers_0"."full_name" AS "full_name", "products_1_join"."json_1" AS "products") AS "json_row_0")) AS "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("json_1"), '[]') AS "json_1" FROM (SELECT row_to_json((SELECT "json_row_1" FROM (SELECT "products_1"."name" AS "name") AS "json_row_1")) AS "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 "json_agg_1") AS "products_1_join" ON ('true') LIMIT ('20') :: integer) AS "json_agg_0") AS "sel_0"`
resSQL, err := compileGQLToPSQL(gql, nil, "user")
if err != nil {
@@ -260,7 +260,7 @@ func aggFunction(t *testing.T) {
}
}`
- 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"`
+ sql := `SELECT json_object_agg('products', json_0) FROM (SELECT coalesce(json_agg("json_0"), '[]') AS "json_0" FROM (SELECT row_to_json((SELECT "json_row_0" FROM (SELECT "products_0"."name" AS "name", "products_0"."count_price" AS "count_price") AS "json_row_0")) AS "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 "json_agg_0") AS "sel_0"`
resSQL, err := compileGQLToPSQL(gql, nil, "user")
if err != nil {
@@ -280,7 +280,7 @@ func aggFunctionBlockedByCol(t *testing.T) {
}
}`
- 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") AS "sel_0")) AS "sel_json_0" FROM (SELECT "products"."name" FROM "products" LIMIT ('20') :: integer) AS "products_0" LIMIT ('20') :: integer) AS "sel_json_agg_0") AS "done_1337"`
+ sql := `SELECT json_object_agg('products', json_0) FROM (SELECT coalesce(json_agg("json_0"), '[]') AS "json_0" FROM (SELECT row_to_json((SELECT "json_row_0" FROM (SELECT "products_0"."name" AS "name") AS "json_row_0")) AS "json_0" FROM (SELECT "products"."name" FROM "products" LIMIT ('20') :: integer) AS "products_0" LIMIT ('20') :: integer) AS "json_agg_0") AS "sel_0"`
resSQL, err := compileGQLToPSQL(gql, nil, "anon")
if err != nil {
@@ -300,7 +300,7 @@ func aggFunctionDisabled(t *testing.T) {
}
}`
- 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") AS "sel_0")) AS "sel_json_0" FROM (SELECT "products"."name" FROM "products" LIMIT ('20') :: integer) AS "products_0" LIMIT ('20') :: integer) AS "sel_json_agg_0") AS "done_1337"`
+ sql := `SELECT json_object_agg('products', json_0) FROM (SELECT coalesce(json_agg("json_0"), '[]') AS "json_0" FROM (SELECT row_to_json((SELECT "json_row_0" FROM (SELECT "products_0"."name" AS "name") AS "json_row_0")) AS "json_0" FROM (SELECT "products"."name" FROM "products" LIMIT ('20') :: integer) AS "products_0" LIMIT ('20') :: integer) AS "json_agg_0") AS "sel_0"`
resSQL, err := compileGQLToPSQL(gql, nil, "anon1")
if err != nil {
@@ -320,7 +320,7 @@ func aggFunctionWithFilter(t *testing.T) {
}
}`
- 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"`
+ sql := `SELECT json_object_agg('products', json_0) FROM (SELECT coalesce(json_agg("json_0"), '[]') AS "json_0" FROM (SELECT row_to_json((SELECT "json_row_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."max_price" AS "max_price") AS "json_row_0")) AS "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 "json_agg_0") AS "sel_0"`
resSQL, err := compileGQLToPSQL(gql, nil, "user")
if err != nil {
@@ -339,7 +339,7 @@ func syntheticTables(t *testing.T) {
}
}`
- sql := `SELECT json_object_agg('me', sel_json_0) FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT ) 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"`
+ sql := `SELECT json_object_agg('me', json_0) FROM (SELECT row_to_json((SELECT "json_row_0" FROM (SELECT ) AS "json_row_0")) AS "json_0" FROM (SELECT "users"."email" FROM "users" WHERE ((("users"."id") = {{user_id}})) LIMIT ('1') :: integer) AS "users_0" LIMIT ('1') :: integer) AS "sel_0"`
resSQL, err := compileGQLToPSQL(gql, nil, "user")
if err != nil {
@@ -359,7 +359,7 @@ func queryWithVariables(t *testing.T) {
}
}`
- 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"`
+ sql := `SELECT json_object_agg('product', json_0) FROM (SELECT row_to_json((SELECT "json_row_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name") AS "json_row_0")) AS "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 "sel_0"`
resSQL, err := compileGQLToPSQL(gql, nil, "user")
if err != nil {
@@ -385,7 +385,40 @@ func withWhereOnRelations(t *testing.T) {
}
}`
- 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"."id" AS "id", "users_0"."email" AS "email") AS "sel_0")) AS "sel_json_0" FROM (SELECT "users"."id", "users"."email" FROM "users" WHERE (NOT EXISTS (SELECT 1 FROM products WHERE (("products"."user_id") = ("users"."id")))) LIMIT ('20') :: integer) AS "users_0" LIMIT ('20') :: integer) AS "sel_json_agg_0") AS "done_1337"`
+ sql := `SELECT json_object_agg('users', json_0) FROM (SELECT coalesce(json_agg("json_0"), '[]') AS "json_0" FROM (SELECT row_to_json((SELECT "json_row_0" FROM (SELECT "users_0"."id" AS "id", "users_0"."email" AS "email") AS "json_row_0")) AS "json_0" FROM (SELECT "users"."id", "users"."email" FROM "users" WHERE (NOT EXISTS (SELECT 1 FROM products WHERE (("products"."user_id") = ("users"."id")))) LIMIT ('20') :: integer) AS "users_0" LIMIT ('20') :: integer) AS "json_agg_0") AS "sel_0"`
+
+ resSQL, err := compileGQLToPSQL(gql, nil, "user")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if string(resSQL) != sql {
+ t.Fatal(errNotExpected)
+ }
+}
+
+func multiRoot(t *testing.T) {
+ gql := `query {
+ product {
+ id
+ name
+ customer {
+ email
+ }
+ customers {
+ email
+ }
+ }
+ user {
+ id
+ email
+ }
+ customer {
+ id
+ }
+ }`
+
+ sql := `SELECT row_to_json("json_root") FROM (SELECT "sel_0"."json_0" AS "customer", "sel_1"."json_1" AS "user", "sel_2"."json_2" AS "product" FROM (SELECT row_to_json((SELECT "json_row_2" FROM (SELECT "products_2"."id" AS "id", "products_2"."name" AS "name", "customers_3_join"."json_3" AS "customers", "customer_4_join"."json_4" AS "customer") AS "json_row_2")) AS "json_2" FROM (SELECT "products"."id", "products"."name" FROM "products" WHERE ((("products"."price") > 0) AND (("products"."price") < 8)) LIMIT ('1') :: integer) AS "products_2" LEFT OUTER JOIN LATERAL (SELECT row_to_json((SELECT "json_row_4" FROM (SELECT "customers_4"."email" AS "email") AS "json_row_4")) AS "json_4" FROM (SELECT "customers"."email" FROM "customers" LEFT OUTER JOIN "purchases" ON (("purchases"."product_id") = ("products_2"."id")) WHERE ((("customers"."id") = ("purchases"."customer_id"))) LIMIT ('1') :: integer) AS "customers_4" LIMIT ('1') :: integer) AS "customer_4_join" ON ('true') LEFT OUTER JOIN LATERAL (SELECT coalesce(json_agg("json_3"), '[]') AS "json_3" FROM (SELECT row_to_json((SELECT "json_row_3" FROM (SELECT "customers_3"."email" AS "email") AS "json_row_3")) AS "json_3" FROM (SELECT "customers"."email" FROM "customers" LEFT OUTER JOIN "purchases" ON (("purchases"."product_id") = ("products_2"."id")) WHERE ((("customers"."id") = ("purchases"."customer_id"))) LIMIT ('20') :: integer) AS "customers_3" LIMIT ('20') :: integer) AS "json_agg_3") AS "customers_3_join" ON ('true') LIMIT ('1') :: integer) AS "sel_2", (SELECT row_to_json((SELECT "json_row_1" FROM (SELECT "users_1"."id" AS "id", "users_1"."email" AS "email") AS "json_row_1")) AS "json_1" FROM (SELECT "users"."id", "users"."email" FROM "users" LIMIT ('1') :: integer) AS "users_1" LIMIT ('1') :: integer) AS "sel_1", (SELECT row_to_json((SELECT "json_row_0" FROM (SELECT "customers_0"."id" AS "id") AS "json_row_0")) AS "json_0" FROM (SELECT "customers"."id" FROM "customers" LIMIT ('1') :: integer) AS "customers_0" LIMIT ('1') :: integer) AS "sel_0") AS "json_root"`
resSQL, err := compileGQLToPSQL(gql, nil, "user")
if err != nil {
@@ -406,7 +439,7 @@ func blockedQuery(t *testing.T) {
}
}`
- sql := `SELECT json_object_agg('user', sel_json_0) FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "users_0"."id" AS "id", "users_0"."full_name" AS "full_name", "users_0"."email" AS "email") AS "sel_0")) AS "sel_json_0" FROM (SELECT "users"."id", "users"."full_name", "users"."email" FROM "users" WHERE (false) LIMIT ('1') :: integer) AS "users_0" LIMIT ('1') :: integer) AS "done_1337"`
+ sql := `SELECT json_object_agg('user', json_0) FROM (SELECT row_to_json((SELECT "json_row_0" FROM (SELECT "users_0"."id" AS "id", "users_0"."full_name" AS "full_name", "users_0"."email" AS "email") AS "json_row_0")) AS "json_0" FROM (SELECT "users"."id", "users"."full_name", "users"."email" FROM "users" WHERE (false) LIMIT ('1') :: integer) AS "users_0" LIMIT ('1') :: integer) AS "sel_0"`
resSQL, err := compileGQLToPSQL(gql, nil, "bad_dude")
if err != nil {
@@ -426,7 +459,7 @@ func blockedFunctions(t *testing.T) {
}
}`
- 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") AS "sel_0")) AS "sel_json_0" FROM (SELECT "users"."email" FROM "users" WHERE (false) LIMIT ('20') :: integer) AS "users_0" LIMIT ('20') :: integer) AS "sel_json_agg_0") AS "done_1337"`
+ sql := `SELECT json_object_agg('users', json_0) FROM (SELECT coalesce(json_agg("json_0"), '[]') AS "json_0" FROM (SELECT row_to_json((SELECT "json_row_0" FROM (SELECT "users_0"."email" AS "email") AS "json_row_0")) AS "json_0" FROM (SELECT "users"."email" FROM "users" WHERE (false) LIMIT ('20') :: integer) AS "users_0" LIMIT ('20') :: integer) AS "json_agg_0") AS "sel_0"`
resSQL, err := compileGQLToPSQL(gql, nil, "bad_dude")
if err != nil {
@@ -456,6 +489,7 @@ func TestCompileQuery(t *testing.T) {
t.Run("syntheticTables", syntheticTables)
t.Run("queryWithVariables", queryWithVariables)
t.Run("withWhereOnRelations", withWhereOnRelations)
+ t.Run("multiRoot", multiRoot)
t.Run("blockedQuery", blockedQuery)
t.Run("blockedFunctions", blockedFunctions)
}
diff --git a/qcode/parse.go b/qcode/parse.go
index 0fe6c34..6600b79 100644
--- a/qcode/parse.go
+++ b/qcode/parse.go
@@ -148,24 +148,13 @@ func parseSelectionSet(gql []byte) (*Operation, error) {
if p.peek(itemObjOpen) {
p.ignore()
- }
-
- if p.peek(itemName) {
- op = opPool.Get().(*Operation)
- op.Reset()
-
- op.Type = opQuery
- op.Name = ""
- op.Fields = op.fieldsA[:0]
- op.Args = op.argsA[:0]
- op.Fields, err = p.parseFields(op.Fields)
-
+ op, err = p.parseQueryOp()
} else {
op, err = p.parseOp()
+ }
- if err != nil {
- return nil, err
- }
+ if err != nil {
+ return nil, err
}
lexPool.Put(l)
@@ -259,6 +248,37 @@ func (p *Parser) parseOp() (*Operation, error) {
if p.peek(itemObjOpen) {
p.ignore()
+
+ for n := 0; n < 10; n++ {
+ if p.peek(itemName) == false {
+ break
+ }
+
+ op.Fields, err = p.parseFields(op.Fields)
+ if err != nil {
+ return nil, err
+ }
+ }
+ }
+
+ return op, nil
+}
+
+func (p *Parser) parseQueryOp() (*Operation, error) {
+ op := opPool.Get().(*Operation)
+ op.Reset()
+
+ op.Type = opQuery
+ op.Fields = op.fieldsA[:0]
+ op.Args = op.argsA[:0]
+
+ var err error
+
+ for n := 0; n < 10; n++ {
+ if p.peek(itemName) == false {
+ break
+ }
+
op.Fields, err = p.parseFields(op.Fields)
if err != nil {
return nil, err
@@ -300,16 +320,12 @@ func (p *Parser) parseFields(fields []Field) ([]Field, error) {
return nil, err
}
- if f.ID != 0 {
- intf := st.Peek()
- pid, ok := intf.(int32)
-
- if !ok {
- return nil, fmt.Errorf("14: unexpected value %v (%t)", intf, intf)
- }
-
+ intf := st.Peek()
+ if pid, ok := intf.(int32); ok {
f.ParentID = pid
fields[pid].Children = append(fields[pid].Children, f.ID)
+ } else {
+ f.ParentID = -1
}
if p.peek(itemObjOpen) {
diff --git a/qcode/parse_test.go b/qcode/parse_test.go
index ea74871..4335583 100644
--- a/qcode/parse_test.go
+++ b/qcode/parse_test.go
@@ -14,10 +14,10 @@ func TestCompile1(t *testing.T) {
})
_, err := qc.Compile([]byte(`
- product(id: 15) {
+ { product(id: 15) {
id
name
- }`), "user")
+ } }`), "user")
if err != nil {
t.Fatal(err)
diff --git a/qcode/qcode.go b/qcode/qcode.go
index 6d8cf9f..571d46e 100644
--- a/qcode/qcode.go
+++ b/qcode/qcode.go
@@ -30,6 +30,8 @@ type QCode struct {
Type QType
ActionVar string
Selects []Select
+ Roots []int32
+ rootsA [5]int32
}
type Select struct {
@@ -233,6 +235,7 @@ func (com *Compiler) Compile(query []byte, role string) (*QCode, error) {
var err error
qc := QCode{Type: QTQuery}
+ qc.Roots = qc.rootsA[:0]
op, err := Parse(query)
if err != nil {
@@ -250,7 +253,7 @@ func (com *Compiler) Compile(query []byte, role string) (*QCode, error) {
func (com *Compiler) compileQuery(qc *QCode, op *Operation, role string) error {
id := int32(0)
- parentID := int32(0)
+ parentID := int32(-1)
if len(op.Fields) == 0 {
return errors.New("invalid graphql no query found")
@@ -269,7 +272,12 @@ func (com *Compiler) compileQuery(qc *QCode, op *Operation, role string) error {
if len(op.Fields) == 0 {
return errors.New("empty query")
}
- st.Push(op.Fields[0].ID)
+
+ for i := range op.Fields {
+ if op.Fields[i].ParentID == -1 {
+ st.Push(op.Fields[i].ID)
+ }
+ }
for {
if st.Len() == 0 {
@@ -313,11 +321,6 @@ func (com *Compiler) compileQuery(qc *QCode, op *Operation, role string) error {
s.PresetList = trv.update.pslist
}
- if s.ID != 0 {
- p := &selects[s.ParentID]
- p.Children = append(p.Children, s.ID)
- }
-
if len(field.Alias) != 0 {
s.FieldName = field.Alias
} else {
@@ -329,6 +332,15 @@ func (com *Compiler) compileQuery(qc *QCode, op *Operation, role string) error {
return err
}
+ // Order is important addFilters must come after compileArgs
+ if s.ParentID == -1 {
+ qc.Roots = append(qc.Roots, s.ID)
+ com.addFilters(qc, s, role)
+ } else {
+ p := &selects[s.ParentID]
+ p.Children = append(p.Children, s.ID)
+ }
+
s.Cols = make([]Column, 0, len(field.Children))
action = QTQuery
@@ -362,36 +374,40 @@ func (com *Compiler) compileQuery(qc *QCode, op *Operation, role string) error {
return errors.New("invalid query")
}
- var fil *Exp
- root := &selects[0]
+ qc.Selects = selects[:id]
+ return nil
+}
- if trv, ok := com.tr[role][op.Fields[0].Name]; ok {
+func (com *Compiler) addFilters(qc *QCode, root *Select, role string) {
+ var fil *Exp
+
+ if trv, ok := com.tr[role][root.Table]; ok {
fil = trv.filter(qc.Type)
}
- if fil != nil {
- switch fil.Op {
- case OpNop:
- case OpFalse:
- root.Where = fil
- default:
- if root.Where != nil {
- ow := root.Where
-
- root.Where = expPool.Get().(*Exp)
- root.Where.Reset()
- root.Where.Op = OpAnd
- root.Where.Children = root.Where.childrenA[:2]
- root.Where.Children[0] = fil
- root.Where.Children[1] = ow
- } else {
- root.Where = fil
- }
- }
+ if fil == nil {
+ return
}
- qc.Selects = selects[:id]
- return nil
+ switch fil.Op {
+ case OpNop:
+ case OpFalse:
+ root.Where = fil
+
+ default:
+ if root.Where != nil {
+ ow := root.Where
+
+ root.Where = expPool.Get().(*Exp)
+ root.Where.Reset()
+ root.Where.Op = OpAnd
+ root.Where.Children = root.Where.childrenA[:2]
+ root.Where.Children[0] = fil
+ root.Where.Children[1] = ow
+ } else {
+ root.Where = fil
+ }
+ }
}
func (com *Compiler) compileArgs(qc *QCode, sel *Select, args []Arg) error {
diff --git a/serv/core.go b/serv/core.go
index a0ffe12..fad4de1 100644
--- a/serv/core.go
+++ b/serv/core.go
@@ -52,8 +52,6 @@ func (c *coreContext) execQuery() ([]byte, error) {
var qc *qcode.QCode
var data []byte
- logger.Debug().Str("role", c.req.role).Msg(c.req.Query)
-
if conf.Production {
var ps *preparedItem
@@ -156,12 +154,17 @@ func (c *coreContext) resolvePreparedSQL() ([]byte, *preparedItem, error) {
if mutation {
err = tx.QueryRow(c, ps.stmt.SQL, vars...).Scan(&root)
} else {
- err = tx.QueryRow(c, ps.stmt.SQL, vars...).Scan(&c.req.role, &root)
+ err = tx.QueryRow(c, ps.stmt.SQL, vars...).Scan(&role, &root)
}
+
+ logger.Debug().Str("default_role", c.req.role).Str("role", role).Msg(c.req.Query)
+
if err != nil {
return nil, nil, err
}
+ c.req.role = role
+
if err := tx.Commit(c); err != nil {
return nil, nil, err
}
@@ -229,12 +232,23 @@ func (c *coreContext) resolveSQL() ([]byte, uint32, error) {
}
var root []byte
+ var role string
+
+ log := logger.Debug()
if mutation {
err = tx.QueryRow(c, finalSQL).Scan(&root)
+ log = log.Str("role", role)
+
} else {
- err = tx.QueryRow(c, finalSQL).Scan(&c.req.role, &root)
+ err = tx.QueryRow(c, finalSQL).Scan(&role, &root)
+ log = log.Str("default_role", c.req.role).Str("role", role)
+ c.req.role = role
+
}
+
+ log.Msg(c.req.Query)
+
if err != nil {
return nil, 0, err
}
@@ -250,10 +264,9 @@ func (c *coreContext) resolveSQL() ([]byte, uint32, error) {
}
if conf.EnableTracing && len(st.qc.Selects) != 0 {
- c.addTrace(
- st.qc.Selects,
- st.qc.Selects[0].ID,
- stime)
+ for _, id := range st.qc.Roots {
+ c.addTrace(st.qc.Selects, id, stime)
+ }
}
if conf.Production == false {
@@ -451,7 +464,7 @@ func (c *coreContext) addTrace(sel []qcode.Select, id int32, st time.Time) {
c.res.Extensions.Tracing.Duration = du
n := 1
- for i := id; i != 0; i = sel[i].ParentID {
+ for i := id; i != -1; i = sel[i].ParentID {
n++
}
path := make([]string, n)
@@ -459,7 +472,7 @@ func (c *coreContext) addTrace(sel []qcode.Select, id int32, st time.Time) {
n--
for i := id; ; i = sel[i].ParentID {
path[n] = sel[i].Table
- if sel[i].ID == 0 {
+ if sel[i].ParentID == -1 {
break
}
n--
diff --git a/serv/core_build.go b/serv/core_build.go
index c08263d..79367a9 100644
--- a/serv/core_build.go
+++ b/serv/core_build.go
@@ -55,8 +55,11 @@ func (c *coreContext) buildStmt() ([]stmt, error) {
}
if conf.Production && role.Name == "anon" {
- if _, ok := role.tablesMap[qc.Selects[0].Table]; !ok {
- continue
+ for _, id := range qc.Roots {
+ root := qc.Selects[id]
+ if _, ok := role.tablesMap[root.Table]; !ok {
+ continue
+ }
}
}