Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
2d466bfb12 | |||
a0b8907c3c | |||
8097ca3b8f | |||
0e498b0e94 |
@ -137,7 +137,7 @@
|
|||||||
</h1>
|
</h1>
|
||||||
<div class="text-2xl md:text-3xl">
|
<div class="text-2xl md:text-3xl">
|
||||||
<small class="text-sm">Download the Docker compose config for the demo</small>
|
<small class="text-sm">Download the Docker compose config for the demo</small>
|
||||||
<pre>‣ curl -L -o demo.yml https://bit.ly/2mq05lW</pre>
|
<pre>‣ curl -L -o demo.yml https://bit.ly/2FZS0uw</pre>
|
||||||
|
|
||||||
<small class="text-sm">Setup the demo database</small>
|
<small class="text-sm">Setup the demo database</small>
|
||||||
<pre>‣ docker-compose -f demo.yml run rails_app rake db:create db:migrate db:seed</pre>
|
<pre>‣ docker-compose -f demo.yml run rails_app rake db:create db:migrate db:seed</pre>
|
||||||
|
@ -31,14 +31,14 @@ Super Graph has a rich feature set like integrating with your existing Ruby on R
|
|||||||
## Try the demo app
|
## Try the demo app
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# download the Docker compose config for the demo
|
# clone the repository
|
||||||
curl -L -o demo.yml https://bit.ly/2mq05lW
|
git clone https://github.com/dosco/super-graph
|
||||||
|
|
||||||
# setup the demo rails app & database and run it
|
# setup the demo rails app & database and run it
|
||||||
docker-compose -f demo.yml run rails_app rake db:create db:migrate db:seed
|
docker-compose run rails_app rake db:create db:migrate db:seed
|
||||||
|
|
||||||
# run the demo
|
# run the demo
|
||||||
docker-compose -f demo.yml up
|
docker-compose up
|
||||||
|
|
||||||
# signin to the demo app (user1@demo.com / 123456)
|
# signin to the demo app (user1@demo.com / 123456)
|
||||||
open http://localhost:3000
|
open http://localhost:3000
|
||||||
|
9
jsn/bench.0
Normal file
9
jsn/bench.0
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
goos: darwin
|
||||||
|
goarch: amd64
|
||||||
|
pkg: github.com/dosco/super-graph/jsn
|
||||||
|
BenchmarkGet-8 13310 88437 ns/op 3328 B/op 2 allocs/op
|
||||||
|
BenchmarkFilter-8 182232 6922 ns/op 448 B/op 1 allocs/op
|
||||||
|
BenchmarkStrip-8 162709 6560 ns/op 224 B/op 1 allocs/op
|
||||||
|
BenchmarkReplace-8 85846 13996 ns/op 416 B/op 1 allocs/op
|
||||||
|
PASS
|
||||||
|
ok github.com/dosco/super-graph/jsn 5.913s
|
14
jsn/get.go
14
jsn/get.go
@ -130,6 +130,20 @@ func Get(b []byte, keys [][]byte) []Field {
|
|||||||
n++
|
n++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if state == expectListClose {
|
||||||
|
loop:
|
||||||
|
for j := i + 1; j < len(b); j++ {
|
||||||
|
switch b[j] {
|
||||||
|
case ' ', '\t', '\n':
|
||||||
|
continue
|
||||||
|
case '{':
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
i = e
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
state = expectKey
|
state = expectKey
|
||||||
e = 0
|
e = 0
|
||||||
}
|
}
|
||||||
|
@ -9,11 +9,11 @@ var (
|
|||||||
input1 = `
|
input1 = `
|
||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
"test": { "__twitter_id": "ABCD" },
|
"test_1a": { "__twitter_id": "ABCD" },
|
||||||
"users": [
|
"users": [
|
||||||
{
|
{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"full_name": "'Sidney Stroman'",
|
"full_name": "'Sidney St[1]roman'",
|
||||||
"email": "user0@demo.com",
|
"email": "user0@demo.com",
|
||||||
"__twitter_id": "2048666903444506956",
|
"__twitter_id": "2048666903444506956",
|
||||||
"embed": {
|
"embed": {
|
||||||
@ -108,7 +108,7 @@ var (
|
|||||||
input2 = `
|
input2 = `
|
||||||
[{
|
[{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"full_name": "Sidney Stroman",
|
"full_name": "Sidney St[1]roman",
|
||||||
"email": "user0@demo.com",
|
"email": "user0@demo.com",
|
||||||
"__twitter_id": "2048666903444506956",
|
"__twitter_id": "2048666903444506956",
|
||||||
"something": null,
|
"something": null,
|
||||||
@ -130,7 +130,7 @@ var (
|
|||||||
input3 = `
|
input3 = `
|
||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
"test": { "__twitter_id": "ABCD" },
|
"test_1a": { "__twitter_id": "ABCD" },
|
||||||
"users": [{"id":1,"embed":{"id":8}},{"id":2},{"id":3},{"id":4},{"id":5},{"id":6},{"id":7},{"id":8},{"id":9},{"id":10},{"id":11},{"id":12},{"id":13}]
|
"users": [{"id":1,"embed":{"id":8}},{"id":2},{"id":3},{"id":4},{"id":5},{"id":6},{"id":7},{"id":8},{"id":9},{"id":10},{"id":11},{"id":12},{"id":13}]
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
@ -138,7 +138,7 @@ var (
|
|||||||
input4 = `
|
input4 = `
|
||||||
{ "users" : [{
|
{ "users" : [{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"full_name": "Sidney Stroman",
|
"full_name": "Sidney St[1]roman",
|
||||||
"email": "user0@demo.com",
|
"email": "user0@demo.com",
|
||||||
"__twitter_id": "2048666903444506956",
|
"__twitter_id": "2048666903444506956",
|
||||||
"embed": {
|
"embed": {
|
||||||
@ -155,24 +155,26 @@ var (
|
|||||||
"email": "user1@demo.com",
|
"email": "user1@demo.com",
|
||||||
"__twitter_id": [{ "name": "hello" }, { "name": "world"}]
|
"__twitter_id": [{ "name": "hello" }, { "name": "world"}]
|
||||||
}] }`
|
}] }`
|
||||||
|
|
||||||
|
input5 = `
|
||||||
|
{"data":{"title":"In September 2018, Slovak police stated that Kuciak was murdered because of his investigative work, and that the murder had been ordered.[9][10] They arrested eight suspects,[11] charging three of them with first-degree murder.[11]","topics":["cpp"]},"a":["1111"]},"thread_slug":"in-september-2018-slovak-police-stated-that-kuciak-7929",}`
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGet(t *testing.T) {
|
func TestGet(t *testing.T) {
|
||||||
values := Get([]byte(input1), [][]byte{
|
values := Get([]byte(input1), [][]byte{
|
||||||
|
[]byte("test_1a"),
|
||||||
[]byte("__twitter_id"),
|
[]byte("__twitter_id"),
|
||||||
[]byte("work_email"),
|
[]byte("work_email"),
|
||||||
})
|
})
|
||||||
|
|
||||||
expected := []Field{
|
expected := []Field{
|
||||||
|
{[]byte("test_1a"), []byte(`{ "__twitter_id": "ABCD" }`)},
|
||||||
{[]byte("__twitter_id"), []byte(`"ABCD"`)},
|
{[]byte("__twitter_id"), []byte(`"ABCD"`)},
|
||||||
{[]byte("__twitter_id"), []byte(`"2048666903444506956"`)},
|
{[]byte("__twitter_id"), []byte(`"2048666903444506956"`)},
|
||||||
{[]byte("__twitter_id"), []byte(`"ABC123"`)},
|
{[]byte("__twitter_id"), []byte(`"ABC123"`)},
|
||||||
{[]byte("__twitter_id"), []byte(`"more123"`)},
|
{[]byte("__twitter_id"), []byte(`"more123"`)},
|
||||||
{[]byte("__twitter_id"),
|
{[]byte("__twitter_id"), []byte(`[{ "name": "hello" }, { "name": "world"}]`)},
|
||||||
[]byte(`[{ "name": "hello" }, { "name": "world"}]`)},
|
{[]byte("__twitter_id"), []byte(`{ "name": "\"hellos\"", "address": { "work": "1 infinity loop" } }`)},
|
||||||
{[]byte("__twitter_id"),
|
|
||||||
[]byte(`{ "name": "\"hellos\"", "address": { "work": "1 infinity loop" } }`),
|
|
||||||
},
|
|
||||||
{[]byte("__twitter_id"), []byte(`1234567890`)},
|
{[]byte("__twitter_id"), []byte(`1234567890`)},
|
||||||
{[]byte("__twitter_id"), []byte(`1.23E`)},
|
{[]byte("__twitter_id"), []byte(`1.23E`)},
|
||||||
{[]byte("__twitter_id"), []byte(`true`)},
|
{[]byte("__twitter_id"), []byte(`true`)},
|
||||||
@ -201,6 +203,30 @@ func TestGet(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGet1(t *testing.T) {
|
||||||
|
values := Get([]byte(input5), [][]byte{
|
||||||
|
[]byte("thread_slug"),
|
||||||
|
})
|
||||||
|
|
||||||
|
expected := []Field{
|
||||||
|
{[]byte("thread_slug"), []byte(`"in-september-2018-slovak-police-stated-that-kuciak-7929"`)},
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(values) != len(expected) {
|
||||||
|
t.Fatal("len(values) != len(expected)")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range expected {
|
||||||
|
if !bytes.Equal(values[i].Key, expected[i].Key) {
|
||||||
|
t.Error(string(values[i].Key), " != ", string(expected[i].Key))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(values[i].Value, expected[i].Value) {
|
||||||
|
t.Error(string(values[i].Value), " != ", string(expected[i].Value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestValue(t *testing.T) {
|
func TestValue(t *testing.T) {
|
||||||
v1 := []byte("12345")
|
v1 := []byte("12345")
|
||||||
if !bytes.Equal(Value(v1), v1) {
|
if !bytes.Equal(Value(v1), v1) {
|
||||||
@ -230,7 +256,7 @@ func TestFilter1(t *testing.T) {
|
|||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := `[{"id": 1,"full_name": "Sidney Stroman","embed": {"id": 8,"full_name": "Caroll Orn Sr.","email": "joannarau@hegmann.io","__twitter_id": "ABC123"}},{"id": 2,"full_name": "Jerry Dickinson"}]`
|
expected := `[{"id": 1,"full_name": "Sidney St[1]roman","embed": {"id": 8,"full_name": "Caroll Orn Sr.","email": "joannarau@hegmann.io","__twitter_id": "ABC123"}},{"id": 2,"full_name": "Jerry Dickinson"}]`
|
||||||
|
|
||||||
if b.String() != expected {
|
if b.String() != expected {
|
||||||
t.Error("Does not match expected json")
|
t.Error("Does not match expected json")
|
||||||
@ -306,7 +332,7 @@ func TestReplace(t *testing.T) {
|
|||||||
|
|
||||||
expected := `{ "users" : [{
|
expected := `{ "users" : [{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"full_name": "Sidney Stroman",
|
"full_name": "Sidney St[1]roman",
|
||||||
"email": "user0@demo.com",
|
"email": "user0@demo.com",
|
||||||
"__twitter_id": "2048666903444506956",
|
"__twitter_id": "2048666903444506956",
|
||||||
"embed": {
|
"embed": {
|
||||||
@ -338,7 +364,7 @@ func TestReplace(t *testing.T) {
|
|||||||
func TestReplaceEmpty(t *testing.T) {
|
func TestReplaceEmpty(t *testing.T) {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
|
|
||||||
json := `{ "users" : [{"id":1,"full_name":"Sidney Stroman","email":"user0@demo.com","__users_twitter_id":"2048666903444506956"}, {"id":2,"full_name":"Jerry Dickinson","email":"user1@demo.com","__users_twitter_id":"2048666903444506956"}, {"id":3,"full_name":"Kenna Cassin","email":"user2@demo.com","__users_twitter_id":"2048666903444506956"}, {"id":4,"full_name":"Mr. Pat Parisian","email":"rodney@kautzer.biz","__users_twitter_id":"2048666903444506956"}, {"id":5,"full_name":"Bette Ebert","email":"janeenrath@goyette.com","__users_twitter_id":"2048666903444506956"}, {"id":6,"full_name":"Everett Kiehn","email":"michael@bartoletti.com","__users_twitter_id":"2048666903444506956"}, {"id":7,"full_name":"Katrina Cronin","email":"loretaklocko@framivolkman.org","__users_twitter_id":"2048666903444506956"}, {"id":8,"full_name":"Caroll Orn Sr.","email":"joannarau@hegmann.io","__users_twitter_id":"2048666903444506956"}, {"id":9,"full_name":"Gwendolyn Ziemann","email":"renaytoy@rutherford.co","__users_twitter_id":"2048666903444506956"}, {"id":10,"full_name":"Mrs. Rosann Fritsch","email":"holliemosciski@thiel.org","__users_twitter_id":"2048666903444506956"}, {"id":11,"full_name":"Arden Koss","email":"cristobalankunding@howewelch.org","__users_twitter_id":"2048666903444506956"}, {"id":12,"full_name":"Brenton Bauch PhD","email":"renee@miller.co","__users_twitter_id":"2048666903444506956"}, {"id":13,"full_name":"Daine Gleichner","email":"andrea@nienow.co","__users_twitter_id":"2048666903444506956"}] }`
|
json := `{ "users" : [{"id":1,"full_name":"Sidney St[1]roman","email":"user0@demo.com","__users_twitter_id":"2048666903444506956"}, {"id":2,"full_name":"Jerry Dickinson","email":"user1@demo.com","__users_twitter_id":"2048666903444506956"}, {"id":3,"full_name":"Kenna Cassin","email":"user2@demo.com","__users_twitter_id":"2048666903444506956"}, {"id":4,"full_name":"Mr. Pat Parisian","email":"rodney@kautzer.biz","__users_twitter_id":"2048666903444506956"}, {"id":5,"full_name":"Bette Ebert","email":"janeenrath@goyette.com","__users_twitter_id":"2048666903444506956"}, {"id":6,"full_name":"Everett Kiehn","email":"michael@bartoletti.com","__users_twitter_id":"2048666903444506956"}, {"id":7,"full_name":"Katrina Cronin","email":"loretaklocko@framivolkman.org","__users_twitter_id":"2048666903444506956"}, {"id":8,"full_name":"Caroll Orn Sr.","email":"joannarau@hegmann.io","__users_twitter_id":"2048666903444506956"}, {"id":9,"full_name":"Gwendolyn Ziemann","email":"renaytoy@rutherford.co","__users_twitter_id":"2048666903444506956"}, {"id":10,"full_name":"Mrs. Rosann Fritsch","email":"holliemosciski@thiel.org","__users_twitter_id":"2048666903444506956"}, {"id":11,"full_name":"Arden Koss","email":"cristobalankunding@howewelch.org","__users_twitter_id":"2048666903444506956"}, {"id":12,"full_name":"Brenton Bauch PhD","email":"renee@miller.co","__users_twitter_id":"2048666903444506956"}, {"id":13,"full_name":"Daine Gleichner","email":"andrea@nienow.co","__users_twitter_id":"2048666903444506956"}] }`
|
||||||
|
|
||||||
err := Replace(&buf, []byte(json), []Field{}, []Field{})
|
err := Replace(&buf, []byte(json), []Field{}, []Field{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -395,7 +421,7 @@ func TestKeys3(t *testing.T) {
|
|||||||
json := `{
|
json := `{
|
||||||
"insert": {
|
"insert": {
|
||||||
"created_at": "now",
|
"created_at": "now",
|
||||||
"test": { "type1": "a", "type2": "b" },
|
"test_1a": { "type1": "a", "type2": "b" },
|
||||||
"name": "Hello",
|
"name": "Hello",
|
||||||
"updated_at": "now",
|
"updated_at": "now",
|
||||||
"description": "World"
|
"description": "World"
|
||||||
@ -406,7 +432,7 @@ func TestKeys3(t *testing.T) {
|
|||||||
fields := Keys([]byte(json))
|
fields := Keys([]byte(json))
|
||||||
|
|
||||||
exp := []string{
|
exp := []string{
|
||||||
"insert", "created_at", "test", "type1", "type2", "name", "updated_at", "description",
|
"insert", "created_at", "test_1a", "type1", "type2", "name", "updated_at", "description",
|
||||||
"user",
|
"user",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
13
jsn/keys.go
13
jsn/keys.go
@ -111,6 +111,19 @@ func Keys(b []byte) [][]byte {
|
|||||||
res = append(res, k)
|
res = append(res, k)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if state == expectListClose {
|
||||||
|
loop:
|
||||||
|
for j := i + 1; j < len(b); j++ {
|
||||||
|
switch b[j] {
|
||||||
|
case ' ', '\t', '\n':
|
||||||
|
continue
|
||||||
|
case '{':
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
i = e
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
}
|
||||||
state = expectKey
|
state = expectKey
|
||||||
k = nil
|
k = nil
|
||||||
e = 0
|
e = 0
|
||||||
|
@ -82,17 +82,21 @@ func (co *Compiler) compileQuery(qc *qcode.QCode, w io.Writer) (uint32, error) {
|
|||||||
multiRoot := (len(qc.Roots) > 1)
|
multiRoot := (len(qc.Roots) > 1)
|
||||||
|
|
||||||
st := NewIntStack()
|
st := NewIntStack()
|
||||||
|
si := 0
|
||||||
|
|
||||||
if multiRoot {
|
if multiRoot {
|
||||||
io.WriteString(c.w, `SELECT row_to_json("json_root") FROM (SELECT `)
|
io.WriteString(c.w, `SELECT row_to_json("json_root") FROM (SELECT `)
|
||||||
|
|
||||||
for i, id := range qc.Roots {
|
for _, id := range qc.Roots {
|
||||||
root := qc.Selects[id]
|
root := qc.Selects[id]
|
||||||
|
if root.SkipRender {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
st.Push(root.ID + closeBlock)
|
st.Push(root.ID + closeBlock)
|
||||||
st.Push(root.ID)
|
st.Push(root.ID)
|
||||||
|
|
||||||
if i != 0 {
|
if si != 0 {
|
||||||
io.WriteString(c.w, `, `)
|
io.WriteString(c.w, `, `)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,24 +107,34 @@ func (co *Compiler) compileQuery(qc *qcode.QCode, w io.Writer) (uint32, error) {
|
|||||||
io.WriteString(c.w, `"`)
|
io.WriteString(c.w, `"`)
|
||||||
|
|
||||||
alias(c.w, root.FieldName)
|
alias(c.w, root.FieldName)
|
||||||
|
si++
|
||||||
}
|
}
|
||||||
|
|
||||||
io.WriteString(c.w, ` FROM `)
|
if si != 0 {
|
||||||
|
io.WriteString(c.w, ` FROM `)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
root := qc.Selects[0]
|
root := qc.Selects[0]
|
||||||
|
if !root.SkipRender {
|
||||||
|
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)
|
||||||
|
|
||||||
io.WriteString(c.w, `SELECT json_object_agg(`)
|
st.Push(root.ID + closeBlock)
|
||||||
io.WriteString(c.w, `'`)
|
st.Push(root.ID)
|
||||||
io.WriteString(c.w, root.FieldName)
|
|
||||||
io.WriteString(c.w, `', `)
|
|
||||||
io.WriteString(c.w, `json_`)
|
|
||||||
int2string(c.w, root.ID)
|
|
||||||
|
|
||||||
st.Push(root.ID + closeBlock)
|
io.WriteString(c.w, `) FROM `)
|
||||||
st.Push(root.ID)
|
si++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
io.WriteString(c.w, `) FROM `)
|
if si == 0 {
|
||||||
|
return 0, errors.New("all tables skipped. cannot render query")
|
||||||
}
|
}
|
||||||
|
|
||||||
var ignored uint32
|
var ignored uint32
|
||||||
@ -161,6 +175,9 @@ func (co *Compiler) compileQuery(qc *qcode.QCode, w io.Writer) (uint32, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
child := &c.s[cid]
|
child := &c.s[cid]
|
||||||
|
if child.SkipRender {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
st.Push(child.ID + closeBlock)
|
st.Push(child.ID + closeBlock)
|
||||||
st.Push(child.ID)
|
st.Push(child.ID)
|
||||||
@ -217,6 +234,10 @@ func (c *compilerContext) processChildren(sel *qcode.Select, ti *DBTableInfo) (u
|
|||||||
colmap[sel.Cols[i].Name] = struct{}{}
|
colmap[sel.Cols[i].Name] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for i := range sel.OrderBy {
|
||||||
|
colmap[sel.OrderBy[i].Col] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
for _, id := range sel.Children {
|
for _, id := range sel.Children {
|
||||||
child := &c.s[id]
|
child := &c.s[id]
|
||||||
|
|
||||||
@ -471,18 +492,22 @@ func (c *compilerContext) renderRemoteRelColumns(sel *qcode.Select, ti *DBTableI
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *compilerContext) renderJoinedColumns(sel *qcode.Select, ti *DBTableInfo, skipped uint32) error {
|
func (c *compilerContext) renderJoinedColumns(sel *qcode.Select, ti *DBTableInfo, skipped uint32) error {
|
||||||
colsRendered := len(sel.Cols) != 0
|
|
||||||
|
// columns previously rendered
|
||||||
|
i := len(sel.Cols)
|
||||||
|
|
||||||
for _, id := range sel.Children {
|
for _, id := range sel.Children {
|
||||||
skipThis := hasBit(skipped, uint32(id))
|
if hasBit(skipped, uint32(id)) {
|
||||||
|
|
||||||
if colsRendered && !skipThis {
|
|
||||||
io.WriteString(c.w, ", ")
|
|
||||||
}
|
|
||||||
if skipThis {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
childSel := &c.s[id]
|
childSel := &c.s[id]
|
||||||
|
if childSel.SkipRender {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if i != 0 {
|
||||||
|
io.WriteString(c.w, ", ")
|
||||||
|
}
|
||||||
|
|
||||||
//fmt.Fprintf(w, `"%s_%d_join"."%s" AS "%s"`,
|
//fmt.Fprintf(w, `"%s_%d_join"."%s" AS "%s"`,
|
||||||
//s.Name, s.ID, s.Name, s.FieldName)
|
//s.Name, s.ID, s.Name, s.FieldName)
|
||||||
@ -496,6 +521,7 @@ func (c *compilerContext) renderJoinedColumns(sel *qcode.Select, ti *DBTableInfo
|
|||||||
io.WriteString(c.w, `" AS "`)
|
io.WriteString(c.w, `" AS "`)
|
||||||
io.WriteString(c.w, childSel.FieldName)
|
io.WriteString(c.w, childSel.FieldName)
|
||||||
io.WriteString(c.w, `"`)
|
io.WriteString(c.w, `"`)
|
||||||
|
i++
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -510,11 +536,14 @@ func (c *compilerContext) renderBaseSelect(sel *qcode.Select, ti *DBTableInfo,
|
|||||||
isSearch := sel.Args["search"] != nil
|
isSearch := sel.Args["search"] != nil
|
||||||
isAgg := false
|
isAgg := false
|
||||||
|
|
||||||
|
colmap := make(map[string]struct{}, (len(sel.Cols) + len(sel.OrderBy)))
|
||||||
|
|
||||||
io.WriteString(c.w, ` FROM (SELECT `)
|
io.WriteString(c.w, ` FROM (SELECT `)
|
||||||
|
|
||||||
i := 0
|
i := 0
|
||||||
for n, col := range sel.Cols {
|
for n, col := range sel.Cols {
|
||||||
cn := col.Name
|
cn := col.Name
|
||||||
|
colmap[cn] = struct{}{}
|
||||||
|
|
||||||
_, isRealCol := ti.ColMap[cn]
|
_, isRealCol := ti.ColMap[cn]
|
||||||
|
|
||||||
@ -625,7 +654,23 @@ func (c *compilerContext) renderBaseSelect(sel *qcode.Select, ti *DBTableInfo,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, ob := range sel.OrderBy {
|
||||||
|
if _, ok := colmap[ob.Col]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
colmap[ob.Col] = struct{}{}
|
||||||
|
|
||||||
|
if i != 0 {
|
||||||
|
io.WriteString(c.w, `, `)
|
||||||
|
}
|
||||||
|
colWithTable(c.w, ti.Name, ob.Col)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
for _, col := range childCols {
|
for _, col := range childCols {
|
||||||
|
if _, ok := colmap[col.Name]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if i != 0 {
|
if i != 0 {
|
||||||
io.WriteString(c.w, `, `)
|
io.WriteString(c.w, `, `)
|
||||||
}
|
}
|
||||||
@ -1062,27 +1107,27 @@ func (c *compilerContext) renderOrderBy(sel *qcode.Select, ti *DBTableInfo) erro
|
|||||||
switch ob.Order {
|
switch ob.Order {
|
||||||
case qcode.OrderAsc:
|
case qcode.OrderAsc:
|
||||||
//fmt.Fprintf(w, `"%s_%d.ob.%s" ASC`, sel.Name, sel.ID, ob.Col)
|
//fmt.Fprintf(w, `"%s_%d.ob.%s" ASC`, sel.Name, sel.ID, ob.Col)
|
||||||
tableIDColSuffix(c.w, ti.Name, sel.ID, ob.Col, "_ob")
|
tableIDColSuffix(c.w, sel.Name, sel.ID, ob.Col, "_ob")
|
||||||
io.WriteString(c.w, ` ASC`)
|
io.WriteString(c.w, ` ASC`)
|
||||||
case qcode.OrderDesc:
|
case qcode.OrderDesc:
|
||||||
//fmt.Fprintf(w, `"%s_%d.ob.%s" DESC`, sel.Name, sel.ID, ob.Col)
|
//fmt.Fprintf(w, `"%s_%d.ob.%s" DESC`, sel.Name, sel.ID, ob.Col)
|
||||||
tableIDColSuffix(c.w, ti.Name, sel.ID, ob.Col, "_ob")
|
tableIDColSuffix(c.w, sel.Name, sel.ID, ob.Col, "_ob")
|
||||||
io.WriteString(c.w, ` DESC`)
|
io.WriteString(c.w, ` DESC`)
|
||||||
case qcode.OrderAscNullsFirst:
|
case qcode.OrderAscNullsFirst:
|
||||||
//fmt.Fprintf(w, `"%s_%d.ob.%s" ASC NULLS FIRST`, sel.Name, sel.ID, ob.Col)
|
//fmt.Fprintf(w, `"%s_%d.ob.%s" ASC NULLS FIRST`, sel.Name, sel.ID, ob.Col)
|
||||||
tableIDColSuffix(c.w, ti.Name, sel.ID, ob.Col, "_ob")
|
tableIDColSuffix(c.w, sel.Name, sel.ID, ob.Col, "_ob")
|
||||||
io.WriteString(c.w, ` ASC NULLS FIRST`)
|
io.WriteString(c.w, ` ASC NULLS FIRST`)
|
||||||
case qcode.OrderDescNullsFirst:
|
case qcode.OrderDescNullsFirst:
|
||||||
//fmt.Fprintf(w, `%s_%d.ob.%s DESC NULLS FIRST`, sel.Name, sel.ID, ob.Col)
|
//fmt.Fprintf(w, `%s_%d.ob.%s DESC NULLS FIRST`, sel.Name, sel.ID, ob.Col)
|
||||||
tableIDColSuffix(c.w, ti.Name, sel.ID, ob.Col, "_ob")
|
tableIDColSuffix(c.w, sel.Name, sel.ID, ob.Col, "_ob")
|
||||||
io.WriteString(c.w, ` DESC NULLLS FIRST`)
|
io.WriteString(c.w, ` DESC NULLLS FIRST`)
|
||||||
case qcode.OrderAscNullsLast:
|
case qcode.OrderAscNullsLast:
|
||||||
//fmt.Fprintf(w, `"%s_%d.ob.%s ASC NULLS LAST`, sel.Name, sel.ID, ob.Col)
|
//fmt.Fprintf(w, `"%s_%d.ob.%s ASC NULLS LAST`, sel.Name, sel.ID, ob.Col)
|
||||||
tableIDColSuffix(c.w, ti.Name, sel.ID, ob.Col, "_ob")
|
tableIDColSuffix(c.w, sel.Name, sel.ID, ob.Col, "_ob")
|
||||||
io.WriteString(c.w, ` ASC NULLS LAST`)
|
io.WriteString(c.w, ` ASC NULLS LAST`)
|
||||||
case qcode.OrderDescNullsLast:
|
case qcode.OrderDescNullsLast:
|
||||||
//fmt.Fprintf(w, `%s_%d.ob.%s DESC NULLS LAST`, sel.Name, sel.ID, ob.Col)
|
//fmt.Fprintf(w, `%s_%d.ob.%s DESC NULLS LAST`, sel.Name, sel.ID, ob.Col)
|
||||||
tableIDColSuffix(c.w, ti.Name, sel.ID, ob.Col, "_ob")
|
tableIDColSuffix(c.w, sel.Name, sel.ID, ob.Col, "_ob")
|
||||||
io.WriteString(c.w, ` DESC NULLS LAST`)
|
io.WriteString(c.w, ` DESC NULLS LAST`)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("13: unexpected value %v", ob.Order)
|
return fmt.Errorf("13: unexpected value %v", ob.Order)
|
||||||
|
@ -463,6 +463,30 @@ func multiRoot(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func skipUserIDForAnonRole(t *testing.T) {
|
||||||
|
gql := `query {
|
||||||
|
products {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
user(where: { id: { eq: $user_id } }) {
|
||||||
|
id
|
||||||
|
email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
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", "products"."user_id" FROM "products" LIMIT ('20') :: integer) AS "products_0" LIMIT ('20') :: integer) AS "json_agg_0") AS "sel_0"`
|
||||||
|
|
||||||
|
resSQL, err := compileGQLToPSQL(gql, nil, "anon")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(resSQL) != sql {
|
||||||
|
t.Fatal(errNotExpected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func blockedQuery(t *testing.T) {
|
func blockedQuery(t *testing.T) {
|
||||||
gql := `query {
|
gql := `query {
|
||||||
user(id: 5, where: { id: { gt: 3 } }) {
|
user(id: 5, where: { id: { gt: 3 } }) {
|
||||||
@ -524,6 +548,7 @@ func TestCompileQuery(t *testing.T) {
|
|||||||
t.Run("queryWithVariables", queryWithVariables)
|
t.Run("queryWithVariables", queryWithVariables)
|
||||||
t.Run("withWhereOnRelations", withWhereOnRelations)
|
t.Run("withWhereOnRelations", withWhereOnRelations)
|
||||||
t.Run("multiRoot", multiRoot)
|
t.Run("multiRoot", multiRoot)
|
||||||
|
t.Run("skipUserIDForAnonRole", skipUserIDForAnonRole)
|
||||||
t.Run("blockedQuery", blockedQuery)
|
t.Run("blockedQuery", blockedQuery)
|
||||||
t.Run("blockedFunctions", blockedFunctions)
|
t.Run("blockedFunctions", blockedFunctions)
|
||||||
}
|
}
|
||||||
|
@ -134,7 +134,7 @@ func (c *compilerContext) renderUpdateStmt(w io.Writer, qc *qcode.QCode, item re
|
|||||||
io.WriteString(w, `)`)
|
io.WriteString(w, `)`)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
io.WriteString(w, `WHERE `)
|
io.WriteString(w, ` WHERE `)
|
||||||
if err := c.renderWhere(&qc.Selects[0], ti); err != nil {
|
if err := c.renderWhere(&qc.Selects[0], ti); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ func singleUpdate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
sql := `WITH "_sg_input" AS (SELECT '{{update}}' :: json AS j), "products" AS (UPDATE "products" SET ("name", "description") = (SELECT "t"."name", "t"."description" FROM "_sg_input" i, json_populate_record(NULL::products, i.j) t)WHERE ((("products"."id") IS NOT DISTINCT FROM 1) AND (("products"."id") = 15)) RETURNING "products".*) 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"`
|
sql := `WITH "_sg_input" AS (SELECT '{{update}}' :: json AS j), "products" AS (UPDATE "products" SET ("name", "description") = (SELECT "t"."name", "t"."description" FROM "_sg_input" i, json_populate_record(NULL::products, i.j) t) WHERE ((("products"."id") IS NOT DISTINCT FROM 1) AND (("products"."id") = 15)) RETURNING "products".*) 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", "description": "my_desc" }`),
|
"update": json.RawMessage(` { "name": "my_name", "description": "my_desc" }`),
|
||||||
@ -36,7 +36,7 @@ func simpleUpdateWithPresets(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
sql := `WITH "_sg_input" AS (SELECT '{{data}}' :: json AS j), "products" AS (UPDATE "products" SET ("name", "price", "updated_at") = (SELECT "t"."name", "t"."price", 'now' :: timestamp without time zone FROM "_sg_input" i, json_populate_record(NULL::products, i.j) t)WHERE (("products"."user_id") IS NOT DISTINCT FROM '{{user_id}}' :: bigint) RETURNING "products".*) 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"`
|
sql := `WITH "_sg_input" AS (SELECT '{{data}}' :: json AS j), "products" AS (UPDATE "products" SET ("name", "price", "updated_at") = (SELECT "t"."name", "t"."price", 'now' :: timestamp without time zone FROM "_sg_input" i, json_populate_record(NULL::products, i.j) t) WHERE (("products"."user_id") IS NOT DISTINCT FROM '{{user_id}}' :: bigint) RETURNING "products".*) 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}`),
|
||||||
@ -71,9 +71,9 @@ func nestedUpdateManyToMany(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
sql1 := `WITH "_sg_input" AS (SELECT '{{data}}' :: json AS j), "purchases" AS (UPDATE "purchases" SET ("sale_type", "quantity", "due_date") = (SELECT "t"."sale_type", "t"."quantity", "t"."due_date" FROM "_sg_input" i, json_populate_record(NULL::purchases, i.j) t)WHERE (("purchases"."id") = 5) RETURNING "purchases".*), "products" AS (UPDATE "products" SET ("name", "price") = (SELECT "t"."name", "t"."price" FROM "_sg_input" i, json_populate_record(NULL::products, i.j->'product') t) FROM "purchases" WHERE (("products"."id") = ("purchases"."product_id")) RETURNING "products".*), "customers" AS (UPDATE "customers" SET ("full_name", "email") = (SELECT "t"."full_name", "t"."email" FROM "_sg_input" i, json_populate_record(NULL::customers, i.j->'customer') t) FROM "purchases" WHERE (("customers"."id") = ("purchases"."customer_id")) RETURNING "customers".*) SELECT json_object_agg('purchase', json_0) FROM (SELECT row_to_json((SELECT "json_row_0" FROM (SELECT "purchases_0"."sale_type" AS "sale_type", "purchases_0"."quantity" AS "quantity", "purchases_0"."due_date" AS "due_date", "product_1_join"."json_1" AS "product", "customer_2_join"."json_2" AS "customer") AS "json_row_0")) AS "json_0" FROM (SELECT "purchases"."sale_type", "purchases"."quantity", "purchases"."due_date", "purchases"."product_id", "purchases"."customer_id" FROM "purchases" LIMIT ('1') :: integer) AS "purchases_0" LEFT OUTER JOIN LATERAL (SELECT row_to_json((SELECT "json_row_2" FROM (SELECT "customers_2"."id" AS "id", "customers_2"."full_name" AS "full_name", "customers_2"."email" AS "email") AS "json_row_2")) AS "json_2" FROM (SELECT "customers"."id", "customers"."full_name", "customers"."email" FROM "customers" WHERE ((("customers"."id") = ("purchases_0"."customer_id"))) LIMIT ('1') :: integer) AS "customers_2" LIMIT ('1') :: integer) AS "customer_2_join" ON ('true') LEFT OUTER JOIN LATERAL (SELECT row_to_json((SELECT "json_row_1" FROM (SELECT "products_1"."id" AS "id", "products_1"."name" AS "name", "products_1"."price" AS "price") AS "json_row_1")) AS "json_1" FROM (SELECT "products"."id", "products"."name", "products"."price" FROM "products" WHERE ((("products"."id") = ("purchases_0"."product_id"))) LIMIT ('1') :: integer) AS "products_1" LIMIT ('1') :: integer) AS "product_1_join" ON ('true') LIMIT ('1') :: integer) AS "sel_0"`
|
sql1 := `WITH "_sg_input" AS (SELECT '{{data}}' :: json AS j), "purchases" AS (UPDATE "purchases" SET ("sale_type", "quantity", "due_date") = (SELECT "t"."sale_type", "t"."quantity", "t"."due_date" FROM "_sg_input" i, json_populate_record(NULL::purchases, i.j) t) WHERE (("purchases"."id") = 5) RETURNING "purchases".*), "products" AS (UPDATE "products" SET ("name", "price") = (SELECT "t"."name", "t"."price" FROM "_sg_input" i, json_populate_record(NULL::products, i.j->'product') t) FROM "purchases" WHERE (("products"."id") = ("purchases"."product_id")) RETURNING "products".*), "customers" AS (UPDATE "customers" SET ("full_name", "email") = (SELECT "t"."full_name", "t"."email" FROM "_sg_input" i, json_populate_record(NULL::customers, i.j->'customer') t) FROM "purchases" WHERE (("customers"."id") = ("purchases"."customer_id")) RETURNING "customers".*) SELECT json_object_agg('purchase', json_0) FROM (SELECT row_to_json((SELECT "json_row_0" FROM (SELECT "purchases_0"."sale_type" AS "sale_type", "purchases_0"."quantity" AS "quantity", "purchases_0"."due_date" AS "due_date", "product_1_join"."json_1" AS "product", "customer_2_join"."json_2" AS "customer") AS "json_row_0")) AS "json_0" FROM (SELECT "purchases"."sale_type", "purchases"."quantity", "purchases"."due_date", "purchases"."product_id", "purchases"."customer_id" FROM "purchases" LIMIT ('1') :: integer) AS "purchases_0" LEFT OUTER JOIN LATERAL (SELECT row_to_json((SELECT "json_row_2" FROM (SELECT "customers_2"."id" AS "id", "customers_2"."full_name" AS "full_name", "customers_2"."email" AS "email") AS "json_row_2")) AS "json_2" FROM (SELECT "customers"."id", "customers"."full_name", "customers"."email" FROM "customers" WHERE ((("customers"."id") = ("purchases_0"."customer_id"))) LIMIT ('1') :: integer) AS "customers_2" LIMIT ('1') :: integer) AS "customer_2_join" ON ('true') LEFT OUTER JOIN LATERAL (SELECT row_to_json((SELECT "json_row_1" FROM (SELECT "products_1"."id" AS "id", "products_1"."name" AS "name", "products_1"."price" AS "price") AS "json_row_1")) AS "json_1" FROM (SELECT "products"."id", "products"."name", "products"."price" FROM "products" WHERE ((("products"."id") = ("purchases_0"."product_id"))) LIMIT ('1') :: integer) AS "products_1" LIMIT ('1') :: integer) AS "product_1_join" ON ('true') LIMIT ('1') :: integer) AS "sel_0"`
|
||||||
|
|
||||||
sql2 := `WITH "_sg_input" AS (SELECT '{{data}}' :: json AS j), "purchases" AS (UPDATE "purchases" SET ("sale_type", "quantity", "due_date") = (SELECT "t"."sale_type", "t"."quantity", "t"."due_date" FROM "_sg_input" i, json_populate_record(NULL::purchases, i.j) t)WHERE (("purchases"."id") = 5) RETURNING "purchases".*), "customers" AS (UPDATE "customers" SET ("full_name", "email") = (SELECT "t"."full_name", "t"."email" FROM "_sg_input" i, json_populate_record(NULL::customers, i.j->'customer') t) FROM "purchases" WHERE (("customers"."id") = ("purchases"."customer_id")) RETURNING "customers".*), "products" AS (UPDATE "products" SET ("name", "price") = (SELECT "t"."name", "t"."price" FROM "_sg_input" i, json_populate_record(NULL::products, i.j->'product') t) FROM "purchases" WHERE (("products"."id") = ("purchases"."product_id")) RETURNING "products".*) SELECT json_object_agg('purchase', json_0) FROM (SELECT row_to_json((SELECT "json_row_0" FROM (SELECT "purchases_0"."sale_type" AS "sale_type", "purchases_0"."quantity" AS "quantity", "purchases_0"."due_date" AS "due_date", "product_1_join"."json_1" AS "product", "customer_2_join"."json_2" AS "customer") AS "json_row_0")) AS "json_0" FROM (SELECT "purchases"."sale_type", "purchases"."quantity", "purchases"."due_date", "purchases"."product_id", "purchases"."customer_id" FROM "purchases" LIMIT ('1') :: integer) AS "purchases_0" LEFT OUTER JOIN LATERAL (SELECT row_to_json((SELECT "json_row_2" FROM (SELECT "customers_2"."id" AS "id", "customers_2"."full_name" AS "full_name", "customers_2"."email" AS "email") AS "json_row_2")) AS "json_2" FROM (SELECT "customers"."id", "customers"."full_name", "customers"."email" FROM "customers" WHERE ((("customers"."id") = ("purchases_0"."customer_id"))) LIMIT ('1') :: integer) AS "customers_2" LIMIT ('1') :: integer) AS "customer_2_join" ON ('true') LEFT OUTER JOIN LATERAL (SELECT row_to_json((SELECT "json_row_1" FROM (SELECT "products_1"."id" AS "id", "products_1"."name" AS "name", "products_1"."price" AS "price") AS "json_row_1")) AS "json_1" FROM (SELECT "products"."id", "products"."name", "products"."price" FROM "products" WHERE ((("products"."id") = ("purchases_0"."product_id"))) LIMIT ('1') :: integer) AS "products_1" LIMIT ('1') :: integer) AS "product_1_join" ON ('true') LIMIT ('1') :: integer) AS "sel_0"`
|
sql2 := `WITH "_sg_input" AS (SELECT '{{data}}' :: json AS j), "purchases" AS (UPDATE "purchases" SET ("sale_type", "quantity", "due_date") = (SELECT "t"."sale_type", "t"."quantity", "t"."due_date" FROM "_sg_input" i, json_populate_record(NULL::purchases, i.j) t) WHERE (("purchases"."id") = 5) RETURNING "purchases".*), "customers" AS (UPDATE "customers" SET ("full_name", "email") = (SELECT "t"."full_name", "t"."email" FROM "_sg_input" i, json_populate_record(NULL::customers, i.j->'customer') t) FROM "purchases" WHERE (("customers"."id") = ("purchases"."customer_id")) RETURNING "customers".*), "products" AS (UPDATE "products" SET ("name", "price") = (SELECT "t"."name", "t"."price" FROM "_sg_input" i, json_populate_record(NULL::products, i.j->'product') t) FROM "purchases" WHERE (("products"."id") = ("purchases"."product_id")) RETURNING "products".*) SELECT json_object_agg('purchase', json_0) FROM (SELECT row_to_json((SELECT "json_row_0" FROM (SELECT "purchases_0"."sale_type" AS "sale_type", "purchases_0"."quantity" AS "quantity", "purchases_0"."due_date" AS "due_date", "product_1_join"."json_1" AS "product", "customer_2_join"."json_2" AS "customer") AS "json_row_0")) AS "json_0" FROM (SELECT "purchases"."sale_type", "purchases"."quantity", "purchases"."due_date", "purchases"."product_id", "purchases"."customer_id" FROM "purchases" LIMIT ('1') :: integer) AS "purchases_0" LEFT OUTER JOIN LATERAL (SELECT row_to_json((SELECT "json_row_2" FROM (SELECT "customers_2"."id" AS "id", "customers_2"."full_name" AS "full_name", "customers_2"."email" AS "email") AS "json_row_2")) AS "json_2" FROM (SELECT "customers"."id", "customers"."full_name", "customers"."email" FROM "customers" WHERE ((("customers"."id") = ("purchases_0"."customer_id"))) LIMIT ('1') :: integer) AS "customers_2" LIMIT ('1') :: integer) AS "customer_2_join" ON ('true') LEFT OUTER JOIN LATERAL (SELECT row_to_json((SELECT "json_row_1" FROM (SELECT "products_1"."id" AS "id", "products_1"."name" AS "name", "products_1"."price" AS "price") AS "json_row_1")) AS "json_1" FROM (SELECT "products"."id", "products"."name", "products"."price" FROM "products" WHERE ((("products"."id") = ("purchases_0"."product_id"))) LIMIT ('1') :: integer) AS "products_1" LIMIT ('1') :: integer) AS "product_1_join" ON ('true') LIMIT ('1') :: integer) AS "sel_0"`
|
||||||
|
|
||||||
vars := map[string]json.RawMessage{
|
vars := map[string]json.RawMessage{
|
||||||
"data": json.RawMessage(` {
|
"data": json.RawMessage(` {
|
||||||
@ -119,7 +119,7 @@ func nestedUpdateOneToMany(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
sql := `WITH "_sg_input" AS (SELECT '{{data}}' :: json AS j), "users" AS (UPDATE "users" SET ("full_name", "email", "created_at", "updated_at") = (SELECT "t"."full_name", "t"."email", "t"."created_at", "t"."updated_at" FROM "_sg_input" i, json_populate_record(NULL::users, i.j) t)WHERE (("users"."id") IS NOT DISTINCT FROM 8) RETURNING "users".*), "products" AS (UPDATE "products" SET ("name", "price", "created_at", "updated_at") = (SELECT "t"."name", "t"."price", "t"."created_at", "t"."updated_at" FROM "_sg_input" i, json_populate_record(NULL::products, i.j->'product') t) FROM "users" WHERE (("products"."user_id") = ("users"."id") AND "products"."id" = '2') RETURNING "products".*) 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", "product_1_join"."json_1" AS "product") AS "json_row_0")) AS "json_0" FROM (SELECT "users"."id", "users"."full_name", "users"."email" FROM "users" LIMIT ('1') :: integer) AS "users_0" LEFT OUTER JOIN LATERAL (SELECT row_to_json((SELECT "json_row_1" FROM (SELECT "products_1"."id" AS "id", "products_1"."name" AS "name", "products_1"."price" AS "price") AS "json_row_1")) AS "json_1" FROM (SELECT "products"."id", "products"."name", "products"."price" FROM "products" WHERE ((("products"."user_id") = ("users_0"."id"))) LIMIT ('1') :: integer) AS "products_1" LIMIT ('1') :: integer) AS "product_1_join" ON ('true') LIMIT ('1') :: integer) AS "sel_0"`
|
sql := `WITH "_sg_input" AS (SELECT '{{data}}' :: json AS j), "users" AS (UPDATE "users" SET ("full_name", "email", "created_at", "updated_at") = (SELECT "t"."full_name", "t"."email", "t"."created_at", "t"."updated_at" FROM "_sg_input" i, json_populate_record(NULL::users, i.j) t) WHERE (("users"."id") IS NOT DISTINCT FROM 8) RETURNING "users".*), "products" AS (UPDATE "products" SET ("name", "price", "created_at", "updated_at") = (SELECT "t"."name", "t"."price", "t"."created_at", "t"."updated_at" FROM "_sg_input" i, json_populate_record(NULL::products, i.j->'product') t) FROM "users" WHERE (("products"."user_id") = ("users"."id") AND "products"."id" = '2') RETURNING "products".*) 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", "product_1_join"."json_1" AS "product") AS "json_row_0")) AS "json_0" FROM (SELECT "users"."id", "users"."full_name", "users"."email" FROM "users" LIMIT ('1') :: integer) AS "users_0" LEFT OUTER JOIN LATERAL (SELECT row_to_json((SELECT "json_row_1" FROM (SELECT "products_1"."id" AS "id", "products_1"."name" AS "name", "products_1"."price" AS "price") AS "json_row_1")) AS "json_1" FROM (SELECT "products"."id", "products"."name", "products"."price" FROM "products" WHERE ((("products"."user_id") = ("users_0"."id"))) LIMIT ('1') :: integer) AS "products_1" LIMIT ('1') :: integer) AS "product_1_join" ON ('true') LIMIT ('1') :: integer) AS "sel_0"`
|
||||||
|
|
||||||
vars := map[string]json.RawMessage{
|
vars := map[string]json.RawMessage{
|
||||||
"data": json.RawMessage(`{
|
"data": json.RawMessage(`{
|
||||||
@ -162,7 +162,7 @@ func nestedUpdateOneToOne(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
sql := `WITH "_sg_input" AS (SELECT '{{data}}' :: json AS j), "products" AS (UPDATE "products" SET ("name", "price", "created_at", "updated_at") = (SELECT "t"."name", "t"."price", "t"."created_at", "t"."updated_at" FROM "_sg_input" i, json_populate_record(NULL::products, i.j) t)WHERE (("products"."id") = 6) RETURNING "products".*), "users" AS (UPDATE "users" SET ("email") = (SELECT "t"."email" FROM "_sg_input" i, json_populate_record(NULL::users, i.j->'user') t) FROM "products" WHERE (("users"."id") = ("products"."user_id")) RETURNING "users".*) 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", "user_1_join"."json_1" AS "user") AS "json_row_0")) AS "json_0" FROM (SELECT "products"."id", "products"."name", "products"."user_id" FROM "products" LIMIT ('1') :: integer) AS "products_0" LEFT OUTER JOIN LATERAL (SELECT row_to_json((SELECT "json_row_1" FROM (SELECT "users_1"."id" AS "id", "users_1"."full_name" AS "full_name", "users_1"."email" AS "email") AS "json_row_1")) AS "json_1" FROM (SELECT "users"."id", "users"."full_name", "users"."email" FROM "users" WHERE ((("users"."id") = ("products_0"."user_id"))) LIMIT ('1') :: integer) AS "users_1" LIMIT ('1') :: integer) AS "user_1_join" ON ('true') LIMIT ('1') :: integer) AS "sel_0"`
|
sql := `WITH "_sg_input" AS (SELECT '{{data}}' :: json AS j), "products" AS (UPDATE "products" SET ("name", "price", "created_at", "updated_at") = (SELECT "t"."name", "t"."price", "t"."created_at", "t"."updated_at" FROM "_sg_input" i, json_populate_record(NULL::products, i.j) t) WHERE (("products"."id") = 6) RETURNING "products".*), "users" AS (UPDATE "users" SET ("email") = (SELECT "t"."email" FROM "_sg_input" i, json_populate_record(NULL::users, i.j->'user') t) FROM "products" WHERE (("users"."id") = ("products"."user_id")) RETURNING "users".*) 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", "user_1_join"."json_1" AS "user") AS "json_row_0")) AS "json_0" FROM (SELECT "products"."id", "products"."name", "products"."user_id" FROM "products" LIMIT ('1') :: integer) AS "products_0" LEFT OUTER JOIN LATERAL (SELECT row_to_json((SELECT "json_row_1" FROM (SELECT "users_1"."id" AS "id", "users_1"."full_name" AS "full_name", "users_1"."email" AS "email") AS "json_row_1")) AS "json_1" FROM (SELECT "users"."id", "users"."full_name", "users"."email" FROM "users" WHERE ((("users"."id") = ("products_0"."user_id"))) LIMIT ('1') :: integer) AS "users_1" LIMIT ('1') :: integer) AS "user_1_join" ON ('true') LIMIT ('1') :: integer) AS "sel_0"`
|
||||||
|
|
||||||
vars := map[string]json.RawMessage{
|
vars := map[string]json.RawMessage{
|
||||||
"data": json.RawMessage(`{
|
"data": json.RawMessage(`{
|
||||||
@ -200,7 +200,7 @@ func nestedUpdateOneToManyWithConnect(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
sql1 := `WITH "_sg_input" AS (SELECT '{{data}}' :: json AS j), "users" AS (UPDATE "users" SET ("full_name", "email", "created_at", "updated_at") = (SELECT "t"."full_name", "t"."email", "t"."created_at", "t"."updated_at" FROM "_sg_input" i, json_populate_record(NULL::users, i.j) t)WHERE (("users"."id") = 6) RETURNING "users".*), "products_c" AS ( UPDATE "products" SET "user_id" = "users"."id"FROM "users" WHERE ("products"."id" = '7') RETURNING "products".*), "products_d" AS ( UPDATE "products" SET "user_id" = NULL FROM "users" WHERE ("products"."id" = '8') RETURNING "products".*), "products" AS (SELECT * FROM "products_c" UNION ALL SELECT * FROM "products_d") 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", "product_1_join"."json_1" AS "product") AS "json_row_0")) AS "json_0" FROM (SELECT "users"."id", "users"."full_name", "users"."email" FROM "users" LIMIT ('1') :: integer) AS "users_0" LEFT OUTER JOIN LATERAL (SELECT row_to_json((SELECT "json_row_1" FROM (SELECT "products_1"."id" AS "id", "products_1"."name" AS "name", "products_1"."price" AS "price") AS "json_row_1")) AS "json_1" FROM (SELECT "products"."id", "products"."name", "products"."price" FROM "products" WHERE ((("products"."user_id") = ("users_0"."id"))) LIMIT ('1') :: integer) AS "products_1" LIMIT ('1') :: integer) AS "product_1_join" ON ('true') LIMIT ('1') :: integer) AS "sel_0"`
|
sql1 := `WITH "_sg_input" AS (SELECT '{{data}}' :: json AS j), "users" AS (UPDATE "users" SET ("full_name", "email", "created_at", "updated_at") = (SELECT "t"."full_name", "t"."email", "t"."created_at", "t"."updated_at" FROM "_sg_input" i, json_populate_record(NULL::users, i.j) t) WHERE (("users"."id") = 6) RETURNING "users".*), "products_c" AS ( UPDATE "products" SET "user_id" = "users"."id"FROM "users" WHERE ("products"."id" = '7') RETURNING "products".*), "products_d" AS ( UPDATE "products" SET "user_id" = NULL FROM "users" WHERE ("products"."id" = '8') RETURNING "products".*), "products" AS (SELECT * FROM "products_c" UNION ALL SELECT * FROM "products_d") 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", "product_1_join"."json_1" AS "product") AS "json_row_0")) AS "json_0" FROM (SELECT "users"."id", "users"."full_name", "users"."email" FROM "users" LIMIT ('1') :: integer) AS "users_0" LEFT OUTER JOIN LATERAL (SELECT row_to_json((SELECT "json_row_1" FROM (SELECT "products_1"."id" AS "id", "products_1"."name" AS "name", "products_1"."price" AS "price") AS "json_row_1")) AS "json_1" FROM (SELECT "products"."id", "products"."name", "products"."price" FROM "products" WHERE ((("products"."user_id") = ("users_0"."id"))) LIMIT ('1') :: integer) AS "products_1" LIMIT ('1') :: integer) AS "product_1_join" ON ('true') LIMIT ('1') :: integer) AS "sel_0"`
|
||||||
|
|
||||||
vars := map[string]json.RawMessage{
|
vars := map[string]json.RawMessage{
|
||||||
"data": json.RawMessage(`{
|
"data": json.RawMessage(`{
|
||||||
@ -238,9 +238,9 @@ func nestedUpdateOneToOneWithConnect(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
sql1 := `WITH "_sg_input" AS (SELECT '{{data}}' :: json AS j), "users" AS (SELECT * FROM "users" WHERE "users"."id" = '5' AND "users"."email" = 'test@test.com' LIMIT 1), "products" AS (UPDATE "products" SET ("name", "price", "user_id") = (SELECT "t"."name", "t"."price", "users"."id" FROM "_sg_input" i, "users", json_populate_record(NULL::products, i.j) t)WHERE (("products"."id") = 9) RETURNING "products".*) 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", "user_1_join"."json_1" AS "user") AS "json_row_0")) AS "json_0" FROM (SELECT "products"."id", "products"."name", "products"."user_id" FROM "products" LIMIT ('1') :: integer) AS "products_0" LEFT OUTER JOIN LATERAL (SELECT row_to_json((SELECT "json_row_1" FROM (SELECT "users_1"."id" AS "id", "users_1"."full_name" AS "full_name", "users_1"."email" AS "email") AS "json_row_1")) AS "json_1" FROM (SELECT "users"."id", "users"."full_name", "users"."email" FROM "users" WHERE ((("users"."id") = ("products_0"."user_id"))) LIMIT ('1') :: integer) AS "users_1" LIMIT ('1') :: integer) AS "user_1_join" ON ('true') LIMIT ('1') :: integer) AS "sel_0"`
|
sql1 := `WITH "_sg_input" AS (SELECT '{{data}}' :: json AS j), "users" AS (SELECT * FROM "users" WHERE "users"."id" = '5' AND "users"."email" = 'test@test.com' LIMIT 1), "products" AS (UPDATE "products" SET ("name", "price", "user_id") = (SELECT "t"."name", "t"."price", "users"."id" FROM "_sg_input" i, "users", json_populate_record(NULL::products, i.j) t) WHERE (("products"."id") = 9) RETURNING "products".*) 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", "user_1_join"."json_1" AS "user") AS "json_row_0")) AS "json_0" FROM (SELECT "products"."id", "products"."name", "products"."user_id" FROM "products" LIMIT ('1') :: integer) AS "products_0" LEFT OUTER JOIN LATERAL (SELECT row_to_json((SELECT "json_row_1" FROM (SELECT "users_1"."id" AS "id", "users_1"."full_name" AS "full_name", "users_1"."email" AS "email") AS "json_row_1")) AS "json_1" FROM (SELECT "users"."id", "users"."full_name", "users"."email" FROM "users" WHERE ((("users"."id") = ("products_0"."user_id"))) LIMIT ('1') :: integer) AS "users_1" LIMIT ('1') :: integer) AS "user_1_join" ON ('true') LIMIT ('1') :: integer) AS "sel_0"`
|
||||||
|
|
||||||
sql2 := `WITH "_sg_input" AS (SELECT '{{data}}' :: json AS j), "users" AS (SELECT * FROM "users" WHERE "users"."email" = 'test@test.com' AND "users"."id" = '5' LIMIT 1), "products" AS (UPDATE "products" SET ("name", "price", "user_id") = (SELECT "t"."name", "t"."price", "users"."id" FROM "_sg_input" i, "users", json_populate_record(NULL::products, i.j) t)WHERE (("products"."id") = 9) RETURNING "products".*) 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", "user_1_join"."json_1" AS "user") AS "json_row_0")) AS "json_0" FROM (SELECT "products"."id", "products"."name", "products"."user_id" FROM "products" LIMIT ('1') :: integer) AS "products_0" LEFT OUTER JOIN LATERAL (SELECT row_to_json((SELECT "json_row_1" FROM (SELECT "users_1"."id" AS "id", "users_1"."full_name" AS "full_name", "users_1"."email" AS "email") AS "json_row_1")) AS "json_1" FROM (SELECT "users"."id", "users"."full_name", "users"."email" FROM "users" WHERE ((("users"."id") = ("products_0"."user_id"))) LIMIT ('1') :: integer) AS "users_1" LIMIT ('1') :: integer) AS "user_1_join" ON ('true') LIMIT ('1') :: integer) AS "sel_0"`
|
sql2 := `WITH "_sg_input" AS (SELECT '{{data}}' :: json AS j), "users" AS (SELECT * FROM "users" WHERE "users"."email" = 'test@test.com' AND "users"."id" = '5' LIMIT 1), "products" AS (UPDATE "products" SET ("name", "price", "user_id") = (SELECT "t"."name", "t"."price", "users"."id" FROM "_sg_input" i, "users", json_populate_record(NULL::products, i.j) t) WHERE (("products"."id") = 9) RETURNING "products".*) 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", "user_1_join"."json_1" AS "user") AS "json_row_0")) AS "json_0" FROM (SELECT "products"."id", "products"."name", "products"."user_id" FROM "products" LIMIT ('1') :: integer) AS "products_0" LEFT OUTER JOIN LATERAL (SELECT row_to_json((SELECT "json_row_1" FROM (SELECT "users_1"."id" AS "id", "users_1"."full_name" AS "full_name", "users_1"."email" AS "email") AS "json_row_1")) AS "json_1" FROM (SELECT "users"."id", "users"."full_name", "users"."email" FROM "users" WHERE ((("users"."id") = ("products_0"."user_id"))) LIMIT ('1') :: integer) AS "users_1" LIMIT ('1') :: integer) AS "user_1_join" ON ('true') LIMIT ('1') :: integer) AS "sel_0"`
|
||||||
|
|
||||||
vars := map[string]json.RawMessage{
|
vars := map[string]json.RawMessage{
|
||||||
"data": json.RawMessage(`{
|
"data": json.RawMessage(`{
|
||||||
@ -273,7 +273,7 @@ func nestedUpdateOneToOneWithDisconnect(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
sql := `WITH "_sg_input" AS (SELECT '{{data}}' :: json AS j), "users" AS (SELECT * FROM (VALUES(NULL::bigint)) AS LOOKUP("id")), "products" AS (UPDATE "products" SET ("name", "price", "user_id") = (SELECT "t"."name", "t"."price", "users"."id" FROM "_sg_input" i, "users", json_populate_record(NULL::products, i.j) t)WHERE (("products"."id") = 2) RETURNING "products".*) 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", "products_0"."user_id" AS "user_id") AS "json_row_0")) AS "json_0" FROM (SELECT "products"."id", "products"."name", "products"."user_id" FROM "products" LIMIT ('1') :: integer) AS "products_0" LIMIT ('1') :: integer) AS "sel_0"`
|
sql := `WITH "_sg_input" AS (SELECT '{{data}}' :: json AS j), "users" AS (SELECT * FROM (VALUES(NULL::bigint)) AS LOOKUP("id")), "products" AS (UPDATE "products" SET ("name", "price", "user_id") = (SELECT "t"."name", "t"."price", "users"."id" FROM "_sg_input" i, "users", json_populate_record(NULL::products, i.j) t) WHERE (("products"."id") = 2) RETURNING "products".*) 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", "products_0"."user_id" AS "user_id") AS "json_row_0")) AS "json_0" FROM (SELECT "products"."id", "products"."name", "products"."user_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(`{
|
"data": json.RawMessage(`{
|
||||||
|
@ -45,6 +45,7 @@ type trval struct {
|
|||||||
query struct {
|
query struct {
|
||||||
limit string
|
limit string
|
||||||
fil *Exp
|
fil *Exp
|
||||||
|
filNU bool
|
||||||
cols map[string]struct{}
|
cols map[string]struct{}
|
||||||
disable struct {
|
disable struct {
|
||||||
funcs bool
|
funcs bool
|
||||||
@ -53,6 +54,7 @@ type trval struct {
|
|||||||
|
|
||||||
insert struct {
|
insert struct {
|
||||||
fil *Exp
|
fil *Exp
|
||||||
|
filNU bool
|
||||||
cols map[string]struct{}
|
cols map[string]struct{}
|
||||||
psmap map[string]string
|
psmap map[string]string
|
||||||
pslist []string
|
pslist []string
|
||||||
@ -60,14 +62,16 @@ type trval struct {
|
|||||||
|
|
||||||
update struct {
|
update struct {
|
||||||
fil *Exp
|
fil *Exp
|
||||||
|
filNU bool
|
||||||
cols map[string]struct{}
|
cols map[string]struct{}
|
||||||
psmap map[string]string
|
psmap map[string]string
|
||||||
pslist []string
|
pslist []string
|
||||||
}
|
}
|
||||||
|
|
||||||
delete struct {
|
delete struct {
|
||||||
fil *Exp
|
fil *Exp
|
||||||
cols map[string]struct{}
|
filNU bool
|
||||||
|
cols map[string]struct{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,21 +92,21 @@ func (trv *trval) allowedColumns(qt QType) map[string]struct{} {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (trv *trval) filter(qt QType) *Exp {
|
func (trv *trval) filter(qt QType) (*Exp, bool) {
|
||||||
switch qt {
|
switch qt {
|
||||||
case QTQuery:
|
case QTQuery:
|
||||||
return trv.query.fil
|
return trv.query.fil, trv.query.filNU
|
||||||
case QTInsert:
|
case QTInsert:
|
||||||
return trv.insert.fil
|
return trv.insert.fil, trv.insert.filNU
|
||||||
case QTUpdate:
|
case QTUpdate:
|
||||||
return trv.update.fil
|
return trv.update.fil, trv.update.filNU
|
||||||
case QTDelete:
|
case QTDelete:
|
||||||
return trv.delete.fil
|
return trv.delete.fil, trv.delete.filNU
|
||||||
case QTUpsert:
|
case QTUpsert:
|
||||||
return trv.insert.fil
|
return trv.insert.fil, trv.insert.filNU
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func listToMap(list []string) map[string]struct{} {
|
func listToMap(list []string) map[string]struct{} {
|
||||||
|
@ -51,6 +51,7 @@ type Select struct {
|
|||||||
Allowed map[string]struct{}
|
Allowed map[string]struct{}
|
||||||
PresetMap map[string]string
|
PresetMap map[string]string
|
||||||
PresetList []string
|
PresetList []string
|
||||||
|
SkipRender bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Column struct {
|
type Column struct {
|
||||||
@ -187,7 +188,7 @@ func (com *Compiler) AddRole(role, table string, trc TRConfig) error {
|
|||||||
trv := &trval{}
|
trv := &trval{}
|
||||||
|
|
||||||
// query config
|
// query config
|
||||||
trv.query.fil, err = compileFilter(trc.Query.Filters)
|
trv.query.fil, trv.query.filNU, err = compileFilter(trc.Query.Filters)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -198,7 +199,8 @@ func (com *Compiler) AddRole(role, table string, trc TRConfig) error {
|
|||||||
trv.query.disable.funcs = trc.Query.DisableFunctions
|
trv.query.disable.funcs = trc.Query.DisableFunctions
|
||||||
|
|
||||||
// insert config
|
// insert config
|
||||||
if trv.insert.fil, err = compileFilter(trc.Insert.Filters); err != nil {
|
trv.insert.fil, trv.insert.filNU, err = compileFilter(trc.Insert.Filters)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
trv.insert.cols = listToMap(trc.Insert.Columns)
|
trv.insert.cols = listToMap(trc.Insert.Columns)
|
||||||
@ -206,7 +208,8 @@ func (com *Compiler) AddRole(role, table string, trc TRConfig) error {
|
|||||||
trv.insert.pslist = mapToList(trv.insert.psmap)
|
trv.insert.pslist = mapToList(trv.insert.psmap)
|
||||||
|
|
||||||
// update config
|
// update config
|
||||||
if trv.update.fil, err = compileFilter(trc.Update.Filters); err != nil {
|
trv.update.fil, trv.update.filNU, err = compileFilter(trc.Update.Filters)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
trv.update.cols = listToMap(trc.Update.Columns)
|
trv.update.cols = listToMap(trc.Update.Columns)
|
||||||
@ -214,7 +217,8 @@ func (com *Compiler) AddRole(role, table string, trc TRConfig) error {
|
|||||||
trv.update.pslist = mapToList(trv.update.psmap)
|
trv.update.pslist = mapToList(trv.update.psmap)
|
||||||
|
|
||||||
// delete config
|
// delete config
|
||||||
if trv.delete.fil, err = compileFilter(trc.Delete.Filters); err != nil {
|
trv.delete.fil, trv.delete.filNU, err = compileFilter(trc.Delete.Filters)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
trv.delete.cols = listToMap(trc.Delete.Columns)
|
trv.delete.cols = listToMap(trc.Delete.Columns)
|
||||||
@ -334,7 +338,7 @@ func (com *Compiler) compileQuery(qc *QCode, op *Operation, role string) error {
|
|||||||
s.FieldName = s.Name
|
s.FieldName = s.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
err := com.compileArgs(qc, s, field.Args)
|
err := com.compileArgs(qc, s, field.Args, role)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -388,9 +392,16 @@ func (com *Compiler) compileQuery(qc *QCode, op *Operation, role string) error {
|
|||||||
|
|
||||||
func (com *Compiler) addFilters(qc *QCode, sel *Select, role string) {
|
func (com *Compiler) addFilters(qc *QCode, sel *Select, role string) {
|
||||||
var fil *Exp
|
var fil *Exp
|
||||||
|
var nu bool
|
||||||
|
|
||||||
if trv, ok := com.tr[role][sel.Name]; ok {
|
if trv, ok := com.tr[role][sel.Name]; ok {
|
||||||
fil = trv.filter(qc.Type)
|
fil, nu = trv.filter(qc.Type)
|
||||||
|
|
||||||
|
} else if role == "anon" {
|
||||||
|
// Tables not defined under the anon role will not be rendered
|
||||||
|
sel.SkipRender = true
|
||||||
|
return
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -399,6 +410,10 @@ func (com *Compiler) addFilters(qc *QCode, sel *Select, role string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if nu && role == "anon" {
|
||||||
|
sel.SkipRender = true
|
||||||
|
}
|
||||||
|
|
||||||
switch fil.Op {
|
switch fil.Op {
|
||||||
case OpNop:
|
case OpNop:
|
||||||
case OpFalse:
|
case OpFalse:
|
||||||
@ -420,7 +435,7 @@ func (com *Compiler) addFilters(qc *QCode, sel *Select, role string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (com *Compiler) compileArgs(qc *QCode, sel *Select, args []Arg) error {
|
func (com *Compiler) compileArgs(qc *QCode, sel *Select, args []Arg, role string) error {
|
||||||
var err error
|
var err error
|
||||||
var ka bool
|
var ka bool
|
||||||
|
|
||||||
@ -435,7 +450,7 @@ func (com *Compiler) compileArgs(qc *QCode, sel *Select, args []Arg) error {
|
|||||||
err, ka = com.compileArgSearch(sel, arg)
|
err, ka = com.compileArgSearch(sel, arg)
|
||||||
|
|
||||||
case "where":
|
case "where":
|
||||||
err, ka = com.compileArgWhere(sel, arg)
|
err, ka = com.compileArgWhere(sel, arg, role)
|
||||||
|
|
||||||
case "orderby", "order_by", "order":
|
case "orderby", "order_by", "order":
|
||||||
err, ka = com.compileArgOrderBy(sel, arg)
|
err, ka = com.compileArgOrderBy(sel, arg)
|
||||||
@ -501,19 +516,20 @@ func (com *Compiler) setMutationType(qc *QCode, args []Arg) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (com *Compiler) compileArgObj(st *util.Stack, arg *Arg) (*Exp, error) {
|
func (com *Compiler) compileArgObj(st *util.Stack, arg *Arg) (*Exp, bool, error) {
|
||||||
if arg.Val.Type != NodeObj {
|
if arg.Val.Type != NodeObj {
|
||||||
return nil, fmt.Errorf("expecting an object")
|
return nil, false, fmt.Errorf("expecting an object")
|
||||||
}
|
}
|
||||||
|
|
||||||
return com.compileArgNode(st, arg.Val, true)
|
return com.compileArgNode(st, arg.Val, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (com *Compiler) compileArgNode(st *util.Stack, node *Node, usePool bool) (*Exp, error) {
|
func (com *Compiler) compileArgNode(st *util.Stack, node *Node, usePool bool) (*Exp, bool, error) {
|
||||||
var root *Exp
|
var root *Exp
|
||||||
|
var needsUser bool
|
||||||
|
|
||||||
if node == nil || len(node.Children) == 0 {
|
if node == nil || len(node.Children) == 0 {
|
||||||
return nil, errors.New("invalid argument value")
|
return nil, needsUser, errors.New("invalid argument value")
|
||||||
}
|
}
|
||||||
|
|
||||||
pushChild(st, nil, node)
|
pushChild(st, nil, node)
|
||||||
@ -526,7 +542,7 @@ func (com *Compiler) compileArgNode(st *util.Stack, node *Node, usePool bool) (*
|
|||||||
intf := st.Pop()
|
intf := st.Pop()
|
||||||
node, ok := intf.(*Node)
|
node, ok := intf.(*Node)
|
||||||
if !ok || node == nil {
|
if !ok || node == nil {
|
||||||
return nil, fmt.Errorf("16: unexpected value %v (%t)", intf, intf)
|
return nil, needsUser, fmt.Errorf("16: unexpected value %v (%t)", intf, intf)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Objects inside a list
|
// Objects inside a list
|
||||||
@ -542,13 +558,17 @@ func (com *Compiler) compileArgNode(st *util.Stack, node *Node, usePool bool) (*
|
|||||||
|
|
||||||
ex, err := newExp(st, node, usePool)
|
ex, err := newExp(st, node, usePool)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, needsUser, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if ex == nil {
|
if ex == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ex.Type == ValVar && ex.Val == "user_id" {
|
||||||
|
needsUser = true
|
||||||
|
}
|
||||||
|
|
||||||
if node.exp == nil {
|
if node.exp == nil {
|
||||||
root = ex
|
root = ex
|
||||||
} else {
|
} else {
|
||||||
@ -571,7 +591,7 @@ func (com *Compiler) compileArgNode(st *util.Stack, node *Node, usePool bool) (*
|
|||||||
nodePool.Put(node)
|
nodePool.Put(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
return root, nil
|
return root, needsUser, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (com *Compiler) compileArgID(sel *Select, arg *Arg) (error, bool) {
|
func (com *Compiler) compileArgID(sel *Select, arg *Arg) (error, bool) {
|
||||||
@ -640,15 +660,19 @@ func (com *Compiler) compileArgSearch(sel *Select, arg *Arg) (error, bool) {
|
|||||||
return nil, true
|
return nil, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (com *Compiler) compileArgWhere(sel *Select, arg *Arg) (error, bool) {
|
func (com *Compiler) compileArgWhere(sel *Select, arg *Arg, role string) (error, bool) {
|
||||||
st := util.NewStack()
|
st := util.NewStack()
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
ex, err := com.compileArgObj(st, arg)
|
ex, nu, err := com.compileArgObj(st, arg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err, false
|
return err, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if nu && role == "anon" {
|
||||||
|
sel.SkipRender = true
|
||||||
|
}
|
||||||
|
|
||||||
if sel.Where != nil {
|
if sel.Where != nil {
|
||||||
ow := sel.Where
|
ow := sel.Where
|
||||||
|
|
||||||
@ -976,27 +1000,32 @@ func pushChild(st *util.Stack, exp *Exp, node *Node) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func compileFilter(filter []string) (*Exp, error) {
|
func compileFilter(filter []string) (*Exp, bool, error) {
|
||||||
var fl *Exp
|
var fl *Exp
|
||||||
|
var needsUser bool
|
||||||
|
|
||||||
com := &Compiler{}
|
com := &Compiler{}
|
||||||
st := util.NewStack()
|
st := util.NewStack()
|
||||||
|
|
||||||
if len(filter) == 0 {
|
if len(filter) == 0 {
|
||||||
return &Exp{Op: OpNop, doFree: false}, nil
|
return &Exp{Op: OpNop, doFree: false}, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range filter {
|
for i := range filter {
|
||||||
if filter[i] == "false" {
|
if filter[i] == "false" {
|
||||||
return &Exp{Op: OpFalse, doFree: false}, nil
|
return &Exp{Op: OpFalse, doFree: false}, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
node, err := ParseArgValue(filter[i])
|
node, err := ParseArgValue(filter[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
f, err := com.compileArgNode(st, node, false)
|
f, nu, err := com.compileArgNode(st, node, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, false, err
|
||||||
|
}
|
||||||
|
if nu {
|
||||||
|
needsUser = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Invalid table names in nested where causes fail silently
|
// TODO: Invalid table names in nested where causes fail silently
|
||||||
@ -1010,7 +1039,7 @@ func compileFilter(filter []string) (*Exp, error) {
|
|||||||
fl = &Exp{Op: OpAnd, Children: []*Exp{fl, f}, doFree: false}
|
fl = &Exp{Op: OpAnd, Children: []*Exp{fl, f}, doFree: false}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return fl, nil
|
return fl, needsUser, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildPath(a []string) string {
|
func buildPath(a []string) string {
|
||||||
|
@ -128,10 +128,10 @@ func (al *allowList) upsert(query, vars []byte, uri string) {
|
|||||||
|
|
||||||
var key string
|
var key string
|
||||||
|
|
||||||
if len(name) == 0 {
|
if len(name) != 0 {
|
||||||
key = hash
|
|
||||||
} else {
|
|
||||||
key = name
|
key = name
|
||||||
|
} else {
|
||||||
|
key = hash
|
||||||
}
|
}
|
||||||
|
|
||||||
if i, ok := al.index[key]; !ok {
|
if i, ok := al.index[key]; !ok {
|
||||||
@ -280,7 +280,7 @@ func (al *allowList) save(item *allowItem) {
|
|||||||
|
|
||||||
for i := range v {
|
for i := range v {
|
||||||
if len(v[i].vars) != 0 && !bytes.Equal(v[i].vars, []byte("{}")) {
|
if len(v[i].vars) != 0 && !bytes.Equal(v[i].vars, []byte("{}")) {
|
||||||
vj, err := json.MarshalIndent(v[i].vars, "", "\t")
|
vj, err := json.MarshalIndent(v[i].vars, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn().Err(err).Msg("Failed to write allow list 'vars' to file")
|
logger.Warn().Err(err).Msg("Failed to write allow list 'vars' to file")
|
||||||
continue
|
continue
|
||||||
|
@ -324,7 +324,7 @@ Branch : %v
|
|||||||
Go version : %v
|
Go version : %v
|
||||||
|
|
||||||
Licensed under the Apache Public License 2.0
|
Licensed under the Apache Public License 2.0
|
||||||
Copyright 2015-2019 Vikram Rangnekar.
|
Copyright 2020, Vikram Rangnekar.
|
||||||
`,
|
`,
|
||||||
version,
|
version,
|
||||||
lastCommitSHA,
|
lastCommitSHA,
|
||||||
|
@ -63,7 +63,7 @@ func cmdDBCreate(cmd *cobra.Command, args []string) {
|
|||||||
}
|
}
|
||||||
defer conn.Close(ctx)
|
defer conn.Close(ctx)
|
||||||
|
|
||||||
sql := fmt.Sprintf("CREATE DATABASE %s", conf.DB.DBName)
|
sql := fmt.Sprintf(`CREATE DATABASE "%s"`, conf.DB.DBName)
|
||||||
|
|
||||||
_, err = conn.Exec(ctx, sql)
|
_, err = conn.Exec(ctx, sql)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -83,7 +83,7 @@ func cmdDBDrop(cmd *cobra.Command, args []string) {
|
|||||||
}
|
}
|
||||||
defer conn.Close(ctx)
|
defer conn.Close(ctx)
|
||||||
|
|
||||||
sql := fmt.Sprintf(`DROP DATABASE IF EXISTS %s`, conf.DB.DBName)
|
sql := fmt.Sprintf(`DROP DATABASE IF EXISTS "%s"`, conf.DB.DBName)
|
||||||
|
|
||||||
_, err = conn.Exec(ctx, sql)
|
_, err = conn.Exec(ctx, sql)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -8,17 +8,19 @@ func cmdServ(cmd *cobra.Command, args []string) {
|
|||||||
var err error
|
var err error
|
||||||
|
|
||||||
if conf, err = initConf(); err != nil {
|
if conf, err = initConf(); err != nil {
|
||||||
errlog.Fatal().Err(err).Msg("failed to read config")
|
fatalInProd(err, "failed to read config")
|
||||||
}
|
}
|
||||||
|
|
||||||
db, err = initDBPool(conf)
|
if conf != nil {
|
||||||
if err != nil {
|
if db, err = initDBPool(conf); err != nil {
|
||||||
errlog.Fatal().Err(err).Msg("failed to connect to database")
|
fatalInProd(err, "failed to connect to database")
|
||||||
|
}
|
||||||
|
|
||||||
|
initCompiler()
|
||||||
|
initAllowList(confPath)
|
||||||
|
initPreparedList()
|
||||||
}
|
}
|
||||||
|
|
||||||
initCompiler()
|
|
||||||
initAllowList(confPath)
|
|
||||||
initPreparedList()
|
|
||||||
initWatcher(confPath)
|
initWatcher(confPath)
|
||||||
|
|
||||||
startHTTP()
|
startHTTP()
|
||||||
|
@ -108,7 +108,7 @@ func Do(log func(string, ...interface{}), additional ...dir) error {
|
|||||||
// Ensure that we use the correct events, as they are not uniform across
|
// Ensure that we use the correct events, as they are not uniform across
|
||||||
// platforms. See https://github.com/fsnotify/fsnotify/issues/74
|
// platforms. See https://github.com/fsnotify/fsnotify/issues/74
|
||||||
|
|
||||||
if !conf.Production && strings.HasSuffix(event.Name, "/allow.list") {
|
if conf != nil && !conf.Production && strings.HasSuffix(event.Name, "/allow.list") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
52
serv/serv.go
52
serv/serv.go
@ -50,7 +50,7 @@ func initCompilers(c *config) (*qcode.Compiler, *psql.Compiler, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func initWatcher(cpath string) {
|
func initWatcher(cpath string) {
|
||||||
if !conf.WatchAndReload {
|
if conf != nil && !conf.WatchAndReload {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,18 +70,33 @@ func initWatcher(cpath string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func startHTTP() {
|
func startHTTP() {
|
||||||
hp := strings.SplitN(conf.HostPort, ":", 2)
|
var hostPort string
|
||||||
|
var appName string
|
||||||
|
|
||||||
if len(conf.Host) != 0 {
|
defaultHP := "0.0.0.0:8080"
|
||||||
hp[0] = conf.Host
|
env := os.Getenv("GO_ENV")
|
||||||
|
|
||||||
|
if conf != nil {
|
||||||
|
appName = conf.AppName
|
||||||
|
hp := strings.SplitN(conf.HostPort, ":", 2)
|
||||||
|
|
||||||
|
if len(hp) == 2 {
|
||||||
|
if len(conf.Host) != 0 {
|
||||||
|
hp[0] = conf.Host
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(conf.Port) != 0 {
|
||||||
|
hp[1] = conf.Port
|
||||||
|
}
|
||||||
|
|
||||||
|
hostPort = fmt.Sprintf("%s:%s", hp[0], hp[1])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(conf.Port) != 0 {
|
if len(hostPort) == 0 {
|
||||||
hp[1] = conf.Port
|
hostPort = defaultHP
|
||||||
}
|
}
|
||||||
|
|
||||||
hostPort := fmt.Sprintf("%s:%s", hp[0], hp[1])
|
|
||||||
|
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
Addr: hostPort,
|
Addr: hostPort,
|
||||||
Handler: routeHandler(),
|
Handler: routeHandler(),
|
||||||
@ -110,8 +125,8 @@ func startHTTP() {
|
|||||||
Str("version", version).
|
Str("version", version).
|
||||||
Str("git_branch", gitBranch).
|
Str("git_branch", gitBranch).
|
||||||
Str("host_post", hostPort).
|
Str("host_post", hostPort).
|
||||||
Str("app_name", conf.AppName).
|
Str("app_name", appName).
|
||||||
Str("env", conf.Env).
|
Str("env", env).
|
||||||
Msgf("%s listening", serverName)
|
Msgf("%s listening", serverName)
|
||||||
|
|
||||||
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
|
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
|
||||||
@ -124,7 +139,7 @@ func startHTTP() {
|
|||||||
func routeHandler() http.Handler {
|
func routeHandler() http.Handler {
|
||||||
var apiH http.Handler
|
var apiH http.Handler
|
||||||
|
|
||||||
if conf.HTTPGZip {
|
if conf != nil && conf.HTTPGZip {
|
||||||
gzipH := gziphandler.MustNewGzipLevelHandler(6)
|
gzipH := gziphandler.MustNewGzipLevelHandler(6)
|
||||||
apiH = gzipH(http.HandlerFunc(apiV1))
|
apiH = gzipH(http.HandlerFunc(apiV1))
|
||||||
} else {
|
} else {
|
||||||
@ -132,11 +147,14 @@ func routeHandler() http.Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("/health", health)
|
|
||||||
mux.Handle("/api/v1/graphql", withAuth(apiH))
|
|
||||||
|
|
||||||
if conf.WebUI {
|
if conf != nil {
|
||||||
mux.Handle("/", http.FileServer(rice.MustFindBox("../web/build").HTTPBox()))
|
mux.HandleFunc("/health", health)
|
||||||
|
mux.Handle("/api/v1/graphql", withAuth(apiH))
|
||||||
|
|
||||||
|
if conf.WebUI {
|
||||||
|
mux.Handle("/", http.FileServer(rice.MustFindBox("../web/build").HTTPBox()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||||
@ -170,3 +188,7 @@ func getConfigName() string {
|
|||||||
|
|
||||||
return ge
|
return ge
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isDev() bool {
|
||||||
|
return strings.HasPrefix(os.Getenv("GO_ENV"), "dev")
|
||||||
|
}
|
||||||
|
@ -141,3 +141,11 @@ func findStmt(role string, stmts []stmt) *stmt {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fatalInProd(err error, msg string) {
|
||||||
|
if isDev() {
|
||||||
|
errlog.Error().Err(err).Msg(msg)
|
||||||
|
} else {
|
||||||
|
errlog.Fatal().Err(err).Msg(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user