Compare commits
3 Commits
Author | SHA1 | Date | |
---|---|---|---|
6bd27d7c79 | |||
a4c09dedd5 | |||
176514b5f1 |
@ -287,4 +287,18 @@ query {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
"data": {
|
||||||
|
"name": "WOOO",
|
||||||
|
"price": 50.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation {
|
||||||
|
products(insert: $data) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ log_level: "debug"
|
|||||||
# from the allow list are permitted.
|
# from the allow list are permitted.
|
||||||
# When it's 'false' all queries are saved to the
|
# When it's 'false' all queries are saved to the
|
||||||
# the allow list in ./config/allow.list
|
# the allow list in ./config/allow.list
|
||||||
production: true
|
production: false
|
||||||
|
|
||||||
# Throw a 401 on auth failure for queries that need auth
|
# Throw a 401 on auth failure for queries that need auth
|
||||||
auth_fail_block: false
|
auth_fail_block: false
|
||||||
@ -97,10 +97,9 @@ database:
|
|||||||
# Enable this if you need the user id in triggers, etc
|
# Enable this if you need the user id in triggers, etc
|
||||||
set_user_id: false
|
set_user_id: false
|
||||||
|
|
||||||
# Define variables here that you want to use in filters
|
# Define additional variables here to be used with filters
|
||||||
# sub-queries must be wrapped in ()
|
|
||||||
variables:
|
variables:
|
||||||
account_id: "(select account_id from users where id = $user_id)"
|
admin_account_id: "5"
|
||||||
|
|
||||||
# Define defaults to for the field key and values below
|
# Define defaults to for the field key and values below
|
||||||
defaults:
|
defaults:
|
||||||
@ -174,8 +173,10 @@ roles:
|
|||||||
filters: ["{ user_id: { eq: $user_id } }"]
|
filters: ["{ user_id: { eq: $user_id } }"]
|
||||||
columns: ["id", "name", "description" ]
|
columns: ["id", "name", "description" ]
|
||||||
presets:
|
presets:
|
||||||
|
- user_id: "$user_id"
|
||||||
- created_at: "now"
|
- created_at: "now"
|
||||||
|
- updated_at: "now"
|
||||||
|
|
||||||
update:
|
update:
|
||||||
filters: ["{ user_id: { eq: $user_id } }"]
|
filters: ["{ user_id: { eq: $user_id } }"]
|
||||||
columns:
|
columns:
|
||||||
@ -188,8 +189,7 @@ roles:
|
|||||||
block: true
|
block: true
|
||||||
|
|
||||||
- name: admin
|
- name: admin
|
||||||
match: id = 1
|
match: id = 1000
|
||||||
tables:
|
tables:
|
||||||
- name: users
|
- name: users
|
||||||
# query:
|
filters: []
|
||||||
# filters: ["{ account_id: { _eq: $account_id } }"]
|
|
||||||
|
@ -46,10 +46,10 @@ for (i = 0; i < product_count; i++) {
|
|||||||
var data = {
|
var data = {
|
||||||
name: fake.beer_name(),
|
name: fake.beer_name(),
|
||||||
description: desc,
|
description: desc,
|
||||||
price: fake.price(),
|
price: fake.price()
|
||||||
user_id: user.id,
|
//user_id: user.id,
|
||||||
created_at: "now",
|
//created_at: "now",
|
||||||
updated_at: "now"
|
//updated_at: "now"
|
||||||
}
|
}
|
||||||
|
|
||||||
var res = graphql(" \
|
var res = graphql(" \
|
||||||
@ -57,7 +57,9 @@ for (i = 0; i < product_count; i++) {
|
|||||||
product(insert: $data) { \
|
product(insert: $data) { \
|
||||||
id \
|
id \
|
||||||
} \
|
} \
|
||||||
}", { data: data })
|
}", { data: data }, {
|
||||||
|
user_id: 5
|
||||||
|
})
|
||||||
products.push(res.product)
|
products.push(res.product)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ module.exports = {
|
|||||||
['meta', { prefix: ogprefix, property: 'og:title', content: title }],
|
['meta', { prefix: ogprefix, property: 'og:title', content: title }],
|
||||||
['meta', { prefix: ogprefix, property: 'twitter:title', content: title }],
|
['meta', { prefix: ogprefix, property: 'twitter:title', content: title }],
|
||||||
['meta', { prefix: ogprefix, property: 'og:type', content: 'website' }],
|
['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:description', content: description }],
|
||||||
//['meta', { prefix: ogprefix, property: 'og:image', content: 'https://wireupyourfrontend.com/assets/logo.png' }],
|
//['meta', { prefix: ogprefix, property: 'og:image', content: 'https://wireupyourfrontend.com/assets/logo.png' }],
|
||||||
// ['meta', { name: 'apple-mobile-web-app-capable', content: 'yes' }],
|
// ['meta', { name: 'apple-mobile-web-app-capable', content: 'yes' }],
|
||||||
|
@ -276,7 +276,7 @@ transmission_gear_type
|
|||||||
// Text
|
// Text
|
||||||
word
|
word
|
||||||
sentence
|
sentence
|
||||||
paragrph
|
paragraph
|
||||||
question
|
question
|
||||||
quote
|
quote
|
||||||
|
|
||||||
@ -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
|
### 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`.
|
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
|
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 (
|
||||||
|
<div className="App">
|
||||||
|
<h1>{ JSON.stringify(user) }</h1>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
export default App;
|
||||||
|
|
||||||
## Authentication
|
## Authentication
|
||||||
|
|
||||||
You can only have one type of auth enabled. You can either pick Rails or JWT.
|
You can only have one type of auth enabled. You can either pick Rails or JWT.
|
||||||
@ -1034,25 +1083,6 @@ must be run to help figure out a users role. This query can be as complex as you
|
|||||||
|
|
||||||
The individual roles are defined under the `roles` parameter and this includes each table the role has a custom setting for. The role is dynamically matched using the `match` parameter for example in the above case `users.id = 1` means that when the `roles_query` is executed a user with the id `1` willbe assigned the admin role and those that don't match get the `user` role if authenticated successfully or the `anon` role.
|
The individual roles are defined under the `roles` parameter and this includes each table the role has a custom setting for. The role is dynamically matched using the `match` parameter for example in the above case `users.id = 1` means that when the `roles_query` is executed a user with the id `1` willbe assigned the admin role and those that don't match get the `user` role if authenticated successfully or the `anon` role.
|
||||||
|
|
||||||
This below example would work for SAAS apps where an account (tenant) is usually the top parent table to everything else.
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
roles_query: "SELECT * FROM users JOIN accounts on accounts.id = users.account_id WHERE users.id = $user_id"
|
|
||||||
|
|
||||||
roles:
|
|
||||||
- name: user
|
|
||||||
tables:
|
|
||||||
- name: users
|
|
||||||
...
|
|
||||||
|
|
||||||
- name: admin
|
|
||||||
match: accounts.admin_id = $user_id
|
|
||||||
tables:
|
|
||||||
- name: users
|
|
||||||
query:
|
|
||||||
filters: [{ accounts: { id: { eq: $account_id } } }]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Remote Joins
|
## Remote Joins
|
||||||
|
|
||||||
It often happens that after fetching some data from the DB we need to call another API to fetch some more data and all this combined into a single JSON response. For example along with a list of users you need their last 5 payments from Stripe. This requires you to query your DB for the users and Stripe for the payments. Super Graph handles all this for you also only the fields you requested from the Stripe API are returned.
|
It often happens that after fetching some data from the DB we need to call another API to fetch some more data and all this combined into a single JSON response. For example along with a list of users you need their last 5 payments from Stripe. This requires you to query your DB for the users and Stripe for the payments. Super Graph handles all this for you also only the fields you requested from the Stripe API are returned.
|
||||||
@ -1241,10 +1271,9 @@ database:
|
|||||||
# Enable this if you need the user id in triggers, etc
|
# Enable this if you need the user id in triggers, etc
|
||||||
set_user_id: false
|
set_user_id: false
|
||||||
|
|
||||||
# Define variables here that you want to use in filters
|
# Define additional variables here to be used with filters
|
||||||
# sub-queries must be wrapped in ()
|
|
||||||
variables:
|
variables:
|
||||||
account_id: "(select account_id from users where id = $user_id)"
|
admin_account_id: "5"
|
||||||
|
|
||||||
# Define defaults to for the field key and values below
|
# Define defaults to for the field key and values below
|
||||||
defaults:
|
defaults:
|
||||||
@ -1329,14 +1358,13 @@ roles:
|
|||||||
- updated_at: "now"
|
- updated_at: "now"
|
||||||
|
|
||||||
delete:
|
delete:
|
||||||
deny: true
|
block: true
|
||||||
|
|
||||||
- name: admin
|
- name: admin
|
||||||
match: id = 1
|
match: id = 1000
|
||||||
tables:
|
tables:
|
||||||
- name: users
|
- name: users
|
||||||
# query:
|
filters: []
|
||||||
# filters: ["{ account_id: { _eq: $account_id } }"]
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -374,10 +374,6 @@ func TestKeys2(t *testing.T) {
|
|||||||
"id", "posts", "title", "description", "full_name", "email", "books", "name", "description",
|
"id", "posts", "title", "description", "full_name", "email", "books", "name", "description",
|
||||||
}
|
}
|
||||||
|
|
||||||
// for i := range fields {
|
|
||||||
// fmt.Println("-->", string(fields[i]))
|
|
||||||
// }
|
|
||||||
|
|
||||||
if len(exp) != len(fields) {
|
if len(exp) != len(fields) {
|
||||||
t.Errorf("Expected %d fields %d", len(exp), len(fields))
|
t.Errorf("Expected %d fields %d", len(exp), len(fields))
|
||||||
}
|
}
|
||||||
|
@ -77,9 +77,9 @@ func (c *compilerContext) renderInsert(qc *qcode.QCode, w io.Writer,
|
|||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
io.WriteString(c.w, `(WITH "input" AS (SELECT {{`)
|
io.WriteString(c.w, `(WITH "input" AS (SELECT '{{`)
|
||||||
io.WriteString(c.w, qc.ActionVar)
|
io.WriteString(c.w, qc.ActionVar)
|
||||||
io.WriteString(c.w, `}}::json AS j) INSERT INTO `)
|
io.WriteString(c.w, `}}' :: json AS j) INSERT INTO `)
|
||||||
quoted(c.w, ti.Name)
|
quoted(c.w, ti.Name)
|
||||||
io.WriteString(c.w, ` (`)
|
io.WriteString(c.w, ` (`)
|
||||||
c.renderInsertUpdateColumns(qc, w, jt, ti, false)
|
c.renderInsertUpdateColumns(qc, w, jt, ti, false)
|
||||||
@ -174,9 +174,9 @@ func (c *compilerContext) renderUpdate(qc *qcode.QCode, w io.Writer,
|
|||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
io.WriteString(c.w, `(WITH "input" AS (SELECT {{`)
|
io.WriteString(c.w, `(WITH "input" AS (SELECT '{{`)
|
||||||
io.WriteString(c.w, qc.ActionVar)
|
io.WriteString(c.w, qc.ActionVar)
|
||||||
io.WriteString(c.w, `}}::json AS j) UPDATE `)
|
io.WriteString(c.w, `}}' :: json AS j) UPDATE `)
|
||||||
quoted(c.w, ti.Name)
|
quoted(c.w, ti.Name)
|
||||||
io.WriteString(c.w, ` SET (`)
|
io.WriteString(c.w, ` SET (`)
|
||||||
c.renderInsertUpdateColumns(qc, w, jt, ti, false)
|
c.renderInsertUpdateColumns(qc, w, jt, ti, false)
|
||||||
|
@ -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{
|
vars := map[string]json.RawMessage{
|
||||||
"data": json.RawMessage(`{"email": "reannagreenholt@orn.com", "full_name": "Flo Barton"}`),
|
"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{
|
vars := map[string]json.RawMessage{
|
||||||
"insert": json.RawMessage(` { "name": "my_name", "woo": { "hoo": "goo" }, "description": "my_desc", "user_id": 5 }`),
|
"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{
|
vars := map[string]json.RawMessage{
|
||||||
"insert": json.RawMessage(` [{ "name": "my_name", "woo": { "hoo": "goo" }, "description": "my_desc" }]`),
|
"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{
|
vars := map[string]json.RawMessage{
|
||||||
"upsert": json.RawMessage(` { "name": "my_name", "woo": { "hoo": "goo" }, "description": "my_desc" }`),
|
"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{
|
vars := map[string]json.RawMessage{
|
||||||
"upsert": json.RawMessage(` { "name": "my_name", "woo": { "hoo": "goo" }, "description": "my_desc" }`),
|
"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{
|
vars := map[string]json.RawMessage{
|
||||||
"upsert": json.RawMessage(` [{ "name": "my_name", "woo": { "hoo": "goo" }, "description": "my_desc" }]`),
|
"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{
|
vars := map[string]json.RawMessage{
|
||||||
"update": json.RawMessage(` { "name": "my_name", "woo": { "hoo": "goo" }, "description": "my_desc" }`),
|
"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{
|
vars := map[string]json.RawMessage{
|
||||||
"update": json.RawMessage(` { "name": "my_name", "woo": { "hoo": "goo" }, "description": "my_desc" }`),
|
"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{
|
vars := map[string]json.RawMessage{
|
||||||
"data": json.RawMessage(`{"email": "reannagreenholt@orn.com", "full_name": "Flo Barton"}`),
|
"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{
|
vars := map[string]json.RawMessage{
|
||||||
"data": json.RawMessage(`{"email": "reannagreenholt@orn.com", "full_name": "Flo Barton"}`),
|
"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{
|
vars := map[string]json.RawMessage{
|
||||||
"data": json.RawMessage(`{"name": "Tomato", "price": 5.76}`),
|
"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}}' :: bigint) 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{
|
vars := map[string]json.RawMessage{
|
||||||
"data": json.RawMessage(`{"name": "Apple", "price": 1.25}`),
|
"data": json.RawMessage(`{"name": "Apple", "price": 1.25}`),
|
||||||
|
@ -171,7 +171,7 @@ func TestMain(m *testing.M) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
vars := NewVariables(map[string]string{
|
vars := NewVariables(map[string]string{
|
||||||
"account_id": "select account_id from users where id = $user_id",
|
"admin_account_id": "5",
|
||||||
})
|
})
|
||||||
|
|
||||||
pcompile = NewCompiler(Config{
|
pcompile = NewCompiler(Config{
|
||||||
|
158
psql/query.go
158
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}
|
c := &compilerContext{w, qc.Selects, co}
|
||||||
root := &qc.Selects[0]
|
multiRoot := (len(qc.Roots) > 1)
|
||||||
|
|
||||||
ti, err := c.schema.GetTable(root.Table)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
st := NewStack()
|
st := NewStack()
|
||||||
st.Push(root.ID + closeBlock)
|
|
||||||
st.Push(root.ID)
|
|
||||||
|
|
||||||
//fmt.Fprintf(w, `SELECT json_object_agg('%s', %s) FROM (`,
|
if multiRoot {
|
||||||
//root.FieldName, root.Table)
|
io.WriteString(c.w, `SELECT row_to_json("json_root") FROM (SELECT `)
|
||||||
io.WriteString(c.w, `SELECT json_object_agg('`)
|
|
||||||
io.WriteString(c.w, root.FieldName)
|
for i, id := range qc.Roots {
|
||||||
io.WriteString(c.w, `', `)
|
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 {
|
} 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)
|
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
|
var ignored uint32
|
||||||
|
|
||||||
@ -114,16 +133,21 @@ func (co *Compiler) compileQuery(qc *qcode.QCode, w io.Writer) (uint32, error) {
|
|||||||
if id < closeBlock {
|
if id < closeBlock {
|
||||||
sel := &c.s[id]
|
sel := &c.s[id]
|
||||||
|
|
||||||
|
if sel.ParentID == -1 {
|
||||||
|
io.WriteString(c.w, `(`)
|
||||||
|
}
|
||||||
|
|
||||||
ti, err := c.schema.GetTable(sel.Table)
|
ti, err := c.schema.GetTable(sel.Table)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if sel.ID != 0 {
|
if sel.ParentID != -1 {
|
||||||
if err = c.renderLateralJoin(sel); err != nil {
|
if err = c.renderLateralJoin(sel); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
skipped, err := c.renderSelect(sel, ti)
|
skipped, err := c.renderSelect(sel, ti)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
@ -153,16 +177,25 @@ func (co *Compiler) compileQuery(qc *qcode.QCode, w io.Writer) (uint32, error) {
|
|||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if sel.ID != 0 {
|
if sel.ParentID != -1 {
|
||||||
if err = c.renderLateralJoinClose(sel); err != nil {
|
if err = c.renderLateralJoinClose(sel); err != nil {
|
||||||
return 0, err
|
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, `)`)
|
if multiRoot {
|
||||||
alias(c.w, `done_1337`)
|
io.WriteString(c.w, `) AS "json_root"`)
|
||||||
|
}
|
||||||
|
|
||||||
return ignored, nil
|
return ignored, nil
|
||||||
}
|
}
|
||||||
@ -219,7 +252,7 @@ func (c *compilerContext) renderSelect(sel *qcode.Select, ti *DBTableInfo) (uint
|
|||||||
if ti.Singular == false {
|
if ti.Singular == false {
|
||||||
//fmt.Fprintf(w, `SELECT coalesce(json_agg("%s"`, c.sel.Table)
|
//fmt.Fprintf(w, `SELECT coalesce(json_agg("%s"`, c.sel.Table)
|
||||||
io.WriteString(c.w, `SELECT coalesce(json_agg("`)
|
io.WriteString(c.w, `SELECT coalesce(json_agg("`)
|
||||||
io.WriteString(c.w, "sel_json_")
|
io.WriteString(c.w, "json_")
|
||||||
int2string(c.w, sel.ID)
|
int2string(c.w, sel.ID)
|
||||||
io.WriteString(c.w, `"`)
|
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)
|
//fmt.Fprintf(w, `), '[]') AS "%s" FROM (`, c.sel.Table)
|
||||||
io.WriteString(c.w, `), '[]')`)
|
io.WriteString(c.w, `), '[]')`)
|
||||||
alias(c.w, sel.Table)
|
aliasWithID(c.w, "json", sel.ID)
|
||||||
io.WriteString(c.w, ` FROM (`)
|
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((`)
|
io.WriteString(c.w, `row_to_json((`)
|
||||||
|
|
||||||
//fmt.Fprintf(w, `SELECT "sel_%d" FROM (SELECT `, c.sel.ID)
|
//fmt.Fprintf(w, `SELECT "%d" FROM (SELECT `, c.sel.ID)
|
||||||
io.WriteString(c.w, `SELECT "sel_`)
|
io.WriteString(c.w, `SELECT "json_row_`)
|
||||||
int2string(c.w, sel.ID)
|
int2string(c.w, sel.ID)
|
||||||
io.WriteString(c.w, `" FROM (SELECT `)
|
io.WriteString(c.w, `" FROM (SELECT `)
|
||||||
|
|
||||||
@ -260,13 +293,13 @@ func (c *compilerContext) renderSelect(sel *qcode.Select, ti *DBTableInfo) (uint
|
|||||||
return skipped, err
|
return skipped, err
|
||||||
}
|
}
|
||||||
|
|
||||||
//fmt.Fprintf(w, `) AS "sel_%d"`, c.sel.ID)
|
//fmt.Fprintf(w, `) AS "%d"`, c.sel.ID)
|
||||||
io.WriteString(c.w, `)`)
|
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)
|
//fmt.Fprintf(w, `)) AS "%s"`, c.sel.Table)
|
||||||
io.WriteString(c.w, `))`)
|
io.WriteString(c.w, `))`)
|
||||||
aliasWithID(c.w, "sel_json", sel.ID)
|
aliasWithID(c.w, "json", sel.ID)
|
||||||
// END-ROW-TO-JSON
|
// END-ROW-TO-JSON
|
||||||
|
|
||||||
if hasOrder {
|
if hasOrder {
|
||||||
@ -295,8 +328,8 @@ func (c *compilerContext) renderSelectClose(sel *qcode.Select, ti *DBTableInfo)
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case sel.Paging.NoLimit:
|
case ti.Singular:
|
||||||
break
|
io.WriteString(c.w, ` LIMIT ('1') :: integer`)
|
||||||
|
|
||||||
case len(sel.Paging.Limit) != 0:
|
case len(sel.Paging.Limit) != 0:
|
||||||
//fmt.Fprintf(w, ` LIMIT ('%s') :: integer`, c.sel.Paging.Limit)
|
//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, sel.Paging.Limit)
|
||||||
io.WriteString(c.w, `') :: integer`)
|
io.WriteString(c.w, `') :: integer`)
|
||||||
|
|
||||||
case ti.Singular:
|
case sel.Paging.NoLimit:
|
||||||
io.WriteString(c.w, ` LIMIT ('1') :: integer`)
|
break
|
||||||
|
|
||||||
default:
|
default:
|
||||||
io.WriteString(c.w, ` LIMIT ('20') :: integer`)
|
io.WriteString(c.w, ` LIMIT ('20') :: integer`)
|
||||||
@ -319,9 +352,9 @@ func (c *compilerContext) renderSelectClose(sel *qcode.Select, ti *DBTableInfo)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ti.Singular == false {
|
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, `)`)
|
io.WriteString(c.w, `)`)
|
||||||
aliasWithID(c.w, "sel_json_agg", sel.ID)
|
aliasWithID(c.w, "json_agg", sel.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -445,24 +478,18 @@ func (c *compilerContext) renderJoinedColumns(sel *qcode.Select, ti *DBTableInfo
|
|||||||
}
|
}
|
||||||
childSel := &c.s[id]
|
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"`,
|
//fmt.Fprintf(w, `"%s_%d_join"."%s" AS "%s"`,
|
||||||
//s.Table, s.ID, s.Table, s.FieldName)
|
//s.Table, s.ID, s.Table, s.FieldName)
|
||||||
if cti.Singular {
|
//if cti.Singular {
|
||||||
io.WriteString(c.w, `"sel_json_`)
|
io.WriteString(c.w, `"`)
|
||||||
int2string(c.w, childSel.ID)
|
io.WriteString(c.w, childSel.Table)
|
||||||
io.WriteString(c.w, `" AS "`)
|
io.WriteString(c.w, `_`)
|
||||||
io.WriteString(c.w, childSel.FieldName)
|
int2string(c.w, childSel.ID)
|
||||||
io.WriteString(c.w, `"`)
|
io.WriteString(c.w, `_join"."json_`)
|
||||||
|
int2string(c.w, childSel.ID)
|
||||||
} else {
|
io.WriteString(c.w, `" AS "`)
|
||||||
colWithTableIDSuffixAlias(c.w, childSel.Table, childSel.ID,
|
io.WriteString(c.w, childSel.FieldName)
|
||||||
"_join", childSel.Table, childSel.FieldName)
|
io.WriteString(c.w, `"`)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -472,7 +499,7 @@ func (c *compilerContext) renderBaseSelect(sel *qcode.Select, ti *DBTableInfo,
|
|||||||
childCols []*qcode.Column, skipped uint32) error {
|
childCols []*qcode.Column, skipped uint32) error {
|
||||||
var groupBy []int
|
var groupBy []int
|
||||||
|
|
||||||
isRoot := sel.ID == 0
|
isRoot := sel.ParentID == -1
|
||||||
isFil := sel.Where != nil
|
isFil := sel.Where != nil
|
||||||
isSearch := sel.Args["search"] != nil
|
isSearch := sel.Args["search"] != nil
|
||||||
isAgg := false
|
isAgg := false
|
||||||
@ -641,8 +668,8 @@ func (c *compilerContext) renderBaseSelect(sel *qcode.Select, ti *DBTableInfo,
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case sel.Paging.NoLimit:
|
case ti.Singular:
|
||||||
break
|
io.WriteString(c.w, ` LIMIT ('1') :: integer`)
|
||||||
|
|
||||||
case len(sel.Paging.Limit) != 0:
|
case len(sel.Paging.Limit) != 0:
|
||||||
//fmt.Fprintf(w, ` LIMIT ('%s') :: integer`, c.sel.Paging.Limit)
|
//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, sel.Paging.Limit)
|
||||||
io.WriteString(c.w, `') :: integer`)
|
io.WriteString(c.w, `') :: integer`)
|
||||||
|
|
||||||
case ti.Singular:
|
case sel.Paging.NoLimit:
|
||||||
io.WriteString(c.w, ` LIMIT ('1') :: integer`)
|
break
|
||||||
|
|
||||||
default:
|
default:
|
||||||
io.WriteString(c.w, ` LIMIT ('20') :: integer`)
|
io.WriteString(c.w, ` LIMIT ('20') :: integer`)
|
||||||
@ -817,7 +844,6 @@ func (c *compilerContext) renderWhere(sel *qcode.Select, ti *DBTableInfo) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *compilerContext) renderNestedWhere(ex *qcode.Exp, sel *qcode.Select, ti *DBTableInfo) error {
|
func (c *compilerContext) renderNestedWhere(ex *qcode.Exp, sel *qcode.Select, ti *DBTableInfo) error {
|
||||||
|
|
||||||
for i := 0; i < len(ex.NestedCols)-1; i++ {
|
for i := 0; i < len(ex.NestedCols)-1; i++ {
|
||||||
cti, err := c.schema.GetTable(ex.NestedCols[i])
|
cti, err := c.schema.GetTable(ex.NestedCols[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -851,7 +877,14 @@ func (c *compilerContext) renderNestedWhere(ex *qcode.Exp, sel *qcode.Select, ti
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *compilerContext) renderOp(ex *qcode.Exp, sel *qcode.Select, ti *DBTableInfo) error {
|
func (c *compilerContext) renderOp(ex *qcode.Exp, sel *qcode.Select, ti *DBTableInfo) error {
|
||||||
|
var col *DBColumn
|
||||||
|
var ok bool
|
||||||
|
|
||||||
if len(ex.Col) != 0 {
|
if len(ex.Col) != 0 {
|
||||||
|
if col, ok = ti.Columns[ex.Col]; !ok {
|
||||||
|
return fmt.Errorf("no column '%s' found ", ex.Col)
|
||||||
|
}
|
||||||
|
|
||||||
io.WriteString(c.w, `((`)
|
io.WriteString(c.w, `((`)
|
||||||
colWithTable(c.w, ti.Name, ex.Col)
|
colWithTable(c.w, ti.Name, ex.Col)
|
||||||
io.WriteString(c.w, `) `)
|
io.WriteString(c.w, `) `)
|
||||||
@ -907,6 +940,9 @@ func (c *compilerContext) renderOp(ex *qcode.Exp, sel *qcode.Select, ti *DBTable
|
|||||||
if len(ti.PrimaryCol) == 0 {
|
if len(ti.PrimaryCol) == 0 {
|
||||||
return fmt.Errorf("no primary key column defined for %s", ti.Name)
|
return fmt.Errorf("no primary key column defined for %s", ti.Name)
|
||||||
}
|
}
|
||||||
|
if col, ok = ti.Columns[ti.PrimaryCol]; !ok {
|
||||||
|
return fmt.Errorf("no primary key column '%s' found ", ti.PrimaryCol)
|
||||||
|
}
|
||||||
//fmt.Fprintf(w, `(("%s") =`, c.ti.PrimaryCol)
|
//fmt.Fprintf(w, `(("%s") =`, c.ti.PrimaryCol)
|
||||||
io.WriteString(c.w, `((`)
|
io.WriteString(c.w, `((`)
|
||||||
colWithTable(c.w, ti.Name, ti.PrimaryCol)
|
colWithTable(c.w, ti.Name, ti.PrimaryCol)
|
||||||
@ -916,6 +952,9 @@ func (c *compilerContext) renderOp(ex *qcode.Exp, sel *qcode.Select, ti *DBTable
|
|||||||
if len(ti.TSVCol) == 0 {
|
if len(ti.TSVCol) == 0 {
|
||||||
return fmt.Errorf("no tsv column defined for %s", ti.Name)
|
return fmt.Errorf("no tsv column defined for %s", ti.Name)
|
||||||
}
|
}
|
||||||
|
if col, ok = ti.Columns[ti.TSVCol]; !ok {
|
||||||
|
return fmt.Errorf("no tsv column '%s' found ", ti.TSVCol)
|
||||||
|
}
|
||||||
//fmt.Fprintf(w, `(("%s") @@ to_tsquery('%s'))`, c.ti.TSVCol, val.Val)
|
//fmt.Fprintf(w, `(("%s") @@ to_tsquery('%s'))`, c.ti.TSVCol, val.Val)
|
||||||
io.WriteString(c.w, `(("`)
|
io.WriteString(c.w, `(("`)
|
||||||
io.WriteString(c.w, ti.TSVCol)
|
io.WriteString(c.w, ti.TSVCol)
|
||||||
@ -931,7 +970,7 @@ func (c *compilerContext) renderOp(ex *qcode.Exp, sel *qcode.Select, ti *DBTable
|
|||||||
if ex.Type == qcode.ValList {
|
if ex.Type == qcode.ValList {
|
||||||
c.renderList(ex)
|
c.renderList(ex)
|
||||||
} else {
|
} else {
|
||||||
c.renderVal(ex, c.vars)
|
c.renderVal(ex, c.vars, col)
|
||||||
}
|
}
|
||||||
|
|
||||||
io.WriteString(c.w, `)`)
|
io.WriteString(c.w, `)`)
|
||||||
@ -1008,7 +1047,7 @@ func (c *compilerContext) renderList(ex *qcode.Exp) {
|
|||||||
io.WriteString(c.w, `)`)
|
io.WriteString(c.w, `)`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *compilerContext) renderVal(ex *qcode.Exp, vars map[string]string) {
|
func (c *compilerContext) renderVal(ex *qcode.Exp, vars map[string]string, col *DBColumn) {
|
||||||
io.WriteString(c.w, ` `)
|
io.WriteString(c.w, ` `)
|
||||||
|
|
||||||
switch ex.Type {
|
switch ex.Type {
|
||||||
@ -1025,6 +1064,7 @@ func (c *compilerContext) renderVal(ex *qcode.Exp, vars map[string]string) {
|
|||||||
io.WriteString(c.w, `'`)
|
io.WriteString(c.w, `'`)
|
||||||
|
|
||||||
case qcode.ValVar:
|
case qcode.ValVar:
|
||||||
|
io.WriteString(c.w, `'`)
|
||||||
if val, ok := vars[ex.Val]; ok {
|
if val, ok := vars[ex.Val]; ok {
|
||||||
io.WriteString(c.w, val)
|
io.WriteString(c.w, val)
|
||||||
} else {
|
} else {
|
||||||
@ -1033,6 +1073,8 @@ func (c *compilerContext) renderVal(ex *qcode.Exp, vars map[string]string) {
|
|||||||
io.WriteString(c.w, ex.Val)
|
io.WriteString(c.w, ex.Val)
|
||||||
io.WriteString(c.w, `}}`)
|
io.WriteString(c.w, `}}`)
|
||||||
}
|
}
|
||||||
|
io.WriteString(c.w, `' :: `)
|
||||||
|
io.WriteString(c.w, col.Type)
|
||||||
}
|
}
|
||||||
//io.WriteString(c.w, `)`)
|
//io.WriteString(c.w, `)`)
|
||||||
}
|
}
|
||||||
|
@ -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")
|
resSQL, err := compileGQLToPSQL(gql, nil, "user")
|
||||||
if err != nil {
|
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")
|
resSQL, err := compileGQLToPSQL(gql, nil, "user")
|
||||||
if err != nil {
|
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")
|
resSQL, err := compileGQLToPSQL(gql, nil, "user")
|
||||||
if err != nil {
|
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")
|
resSQL, err := compileGQLToPSQL(gql, nil, "user")
|
||||||
if err != nil {
|
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")
|
resSQL, err := compileGQLToPSQL(gql, nil, "user")
|
||||||
if err != nil {
|
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")
|
resSQL, err := compileGQLToPSQL(gql, nil, "user")
|
||||||
if err != nil {
|
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")
|
resSQL, err := compileGQLToPSQL(gql, nil, "user")
|
||||||
if err != nil {
|
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")
|
resSQL, err := compileGQLToPSQL(gql, nil, "user")
|
||||||
if err != nil {
|
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")
|
resSQL, err := compileGQLToPSQL(gql, nil, "user")
|
||||||
if err != nil {
|
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")
|
resSQL, err := compileGQLToPSQL(gql, nil, "user")
|
||||||
if err != nil {
|
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")
|
resSQL, err := compileGQLToPSQL(gql, nil, "user")
|
||||||
if err != nil {
|
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")
|
resSQL, err := compileGQLToPSQL(gql, nil, "anon")
|
||||||
if err != nil {
|
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")
|
resSQL, err := compileGQLToPSQL(gql, nil, "anon1")
|
||||||
if err != nil {
|
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")
|
resSQL, err := compileGQLToPSQL(gql, nil, "user")
|
||||||
if err != nil {
|
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}}' :: bigint)) LIMIT ('1') :: integer) AS "users_0" LIMIT ('1') :: integer) AS "sel_0"`
|
||||||
|
|
||||||
resSQL, err := compileGQLToPSQL(gql, nil, "user")
|
resSQL, err := compileGQLToPSQL(gql, nil, "user")
|
||||||
if err != nil {
|
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}}' :: numeric(7,2)) AND (("products"."id") = '{{product_id}}' :: bigint)) LIMIT ('1') :: integer) AS "products_0" LIMIT ('1') :: integer) AS "sel_0"`
|
||||||
|
|
||||||
resSQL, err := compileGQLToPSQL(gql, nil, "user")
|
resSQL, err := compileGQLToPSQL(gql, nil, "user")
|
||||||
if err != nil {
|
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")
|
resSQL, err := compileGQLToPSQL(gql, nil, "user")
|
||||||
if err != nil {
|
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")
|
resSQL, err := compileGQLToPSQL(gql, nil, "bad_dude")
|
||||||
if err != nil {
|
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")
|
resSQL, err := compileGQLToPSQL(gql, nil, "bad_dude")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -456,6 +489,7 @@ func TestCompileQuery(t *testing.T) {
|
|||||||
t.Run("syntheticTables", syntheticTables)
|
t.Run("syntheticTables", syntheticTables)
|
||||||
t.Run("queryWithVariables", queryWithVariables)
|
t.Run("queryWithVariables", queryWithVariables)
|
||||||
t.Run("withWhereOnRelations", withWhereOnRelations)
|
t.Run("withWhereOnRelations", withWhereOnRelations)
|
||||||
|
t.Run("multiRoot", multiRoot)
|
||||||
t.Run("blockedQuery", blockedQuery)
|
t.Run("blockedQuery", blockedQuery)
|
||||||
t.Run("blockedFunctions", blockedFunctions)
|
t.Run("blockedFunctions", blockedFunctions)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package qcode
|
package qcode
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@ -121,3 +122,12 @@ func mapToList(m map[string]string) []string {
|
|||||||
sort.Strings(list)
|
sort.Strings(list)
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var varRe = regexp.MustCompile(`\$([a-zA-Z0-9_]+)`)
|
||||||
|
|
||||||
|
func parsePresets(m map[string]string) map[string]string {
|
||||||
|
for k, v := range m {
|
||||||
|
m[k] = varRe.ReplaceAllString(v, `{{$1}}`)
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
@ -148,24 +148,13 @@ func parseSelectionSet(gql []byte) (*Operation, error) {
|
|||||||
|
|
||||||
if p.peek(itemObjOpen) {
|
if p.peek(itemObjOpen) {
|
||||||
p.ignore()
|
p.ignore()
|
||||||
}
|
op, err = p.parseQueryOp()
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
op, err = p.parseOp()
|
op, err = p.parseOp()
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lexPool.Put(l)
|
lexPool.Put(l)
|
||||||
@ -259,6 +248,37 @@ func (p *Parser) parseOp() (*Operation, error) {
|
|||||||
|
|
||||||
if p.peek(itemObjOpen) {
|
if p.peek(itemObjOpen) {
|
||||||
p.ignore()
|
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)
|
op.Fields, err = p.parseFields(op.Fields)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -300,16 +320,12 @@ func (p *Parser) parseFields(fields []Field) ([]Field, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.ID != 0 {
|
intf := st.Peek()
|
||||||
intf := st.Peek()
|
if pid, ok := intf.(int32); ok {
|
||||||
pid, ok := intf.(int32)
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("14: unexpected value %v (%t)", intf, intf)
|
|
||||||
}
|
|
||||||
|
|
||||||
f.ParentID = pid
|
f.ParentID = pid
|
||||||
fields[pid].Children = append(fields[pid].Children, f.ID)
|
fields[pid].Children = append(fields[pid].Children, f.ID)
|
||||||
|
} else {
|
||||||
|
f.ParentID = -1
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.peek(itemObjOpen) {
|
if p.peek(itemObjOpen) {
|
||||||
|
@ -14,10 +14,10 @@ func TestCompile1(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
_, err := qc.Compile([]byte(`
|
_, err := qc.Compile([]byte(`
|
||||||
product(id: 15) {
|
{ product(id: 15) {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
}`), "user")
|
} }`), "user")
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -30,6 +30,8 @@ type QCode struct {
|
|||||||
Type QType
|
Type QType
|
||||||
ActionVar string
|
ActionVar string
|
||||||
Selects []Select
|
Selects []Select
|
||||||
|
Roots []int32
|
||||||
|
rootsA [5]int32
|
||||||
}
|
}
|
||||||
|
|
||||||
type Select struct {
|
type Select struct {
|
||||||
@ -200,7 +202,7 @@ func (com *Compiler) AddRole(role, table string, trc TRConfig) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
trv.insert.cols = listToMap(trc.Insert.Columns)
|
trv.insert.cols = listToMap(trc.Insert.Columns)
|
||||||
trv.insert.psmap = trc.Insert.Presets
|
trv.insert.psmap = parsePresets(trc.Insert.Presets)
|
||||||
trv.insert.pslist = mapToList(trv.insert.psmap)
|
trv.insert.pslist = mapToList(trv.insert.psmap)
|
||||||
|
|
||||||
// update config
|
// update config
|
||||||
@ -208,7 +210,7 @@ func (com *Compiler) AddRole(role, table string, trc TRConfig) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
trv.update.cols = listToMap(trc.Update.Columns)
|
trv.update.cols = listToMap(trc.Update.Columns)
|
||||||
trv.update.psmap = trc.Update.Presets
|
trv.update.psmap = parsePresets(trc.Update.Presets)
|
||||||
trv.update.pslist = mapToList(trv.update.psmap)
|
trv.update.pslist = mapToList(trv.update.psmap)
|
||||||
|
|
||||||
// delete config
|
// delete config
|
||||||
@ -233,6 +235,7 @@ func (com *Compiler) Compile(query []byte, role string) (*QCode, error) {
|
|||||||
var err error
|
var err error
|
||||||
|
|
||||||
qc := QCode{Type: QTQuery}
|
qc := QCode{Type: QTQuery}
|
||||||
|
qc.Roots = qc.rootsA[:0]
|
||||||
|
|
||||||
op, err := Parse(query)
|
op, err := Parse(query)
|
||||||
if err != nil {
|
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 {
|
func (com *Compiler) compileQuery(qc *QCode, op *Operation, role string) error {
|
||||||
id := int32(0)
|
id := int32(0)
|
||||||
parentID := int32(0)
|
parentID := int32(-1)
|
||||||
|
|
||||||
if len(op.Fields) == 0 {
|
if len(op.Fields) == 0 {
|
||||||
return errors.New("invalid graphql no query found")
|
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 {
|
if len(op.Fields) == 0 {
|
||||||
return errors.New("empty query")
|
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 {
|
for {
|
||||||
if st.Len() == 0 {
|
if st.Len() == 0 {
|
||||||
@ -313,11 +321,6 @@ func (com *Compiler) compileQuery(qc *QCode, op *Operation, role string) error {
|
|||||||
s.PresetList = trv.update.pslist
|
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 {
|
if len(field.Alias) != 0 {
|
||||||
s.FieldName = field.Alias
|
s.FieldName = field.Alias
|
||||||
} else {
|
} else {
|
||||||
@ -329,6 +332,15 @@ func (com *Compiler) compileQuery(qc *QCode, op *Operation, role string) error {
|
|||||||
return err
|
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))
|
s.Cols = make([]Column, 0, len(field.Children))
|
||||||
action = QTQuery
|
action = QTQuery
|
||||||
|
|
||||||
@ -362,36 +374,40 @@ func (com *Compiler) compileQuery(qc *QCode, op *Operation, role string) error {
|
|||||||
return errors.New("invalid query")
|
return errors.New("invalid query")
|
||||||
}
|
}
|
||||||
|
|
||||||
var fil *Exp
|
qc.Selects = selects[:id]
|
||||||
root := &selects[0]
|
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)
|
fil = trv.filter(qc.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
if fil != nil {
|
if fil == nil {
|
||||||
switch fil.Op {
|
return
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
qc.Selects = selects[:id]
|
switch fil.Op {
|
||||||
return nil
|
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 {
|
func (com *Compiler) compileArgs(qc *QCode, sel *Select, args []Arg) error {
|
||||||
|
53
serv/args.go
53
serv/args.go
@ -13,25 +13,21 @@ func argMap(ctx *coreContext) func(w io.Writer, tag string) (int, error) {
|
|||||||
switch tag {
|
switch tag {
|
||||||
case "user_id_provider":
|
case "user_id_provider":
|
||||||
if v := ctx.Value(userIDProviderKey); v != nil {
|
if v := ctx.Value(userIDProviderKey); v != nil {
|
||||||
return stringArg(w, v.(string))
|
return io.WriteString(w, v.(string))
|
||||||
}
|
}
|
||||||
io.WriteString(w, "null")
|
return io.WriteString(w, "null")
|
||||||
return 0, nil
|
|
||||||
|
|
||||||
case "user_id":
|
case "user_id":
|
||||||
if v := ctx.Value(userIDKey); v != nil {
|
if v := ctx.Value(userIDKey); v != nil {
|
||||||
return stringArg(w, v.(string))
|
return io.WriteString(w, v.(string))
|
||||||
}
|
}
|
||||||
|
return io.WriteString(w, "null")
|
||||||
io.WriteString(w, "null")
|
|
||||||
return 0, nil
|
|
||||||
|
|
||||||
case "user_role":
|
case "user_role":
|
||||||
if v := ctx.Value(userRoleKey); v != nil {
|
if v := ctx.Value(userRoleKey); v != nil {
|
||||||
return stringArg(w, v.(string))
|
return io.WriteString(w, v.(string))
|
||||||
}
|
}
|
||||||
io.WriteString(w, "null")
|
return io.WriteString(w, "null")
|
||||||
return 0, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fields := jsn.Get(ctx.req.Vars, [][]byte{[]byte(tag)})
|
fields := jsn.Get(ctx.req.Vars, [][]byte{[]byte(tag)})
|
||||||
@ -39,22 +35,7 @@ func argMap(ctx *coreContext) func(w io.Writer, tag string) (int, error) {
|
|||||||
return 0, fmt.Errorf("variable '%s' not found", tag)
|
return 0, fmt.Errorf("variable '%s' not found", tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
is := false
|
return w.Write(fields[0].Value)
|
||||||
|
|
||||||
for i := range fields[0].Value {
|
|
||||||
c := fields[0].Value[i]
|
|
||||||
if c != ' ' {
|
|
||||||
is = (c == '"') || (c == '{') || (c == '[')
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if is {
|
|
||||||
return stringArgB(w, fields[0].Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Write(fields[0].Value)
|
|
||||||
return 0, nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,23 +81,3 @@ func argList(ctx *coreContext, args [][]byte) []interface{} {
|
|||||||
|
|
||||||
return vars
|
return vars
|
||||||
}
|
}
|
||||||
|
|
||||||
func stringArg(w io.Writer, v string) (int, error) {
|
|
||||||
if n, err := w.Write([]byte(`'`)); err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
if n, err := w.Write([]byte(v)); err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
return w.Write([]byte(`'`))
|
|
||||||
}
|
|
||||||
|
|
||||||
func stringArgB(w io.Writer, v []byte) (int, error) {
|
|
||||||
if n, err := w.Write([]byte(`'`)); err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
if n, err := w.Write(v); err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
return w.Write([]byte(`'`))
|
|
||||||
}
|
|
||||||
|
17
serv/cmd.go
17
serv/cmd.go
@ -110,6 +110,13 @@ e.g. db:migrate -+1
|
|||||||
Run: cmdDBSetup,
|
Run: cmdDBSetup,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
rootCmd.AddCommand(&cobra.Command{
|
||||||
|
Use: "db:reset",
|
||||||
|
Short: "Reset database",
|
||||||
|
Long: "This command will drop, create, migrate and seed the database (won't run in production)",
|
||||||
|
Run: cmdDBReset,
|
||||||
|
})
|
||||||
|
|
||||||
rootCmd.AddCommand(&cobra.Command{
|
rootCmd.AddCommand(&cobra.Command{
|
||||||
Use: "new APP-NAME",
|
Use: "new APP-NAME",
|
||||||
Short: "Create a new application",
|
Short: "Create a new application",
|
||||||
@ -276,3 +283,13 @@ func initCompiler() {
|
|||||||
logger.Fatal().Err(err).Msg("failed to initialized resolvers")
|
logger.Fatal().Err(err).Msg("failed to initialized resolvers")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func initConfOnce() {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if conf == nil {
|
||||||
|
if conf, err = initConf(); err != nil {
|
||||||
|
logger.Fatal().Err(err).Msg("failed to read config")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -36,6 +36,7 @@ var newMigrationText = `-- Write your migrate up statements here
|
|||||||
`
|
`
|
||||||
|
|
||||||
func cmdDBSetup(cmd *cobra.Command, args []string) {
|
func cmdDBSetup(cmd *cobra.Command, args []string) {
|
||||||
|
initConfOnce()
|
||||||
cmdDBCreate(cmd, []string{})
|
cmdDBCreate(cmd, []string{})
|
||||||
cmdDBMigrate(cmd, []string{"up"})
|
cmdDBMigrate(cmd, []string{"up"})
|
||||||
|
|
||||||
@ -54,13 +55,19 @@ func cmdDBSetup(cmd *cobra.Command, args []string) {
|
|||||||
logger.Warn().Msgf("failed to read seed file '%s'", sfile)
|
logger.Warn().Msgf("failed to read seed file '%s'", sfile)
|
||||||
}
|
}
|
||||||
|
|
||||||
func cmdDBCreate(cmd *cobra.Command, args []string) {
|
func cmdDBReset(cmd *cobra.Command, args []string) {
|
||||||
var err error
|
initConfOnce()
|
||||||
|
|
||||||
if conf, err = initConf(); err != nil {
|
if conf.Production {
|
||||||
logger.Fatal().Err(err).Msg("failed to read config")
|
logger.Fatal().Msg("db:reset does not work in production")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
cmdDBDrop(cmd, []string{})
|
||||||
|
cmdDBSetup(cmd, []string{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdDBCreate(cmd *cobra.Command, args []string) {
|
||||||
|
initConfOnce()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
conn, err := initDB(conf, false)
|
conn, err := initDB(conf, false)
|
||||||
@ -80,12 +87,7 @@ func cmdDBCreate(cmd *cobra.Command, args []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func cmdDBDrop(cmd *cobra.Command, args []string) {
|
func cmdDBDrop(cmd *cobra.Command, args []string) {
|
||||||
var err error
|
initConfOnce()
|
||||||
|
|
||||||
if conf, err = initConf(); err != nil {
|
|
||||||
logger.Fatal().Err(err).Msg("failed to read config")
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
conn, err := initDB(conf, false)
|
conn, err := initDB(conf, false)
|
||||||
@ -110,12 +112,7 @@ func cmdDBNew(cmd *cobra.Command, args []string) {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
initConfOnce()
|
||||||
|
|
||||||
if conf, err = initConf(); err != nil {
|
|
||||||
logger.Fatal().Err(err).Msg("failed to read config")
|
|
||||||
}
|
|
||||||
|
|
||||||
name := args[0]
|
name := args[0]
|
||||||
|
|
||||||
m, err := migrate.FindMigrations(conf.MigrationsPath)
|
m, err := migrate.FindMigrations(conf.MigrationsPath)
|
||||||
@ -144,19 +141,14 @@ func cmdDBNew(cmd *cobra.Command, args []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func cmdDBMigrate(cmd *cobra.Command, args []string) {
|
func cmdDBMigrate(cmd *cobra.Command, args []string) {
|
||||||
var err error
|
|
||||||
|
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
cmd.Help()
|
cmd.Help()
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initConfOnce()
|
||||||
dest := args[0]
|
dest := args[0]
|
||||||
|
|
||||||
if conf, err = initConf(); err != nil {
|
|
||||||
logger.Fatal().Err(err).Msg("failed to read config")
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, err := initDB(conf, true)
|
conn, err := initDB(conf, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Fatal().Err(err).Msg("failed to connect to database")
|
logger.Fatal().Err(err).Msg("failed to connect to database")
|
||||||
@ -251,11 +243,7 @@ func cmdDBMigrate(cmd *cobra.Command, args []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func cmdDBStatus(cmd *cobra.Command, args []string) {
|
func cmdDBStatus(cmd *cobra.Command, args []string) {
|
||||||
var err error
|
initConfOnce()
|
||||||
|
|
||||||
if conf, err = initConf(); err != nil {
|
|
||||||
logger.Fatal().Err(err).Msg("failed to read config")
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, err := initDB(conf, true)
|
conn, err := initDB(conf, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package serv
|
package serv
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -12,6 +13,7 @@ import (
|
|||||||
"github.com/brianvoe/gofakeit"
|
"github.com/brianvoe/gofakeit"
|
||||||
"github.com/dop251/goja"
|
"github.com/dop251/goja"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/valyala/fasttemplate"
|
||||||
)
|
)
|
||||||
|
|
||||||
func cmdDBSeed(cmd *cobra.Command, args []string) {
|
func cmdDBSeed(cmd *cobra.Command, args []string) {
|
||||||
@ -57,27 +59,82 @@ func cmdDBSeed(cmd *cobra.Command, args []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//func runFunc(call goja.FunctionCall) {
|
//func runFunc(call goja.FunctionCall) {
|
||||||
func graphQLFunc(query string, data interface{}) map[string]interface{} {
|
func graphQLFunc(query string, data interface{}, opt map[string]string) map[string]interface{} {
|
||||||
b, err := json.Marshal(data)
|
b, err := json.Marshal(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Fatal().Err(err).Msg("failed to json serialize")
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
c := &coreContext{Context: context.Background()}
|
ctx := context.Background()
|
||||||
|
|
||||||
|
if v, ok := opt["user_id"]; ok && len(v) != 0 {
|
||||||
|
ctx = context.WithValue(ctx, userIDKey, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
var role string
|
||||||
|
|
||||||
|
if v, ok := opt["role"]; ok && len(v) != 0 {
|
||||||
|
role = v
|
||||||
|
} else {
|
||||||
|
role = "user"
|
||||||
|
}
|
||||||
|
|
||||||
|
c := &coreContext{Context: ctx}
|
||||||
c.req.Query = query
|
c.req.Query = query
|
||||||
c.req.Vars = b
|
c.req.Vars = b
|
||||||
c.req.role = "user"
|
|
||||||
|
|
||||||
res, err := c.execQuery()
|
st, err := c.buildStmtByRole(role)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Fatal().Err(err).Msg("graphql query failed")
|
panic(fmt.Errorf("graphql query failed: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
|
||||||
|
t := fasttemplate.New(st.sql, openVar, closeVar)
|
||||||
|
_, err = t.ExecuteFunc(buf, argMap(c))
|
||||||
|
|
||||||
|
if err == errNoUserID {
|
||||||
|
panic(fmt.Errorf("query requires a user_id"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
finalSQL := buf.String()
|
||||||
|
|
||||||
|
tx, err := db.Begin(c)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer tx.Rollback(c)
|
||||||
|
|
||||||
|
if conf.DB.SetUserID {
|
||||||
|
if err := c.setLocalUserID(tx); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var root []byte
|
||||||
|
|
||||||
|
if err = tx.QueryRow(c, finalSQL).Scan(&root); err != nil {
|
||||||
|
panic(fmt.Errorf("sql query failed: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Commit(c); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := c.execRemoteJoin(st.qc, st.skipped, root)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
val := make(map[string]interface{})
|
val := make(map[string]interface{})
|
||||||
|
|
||||||
err = json.Unmarshal(res, &val)
|
err = json.Unmarshal(res, &val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Fatal().Err(err).Msg("failed to deserialize json")
|
panic(fmt.Errorf("failed to deserialize json: %s", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
return val
|
return val
|
||||||
@ -156,10 +213,9 @@ func setFakeFuncs(f *goja.Object) {
|
|||||||
f.Set("transmission_gear_type", gofakeit.TransmissionGearType)
|
f.Set("transmission_gear_type", gofakeit.TransmissionGearType)
|
||||||
|
|
||||||
// Text
|
// Text
|
||||||
|
|
||||||
f.Set("word", gofakeit.Word)
|
f.Set("word", gofakeit.Word)
|
||||||
f.Set("sentence", gofakeit.Sentence)
|
f.Set("sentence", gofakeit.Sentence)
|
||||||
f.Set("paragrph", gofakeit.Paragraph)
|
f.Set("paragraph", gofakeit.Paragraph)
|
||||||
f.Set("question", gofakeit.Question)
|
f.Set("question", gofakeit.Question)
|
||||||
f.Set("quote", gofakeit.Quote)
|
f.Set("quote", gofakeit.Quote)
|
||||||
|
|
||||||
|
119
serv/core.go
119
serv/core.go
@ -52,8 +52,6 @@ func (c *coreContext) execQuery() ([]byte, error) {
|
|||||||
var qc *qcode.QCode
|
var qc *qcode.QCode
|
||||||
var data []byte
|
var data []byte
|
||||||
|
|
||||||
logger.Debug().Str("role", c.req.role).Msg(c.req.Query)
|
|
||||||
|
|
||||||
if conf.Production {
|
if conf.Production {
|
||||||
var ps *preparedItem
|
var ps *preparedItem
|
||||||
|
|
||||||
@ -73,6 +71,12 @@ func (c *coreContext) execQuery() ([]byte, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return c.execRemoteJoin(qc, skipped, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *coreContext) execRemoteJoin(qc *qcode.QCode, skipped uint32, data []byte) ([]byte, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
if len(data) == 0 || skipped == 0 {
|
if len(data) == 0 || skipped == 0 {
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
@ -116,11 +120,19 @@ func (c *coreContext) execQuery() ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *coreContext) resolvePreparedSQL() ([]byte, *preparedItem, error) {
|
func (c *coreContext) resolvePreparedSQL() ([]byte, *preparedItem, error) {
|
||||||
tx, err := db.Begin(c)
|
var tx pgx.Tx
|
||||||
if err != nil {
|
var err error
|
||||||
return nil, nil, err
|
|
||||||
|
mutation := isMutation(c.req.Query)
|
||||||
|
useRoleQuery := len(conf.RolesQuery) != 0 && mutation
|
||||||
|
useTx := useRoleQuery || conf.DB.SetUserID
|
||||||
|
|
||||||
|
if useTx {
|
||||||
|
if tx, err = db.Begin(c); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
defer tx.Rollback(c)
|
||||||
}
|
}
|
||||||
defer tx.Rollback(c)
|
|
||||||
|
|
||||||
if conf.DB.SetUserID {
|
if conf.DB.SetUserID {
|
||||||
if err := c.setLocalUserID(tx); err != nil {
|
if err := c.setLocalUserID(tx); err != nil {
|
||||||
@ -129,8 +141,6 @@ func (c *coreContext) resolvePreparedSQL() ([]byte, *preparedItem, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var role string
|
var role string
|
||||||
mutation := isMutation(c.req.Query)
|
|
||||||
useRoleQuery := len(conf.RolesQuery) != 0 && mutation
|
|
||||||
|
|
||||||
if useRoleQuery {
|
if useRoleQuery {
|
||||||
if role, err = c.executeRoleQuery(tx); err != nil {
|
if role, err = c.executeRoleQuery(tx); err != nil {
|
||||||
@ -151,33 +161,58 @@ func (c *coreContext) resolvePreparedSQL() ([]byte, *preparedItem, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var root []byte
|
var root []byte
|
||||||
|
var row pgx.Row
|
||||||
vars := argList(c, ps.args)
|
vars := argList(c, ps.args)
|
||||||
|
|
||||||
if mutation {
|
if useTx {
|
||||||
err = tx.QueryRow(c, ps.stmt.SQL, vars...).Scan(&root)
|
row = tx.QueryRow(c, ps.stmt.SQL, vars...)
|
||||||
} else {
|
} else {
|
||||||
err = tx.QueryRow(c, ps.stmt.SQL, vars...).Scan(&c.req.role, &root)
|
row = db.QueryRow(c, ps.stmt.SQL, vars...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if mutation {
|
||||||
|
err = row.Scan(&root)
|
||||||
|
} else {
|
||||||
|
err = row.Scan(&role, &root)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Debug().Str("default_role", c.req.role).Str("role", role).Msg(c.req.Query)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tx.Commit(c); err != nil {
|
c.req.role = role
|
||||||
return nil, nil, err
|
|
||||||
|
if useTx {
|
||||||
|
if err := tx.Commit(c); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return root, ps, nil
|
return root, ps, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *coreContext) resolveSQL() ([]byte, uint32, error) {
|
func (c *coreContext) resolveSQL() ([]byte, uint32, error) {
|
||||||
tx, err := db.Begin(c)
|
var tx pgx.Tx
|
||||||
if err != nil {
|
var err error
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
defer tx.Rollback(c)
|
|
||||||
|
|
||||||
mutation := isMutation(c.req.Query)
|
mutation := isMutation(c.req.Query)
|
||||||
useRoleQuery := len(conf.RolesQuery) != 0 && mutation
|
useRoleQuery := len(conf.RolesQuery) != 0 && mutation
|
||||||
|
useTx := useRoleQuery || conf.DB.SetUserID
|
||||||
|
|
||||||
|
if useTx {
|
||||||
|
if tx, err = db.Begin(c); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
defer tx.Rollback(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
if conf.DB.SetUserID {
|
||||||
|
if err := c.setLocalUserID(tx); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if useRoleQuery {
|
if useRoleQuery {
|
||||||
if c.req.role, err = c.executeRoleQuery(tx); err != nil {
|
if c.req.role, err = c.executeRoleQuery(tx); err != nil {
|
||||||
@ -222,38 +257,42 @@ func (c *coreContext) resolveSQL() ([]byte, uint32, error) {
|
|||||||
stime = time.Now()
|
stime = time.Now()
|
||||||
}
|
}
|
||||||
|
|
||||||
if conf.DB.SetUserID {
|
|
||||||
if err := c.setLocalUserID(tx); err != nil {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var root []byte
|
var root []byte
|
||||||
|
var role, defaultRole string
|
||||||
|
var row pgx.Row
|
||||||
|
|
||||||
|
if useTx {
|
||||||
|
row = tx.QueryRow(c, finalSQL)
|
||||||
|
} else {
|
||||||
|
row = db.QueryRow(c, finalSQL)
|
||||||
|
}
|
||||||
|
|
||||||
if mutation {
|
if mutation {
|
||||||
err = tx.QueryRow(c, finalSQL).Scan(&root)
|
err = row.Scan(&root)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
err = tx.QueryRow(c, finalSQL).Scan(&c.req.role, &root)
|
err = row.Scan(&role, &root)
|
||||||
|
defaultRole = c.req.role
|
||||||
|
c.req.role = role
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.Debug().Str("default_role", defaultRole).Str("role", role).Msg(c.req.Query)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tx.Commit(c); err != nil {
|
if useTx {
|
||||||
return nil, 0, err
|
if err := tx.Commit(c); err != nil {
|
||||||
}
|
return nil, 0, err
|
||||||
|
}
|
||||||
if mutation {
|
|
||||||
st = findStmt(c.req.role, stmts)
|
|
||||||
} else {
|
|
||||||
st = &stmts[0]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if conf.EnableTracing && len(st.qc.Selects) != 0 {
|
if conf.EnableTracing && len(st.qc.Selects) != 0 {
|
||||||
c.addTrace(
|
for _, id := range st.qc.Roots {
|
||||||
st.qc.Selects,
|
c.addTrace(st.qc.Selects, id, stime)
|
||||||
st.qc.Selects[0].ID,
|
}
|
||||||
stime)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if conf.Production == false {
|
if conf.Production == false {
|
||||||
@ -451,7 +490,7 @@ func (c *coreContext) addTrace(sel []qcode.Select, id int32, st time.Time) {
|
|||||||
c.res.Extensions.Tracing.Duration = du
|
c.res.Extensions.Tracing.Duration = du
|
||||||
|
|
||||||
n := 1
|
n := 1
|
||||||
for i := id; i != 0; i = sel[i].ParentID {
|
for i := id; i != -1; i = sel[i].ParentID {
|
||||||
n++
|
n++
|
||||||
}
|
}
|
||||||
path := make([]string, n)
|
path := make([]string, n)
|
||||||
@ -459,7 +498,7 @@ func (c *coreContext) addTrace(sel []qcode.Select, id int32, st time.Time) {
|
|||||||
n--
|
n--
|
||||||
for i := id; ; i = sel[i].ParentID {
|
for i := id; ; i = sel[i].ParentID {
|
||||||
path[n] = sel[i].Table
|
path[n] = sel[i].Table
|
||||||
if sel[i].ID == 0 {
|
if sel[i].ParentID == -1 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
n--
|
n--
|
||||||
|
@ -55,8 +55,11 @@ func (c *coreContext) buildStmt() ([]stmt, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if conf.Production && role.Name == "anon" {
|
if conf.Production && role.Name == "anon" {
|
||||||
if _, ok := role.tablesMap[qc.Selects[0].Table]; !ok {
|
for _, id := range qc.Roots {
|
||||||
continue
|
root := qc.Selects[id]
|
||||||
|
if _, ok := role.tablesMap[root.Table]; !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,3 +150,39 @@ func (c *coreContext) buildStmt() ([]stmt, error) {
|
|||||||
|
|
||||||
return stmts, nil
|
return stmts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *coreContext) buildStmtByRole(role string) (stmt, error) {
|
||||||
|
var st stmt
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if len(role) == 0 {
|
||||||
|
return st, errors.New(`no role defined`)
|
||||||
|
}
|
||||||
|
|
||||||
|
var vars map[string]json.RawMessage
|
||||||
|
|
||||||
|
if len(c.req.Vars) != 0 {
|
||||||
|
if err := json.Unmarshal(c.req.Vars, &vars); err != nil {
|
||||||
|
return st, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gql := []byte(c.req.Query)
|
||||||
|
|
||||||
|
st.qc, err = qcompile.Compile(gql, role)
|
||||||
|
if err != nil {
|
||||||
|
return st, err
|
||||||
|
}
|
||||||
|
|
||||||
|
w := &bytes.Buffer{}
|
||||||
|
|
||||||
|
st.skipped, err = pcompile.Compile(st.qc, w, psql.Variables(vars))
|
||||||
|
if err != nil {
|
||||||
|
return st, err
|
||||||
|
}
|
||||||
|
|
||||||
|
st.sql = w.String()
|
||||||
|
|
||||||
|
return st, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/dosco/super-graph/qcode"
|
"github.com/dosco/super-graph/qcode"
|
||||||
"github.com/jackc/pgconn"
|
"github.com/jackc/pgconn"
|
||||||
|
"github.com/jackc/pgx/v4"
|
||||||
"github.com/valyala/fasttemplate"
|
"github.com/valyala/fasttemplate"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -24,22 +25,35 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func initPreparedList() {
|
func initPreparedList() {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
tx, err := db.Begin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatal().Err(err).Send()
|
||||||
|
}
|
||||||
|
defer tx.Rollback(ctx)
|
||||||
|
|
||||||
_preparedList = make(map[string]*preparedItem)
|
_preparedList = make(map[string]*preparedItem)
|
||||||
|
|
||||||
if err := prepareRoleStmt(); err != nil {
|
if err := prepareRoleStmt(ctx, tx); err != nil {
|
||||||
logger.Fatal().Err(err).Msg("failed to prepare get role statement")
|
logger.Fatal().Err(err).Msg("failed to prepare get role statement")
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, v := range _allowList.list {
|
for _, v := range _allowList.list {
|
||||||
|
err := prepareStmt(ctx, tx, v.gql, v.vars)
|
||||||
err := prepareStmt(v.gql, v.vars)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn().Str("gql", v.gql).Err(err).Send()
|
logger.Warn().Str("gql", v.gql).Err(err).Send()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := tx.Commit(ctx); err != nil {
|
||||||
|
logger.Fatal().Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info().Msgf("Registered %d queries from allow.list as prepared statements", len(_allowList.list))
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareStmt(gql string, varBytes json.RawMessage) error {
|
func prepareStmt(ctx context.Context, tx pgx.Tx, gql string, varBytes json.RawMessage) error {
|
||||||
if len(gql) == 0 {
|
if len(gql) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -64,15 +78,7 @@ func prepareStmt(gql string, varBytes json.RawMessage) error {
|
|||||||
|
|
||||||
finalSQL, am := processTemplate(s.sql)
|
finalSQL, am := processTemplate(s.sql)
|
||||||
|
|
||||||
ctx := context.Background()
|
pstmt, err := tx.Prepare(c.Context, "", finalSQL)
|
||||||
|
|
||||||
tx, err := db.Begin(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer tx.Rollback(ctx)
|
|
||||||
|
|
||||||
pstmt, err := tx.Prepare(ctx, "", finalSQL)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -92,15 +98,12 @@ func prepareStmt(gql string, varBytes json.RawMessage) error {
|
|||||||
qc: s.qc,
|
qc: s.qc,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tx.Commit(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareRoleStmt() error {
|
func prepareRoleStmt(ctx context.Context, tx pgx.Tx) error {
|
||||||
if len(conf.RolesQuery) == 0 {
|
if len(conf.RolesQuery) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -125,15 +128,7 @@ func prepareRoleStmt() error {
|
|||||||
|
|
||||||
roleSQL, _ := processTemplate(w.String())
|
roleSQL, _ := processTemplate(w.String())
|
||||||
|
|
||||||
ctx := context.Background()
|
_, err := tx.Prepare(ctx, "_sg_get_role", roleSQL)
|
||||||
|
|
||||||
tx, err := db.Begin(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer tx.Rollback(ctx)
|
|
||||||
|
|
||||||
_, err = tx.Prepare(ctx, "_sg_get_role", roleSQL)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -142,19 +137,31 @@ func prepareRoleStmt() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func processTemplate(tmpl string) (string, [][]byte) {
|
func processTemplate(tmpl string) (string, [][]byte) {
|
||||||
t := fasttemplate.New(tmpl, `{{`, `}}`)
|
st := struct {
|
||||||
am := make([][]byte, 0, 5)
|
vmap map[string]int
|
||||||
i := 0
|
am [][]byte
|
||||||
|
i int
|
||||||
|
}{
|
||||||
|
vmap: make(map[string]int),
|
||||||
|
am: make([][]byte, 0, 5),
|
||||||
|
i: 0,
|
||||||
|
}
|
||||||
|
|
||||||
vmap := make(map[string]int)
|
execFunc := func(w io.Writer, tag string) (int, error) {
|
||||||
|
if n, ok := st.vmap[tag]; ok {
|
||||||
return t.ExecuteFuncString(func(w io.Writer, tag string) (int, error) {
|
|
||||||
if n, ok := vmap[tag]; ok {
|
|
||||||
return w.Write([]byte(fmt.Sprintf("$%d", n)))
|
return w.Write([]byte(fmt.Sprintf("$%d", n)))
|
||||||
}
|
}
|
||||||
am = append(am, []byte(tag))
|
st.am = append(st.am, []byte(tag))
|
||||||
i++
|
st.i++
|
||||||
vmap[tag] = i
|
st.vmap[tag] = st.i
|
||||||
return w.Write([]byte(fmt.Sprintf("$%d", i)))
|
return w.Write([]byte(fmt.Sprintf("$%d", st.i)))
|
||||||
}), am
|
}
|
||||||
|
|
||||||
|
t1 := fasttemplate.New(tmpl, `'{{`, `}}'`)
|
||||||
|
ts1 := t1.ExecuteFuncString(execFunc)
|
||||||
|
|
||||||
|
t2 := fasttemplate.New(ts1, `{{`, `}}`)
|
||||||
|
ts2 := t2.ExecuteFuncString(execFunc)
|
||||||
|
|
||||||
|
return ts2, st.am
|
||||||
}
|
}
|
||||||
|
@ -77,7 +77,7 @@ SQL Output
|
|||||||
|
|
||||||
database:
|
database:
|
||||||
variables:
|
variables:
|
||||||
account_id: "select account_id from users where id = $user_id"
|
admin_account_id: "5"
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
Filters: ["{ user_id: { eq: $user_id } }"]
|
Filters: ["{ user_id: { eq: $user_id } }"]
|
||||||
|
14
tmpl/dev.yml
14
tmpl/dev.yml
@ -3,7 +3,7 @@ host_port: 0.0.0.0:8080
|
|||||||
web_ui: true
|
web_ui: true
|
||||||
|
|
||||||
# debug, info, warn, error, fatal, panic
|
# debug, info, warn, error, fatal, panic
|
||||||
log_level: "debug"
|
log_level: "info"
|
||||||
|
|
||||||
# When production mode is 'true' only queries
|
# When production mode is 'true' only queries
|
||||||
# from the allow list are permitted.
|
# from the allow list are permitted.
|
||||||
@ -97,10 +97,9 @@ database:
|
|||||||
# Enable this if you need the user id in triggers, etc
|
# Enable this if you need the user id in triggers, etc
|
||||||
set_user_id: false
|
set_user_id: false
|
||||||
|
|
||||||
# Define variables here that you want to use in filters
|
# Define additional variables here to be used with filters
|
||||||
# sub-queries must be wrapped in ()
|
|
||||||
variables:
|
variables:
|
||||||
account_id: "(select account_id from users where id = $user_id)"
|
admin_account_id: "5"
|
||||||
|
|
||||||
# Define defaults to for the field key and values below
|
# Define defaults to for the field key and values below
|
||||||
defaults:
|
defaults:
|
||||||
@ -185,11 +184,10 @@ roles:
|
|||||||
- updated_at: "now"
|
- updated_at: "now"
|
||||||
|
|
||||||
delete:
|
delete:
|
||||||
deny: true
|
block: true
|
||||||
|
|
||||||
- name: admin
|
- name: admin
|
||||||
match: id = 1
|
match: id = 1000
|
||||||
tables:
|
tables:
|
||||||
- name: users
|
- name: users
|
||||||
# query:
|
filters: []
|
||||||
# filters: ["{ account_id: { _eq: $account_id } }"]
|
|
||||||
|
Reference in New Issue
Block a user