Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
68a378c00f | |||
d96eaf14f4 | |||
01e488b69d | |||
7a450b16ba | |||
1ad8cbf15b | |||
f69f1c67d5 | |||
a172193955 | |||
81338b6123 | |||
265b93b203 | |||
6c240e21b4 | |||
7930719eaa | |||
cc687b1b2b | |||
3033dcf1a9 | |||
0381982d19 |
@ -11,7 +11,7 @@ RUN apk update && \
|
|||||||
apk add --no-cache make && \
|
apk add --no-cache make && \
|
||||||
apk add --no-cache git && \
|
apk add --no-cache git && \
|
||||||
apk add --no-cache jq && \
|
apk add --no-cache jq && \
|
||||||
apk add --no-cache upx=3.96
|
apk add --no-cache upx=3.95-r2
|
||||||
|
|
||||||
RUN GO111MODULE=off go get -u github.com/rafaelsq/wtc
|
RUN GO111MODULE=off go get -u github.com/rafaelsq/wtc
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ RUN chmod +x /start.sh
|
|||||||
|
|
||||||
USER nobody
|
USER nobody
|
||||||
|
|
||||||
EXPOSE 8080
|
ENV GO_ENV production
|
||||||
|
|
||||||
CMD ./super-graph serv
|
|
||||||
ENTRYPOINT ["./start.sh"]
|
ENTRYPOINT ["./start.sh"]
|
||||||
|
CMD ["./super-graph", "serv"]
|
||||||
|
@ -36,6 +36,15 @@ migrations_path: ./config/migrations
|
|||||||
# encrypting the cursor data
|
# encrypting the cursor data
|
||||||
secret_key: supercalifajalistics
|
secret_key: supercalifajalistics
|
||||||
|
|
||||||
|
# CORS: A list of origins a cross-domain request can be executed from.
|
||||||
|
# If the special * value is present in the list, all origins will be allowed.
|
||||||
|
# An origin may contain a wildcard (*) to replace 0 or more
|
||||||
|
# characters (i.e.: http://*.domain.com).
|
||||||
|
cors_allowed_origins: ["*"]
|
||||||
|
|
||||||
|
# Debug Cross Origin Resource Sharing requests
|
||||||
|
cors_debug: true
|
||||||
|
|
||||||
# Postgres related environment Variables
|
# Postgres related environment Variables
|
||||||
# SG_DATABASE_HOST
|
# SG_DATABASE_HOST
|
||||||
# SG_DATABASE_PORT
|
# SG_DATABASE_PORT
|
||||||
|
1
go.mod
1
go.mod
@ -21,6 +21,7 @@ require (
|
|||||||
github.com/magiconair/properties v1.8.1 // indirect
|
github.com/magiconair/properties v1.8.1 // indirect
|
||||||
github.com/pelletier/go-toml v1.4.0 // indirect
|
github.com/pelletier/go-toml v1.4.0 // indirect
|
||||||
github.com/pkg/errors v0.8.1
|
github.com/pkg/errors v0.8.1
|
||||||
|
github.com/rs/cors v1.7.0
|
||||||
github.com/rs/zerolog v1.15.0
|
github.com/rs/zerolog v1.15.0
|
||||||
github.com/spf13/afero v1.2.2 // indirect
|
github.com/spf13/afero v1.2.2 // indirect
|
||||||
github.com/spf13/cobra v0.0.5
|
github.com/spf13/cobra v0.0.5
|
||||||
|
2
go.sum
2
go.sum
@ -180,6 +180,8 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R
|
|||||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||||
|
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
|
||||||
|
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||||
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
|
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
|
||||||
github.com/rs/zerolog v1.15.0 h1:uPRuwkWF4J6fGsJ2R0Gn2jB1EQiav9k3S6CSdygQJXY=
|
github.com/rs/zerolog v1.15.0 h1:uPRuwkWF4J6fGsJ2R0Gn2jB1EQiav9k3S6CSdygQJXY=
|
||||||
|
@ -28,12 +28,19 @@ func Filter(w *bytes.Buffer, b []byte, keys []string) error {
|
|||||||
var k []byte
|
var k []byte
|
||||||
state := expectKey
|
state := expectKey
|
||||||
instr := false
|
instr := false
|
||||||
|
slash := 0
|
||||||
|
|
||||||
for i := 0; i < len(b); i++ {
|
for i := 0; i < len(b); i++ {
|
||||||
|
if instr && b[i] == '\\' {
|
||||||
|
slash++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if b[i] == '"' && (slash%2 == 0) {
|
||||||
|
instr = !instr
|
||||||
|
}
|
||||||
|
|
||||||
if state == expectObjClose || state == expectListClose {
|
if state == expectObjClose || state == expectListClose {
|
||||||
if b[i-1] != '\\' && b[i] == '"' {
|
|
||||||
instr = !instr
|
|
||||||
}
|
|
||||||
if !instr {
|
if !instr {
|
||||||
switch b[i] {
|
switch b[i] {
|
||||||
case '{', '[':
|
case '{', '[':
|
||||||
@ -70,7 +77,7 @@ func Filter(w *bytes.Buffer, b []byte, keys []string) error {
|
|||||||
state = expectKeyClose
|
state = expectKeyClose
|
||||||
s = i
|
s = i
|
||||||
|
|
||||||
case state == expectKeyClose && (b[i-1] != '\\' && b[i] == '"'):
|
case state == expectKeyClose && (b[i] == '"' && (slash%2 == 0)):
|
||||||
state = expectColon
|
state = expectColon
|
||||||
k = b[(s + 1):i]
|
k = b[(s + 1):i]
|
||||||
|
|
||||||
@ -80,7 +87,7 @@ func Filter(w *bytes.Buffer, b []byte, keys []string) error {
|
|||||||
case state == expectValue && b[i] == '"':
|
case state == expectValue && b[i] == '"':
|
||||||
state = expectString
|
state = expectString
|
||||||
|
|
||||||
case state == expectString && (b[i-1] != '\\' && b[i] == '"'):
|
case state == expectString && (b[i] == '"' && (slash%2 == 0)):
|
||||||
e = i
|
e = i
|
||||||
|
|
||||||
case state == expectValue && b[i] == '[':
|
case state == expectValue && b[i] == '[':
|
||||||
|
19
jsn/get.go
19
jsn/get.go
@ -52,12 +52,19 @@ func Get(b []byte, keys [][]byte) []Field {
|
|||||||
|
|
||||||
n := 0
|
n := 0
|
||||||
instr := false
|
instr := false
|
||||||
|
slash := 0
|
||||||
|
|
||||||
for i := 0; i < len(b); i++ {
|
for i := 0; i < len(b); i++ {
|
||||||
|
if instr && b[i] == '\\' {
|
||||||
|
slash++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if b[i] == '"' && (slash%2 == 0) {
|
||||||
|
instr = !instr
|
||||||
|
}
|
||||||
|
|
||||||
if state == expectObjClose || state == expectListClose {
|
if state == expectObjClose || state == expectListClose {
|
||||||
if b[i-1] != '\\' && b[i] == '"' {
|
|
||||||
instr = !instr
|
|
||||||
}
|
|
||||||
if !instr {
|
if !instr {
|
||||||
switch b[i] {
|
switch b[i] {
|
||||||
case '{', '[':
|
case '{', '[':
|
||||||
@ -73,7 +80,7 @@ func Get(b []byte, keys [][]byte) []Field {
|
|||||||
state = expectKeyClose
|
state = expectKeyClose
|
||||||
s = i
|
s = i
|
||||||
|
|
||||||
case state == expectKeyClose && (b[i-1] != '\\' && b[i] == '"'):
|
case state == expectKeyClose && (b[i] == '"' && (slash%2 == 0)):
|
||||||
state = expectColon
|
state = expectColon
|
||||||
k = b[(s + 1):i]
|
k = b[(s + 1):i]
|
||||||
|
|
||||||
@ -84,7 +91,7 @@ func Get(b []byte, keys [][]byte) []Field {
|
|||||||
state = expectString
|
state = expectString
|
||||||
s = i
|
s = i
|
||||||
|
|
||||||
case state == expectString && (b[i-1] != '\\' && b[i] == '"'):
|
case state == expectString && (b[i] == '"' && (slash%2 == 0)):
|
||||||
e = i
|
e = i
|
||||||
|
|
||||||
case state == expectValue && b[i] == '[':
|
case state == expectValue && b[i] == '[':
|
||||||
@ -155,6 +162,8 @@ func Get(b []byte, keys [][]byte) []Field {
|
|||||||
state = expectKey
|
state = expectKey
|
||||||
e = 0
|
e = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
slash = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
return res[:n]
|
return res[:n]
|
||||||
|
@ -2,7 +2,9 @@ package jsn
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -163,7 +165,9 @@ var (
|
|||||||
input6 = `
|
input6 = `
|
||||||
{"users" : [{"id" : 1, "email" : "vicram@gmail.com", "slug" : "vikram-rangnekar", "threads" : [], "threads_cursor" : null}, {"id" : 3, "email" : "marareilly@lang.name", "slug" : "raymundo-corwin", "threads" : [{"id" : 9, "title" : "Et alias et aut porro praesentium nam in voluptatem reiciendis quisquam perspiciatis inventore eos quia et et enim qui amet."}, {"id" : 25, "title" : "Ipsam quam nemo culpa tempore amet optio sit sed eligendi autem consequatur quaerat rem velit quibusdam quibusdam optio a voluptatem."}], "threads_cursor" : 25}], "users_cursor" : 3}`
|
{"users" : [{"id" : 1, "email" : "vicram@gmail.com", "slug" : "vikram-rangnekar", "threads" : [], "threads_cursor" : null}, {"id" : 3, "email" : "marareilly@lang.name", "slug" : "raymundo-corwin", "threads" : [{"id" : 9, "title" : "Et alias et aut porro praesentium nam in voluptatem reiciendis quisquam perspiciatis inventore eos quia et et enim qui amet."}, {"id" : 25, "title" : "Ipsam quam nemo culpa tempore amet optio sit sed eligendi autem consequatur quaerat rem velit quibusdam quibusdam optio a voluptatem."}], "threads_cursor" : 25}], "users_cursor" : 3}`
|
||||||
|
|
||||||
input7, _ = ioutil.ReadFile("test.json")
|
input7, _ = ioutil.ReadFile("test7.json")
|
||||||
|
|
||||||
|
input8, _ = ioutil.ReadFile("test8.json")
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGet(t *testing.T) {
|
func TestGet(t *testing.T) {
|
||||||
@ -268,6 +272,23 @@ func TestGet3(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGet4(t *testing.T) {
|
||||||
|
exp := `"# \n\n@@@java\npackage main\n\nimport (\n \"net/http\"\n \"strings\"\n\n \"github.com/gin-gonic/gin\"\n)\n\nfunc main() {\n r := gin.Default()\n r.LoadHTMLGlob(\"templates/*\")\n\n r.GET(\"/\", handleIndex)\n r.GET(\"/to/:name\", handleIndex)\n r.Run()\n}\n\n// Hello is page data for the template\ntype Hello struct {\n Name string\n}\n\nfunc handleIndex(c *gin.Context) {\n name := c.Param(\"name\")\n if name != \"\" {\n name = strings.TrimPrefix(c.Param(\"name\"), \"/\")\n }\n c.HTML(http.StatusOK, \"hellofly.tmpl\", gin.H{\"Name\": name})\n}\n@@@\n\n\\"`
|
||||||
|
|
||||||
|
exp = strings.ReplaceAll(exp, "@", "`")
|
||||||
|
|
||||||
|
values := Get(input8, [][]byte{[]byte("body")})
|
||||||
|
|
||||||
|
if string(values[0].Key) != "body" {
|
||||||
|
t.Fatal("unexpected key")
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(values[0].Value) != exp {
|
||||||
|
fmt.Println(string(values[0].Value))
|
||||||
|
t.Fatal("unexpected 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) {
|
||||||
|
18
jsn/keys.go
18
jsn/keys.go
@ -11,12 +11,19 @@ func Keys(b []byte) [][]byte {
|
|||||||
st := NewStack()
|
st := NewStack()
|
||||||
ae := 0
|
ae := 0
|
||||||
instr := false
|
instr := false
|
||||||
|
slash := 0
|
||||||
|
|
||||||
for i := 0; i < len(b); i++ {
|
for i := 0; i < len(b); i++ {
|
||||||
|
if instr && b[i] == '\\' {
|
||||||
|
slash++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if b[i] == '"' && (slash%2 == 0) {
|
||||||
|
instr = !instr
|
||||||
|
}
|
||||||
|
|
||||||
if state == expectObjClose || state == expectListClose {
|
if state == expectObjClose || state == expectListClose {
|
||||||
if b[i-1] != '\\' && b[i] == '"' {
|
|
||||||
instr = !instr
|
|
||||||
}
|
|
||||||
if !instr {
|
if !instr {
|
||||||
switch b[i] {
|
switch b[i] {
|
||||||
case '{', '[':
|
case '{', '[':
|
||||||
@ -52,7 +59,7 @@ func Keys(b []byte) [][]byte {
|
|||||||
state = expectKeyClose
|
state = expectKeyClose
|
||||||
s = i
|
s = i
|
||||||
|
|
||||||
case state == expectKeyClose && (b[i-1] != '\\' && b[i] == '"'):
|
case state == expectKeyClose && (b[i] == '"' && (slash%2 == 0)):
|
||||||
state = expectColon
|
state = expectColon
|
||||||
k = b[(s + 1):i]
|
k = b[(s + 1):i]
|
||||||
|
|
||||||
@ -63,7 +70,7 @@ func Keys(b []byte) [][]byte {
|
|||||||
state = expectString
|
state = expectString
|
||||||
s = i
|
s = i
|
||||||
|
|
||||||
case state == expectString && (b[i-1] != '\\' && b[i] == '"'):
|
case state == expectString && (b[i] == '"' && (slash%2 == 0)):
|
||||||
e = i
|
e = i
|
||||||
|
|
||||||
case state == expectValue && b[i] == '{':
|
case state == expectValue && b[i] == '{':
|
||||||
@ -135,6 +142,7 @@ func Keys(b []byte) [][]byte {
|
|||||||
e = 0
|
e = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
slash = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
@ -12,6 +12,11 @@ func Replace(w *bytes.Buffer, b []byte, from, to []Field) error {
|
|||||||
return errors.New("'from' and 'to' must be of the same length")
|
return errors.New("'from' and 'to' must be of the same length")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(from) == 0 || len(to) == 0 {
|
||||||
|
_, err := w.Write(b)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
h := xxhash.New()
|
h := xxhash.New()
|
||||||
tmap := make(map[uint64]int, len(from))
|
tmap := make(map[uint64]int, len(from))
|
||||||
|
|
||||||
@ -33,17 +38,24 @@ func Replace(w *bytes.Buffer, b []byte, from, to []Field) error {
|
|||||||
ws, we := -1, len(b)
|
ws, we := -1, len(b)
|
||||||
|
|
||||||
instr := false
|
instr := false
|
||||||
|
slash := 0
|
||||||
|
|
||||||
for i := 0; i < len(b); i++ {
|
for i := 0; i < len(b); i++ {
|
||||||
|
if instr && b[i] == '\\' {
|
||||||
|
slash++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// skip any left padding whitespace
|
// skip any left padding whitespace
|
||||||
if ws == -1 && (b[i] == '{' || b[i] == '[') {
|
if ws == -1 && (b[i] == '{' || b[i] == '[') {
|
||||||
ws = i
|
ws = i
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if b[i] == '"' && (slash%2 == 0) {
|
||||||
|
instr = !instr
|
||||||
|
}
|
||||||
|
|
||||||
if state == expectObjClose || state == expectListClose {
|
if state == expectObjClose || state == expectListClose {
|
||||||
if b[i-1] != '\\' && b[i] == '"' {
|
|
||||||
instr = !instr
|
|
||||||
}
|
|
||||||
if !instr {
|
if !instr {
|
||||||
switch b[i] {
|
switch b[i] {
|
||||||
case '{', '[':
|
case '{', '[':
|
||||||
@ -59,7 +71,7 @@ func Replace(w *bytes.Buffer, b []byte, from, to []Field) error {
|
|||||||
state = expectKeyClose
|
state = expectKeyClose
|
||||||
s = i
|
s = i
|
||||||
|
|
||||||
case state == expectKeyClose && (b[i-1] != '\\' && b[i] == '"'):
|
case state == expectKeyClose && (b[i] == '"' && (slash%2 == 0)):
|
||||||
state = expectColon
|
state = expectColon
|
||||||
if _, err := h.Write(b[(s + 1):i]); err != nil {
|
if _, err := h.Write(b[(s + 1):i]); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -73,7 +85,7 @@ func Replace(w *bytes.Buffer, b []byte, from, to []Field) error {
|
|||||||
state = expectString
|
state = expectString
|
||||||
s = i
|
s = i
|
||||||
|
|
||||||
case state == expectString && (b[i-1] != '\\' && b[i] == '"'):
|
case state == expectString && (b[i] == '"' && (slash%2 == 0)):
|
||||||
e = i
|
e = i
|
||||||
|
|
||||||
case state == expectValue && b[i] == '[':
|
case state == expectValue && b[i] == '[':
|
||||||
@ -167,6 +179,8 @@ func Replace(w *bytes.Buffer, b []byte, from, to []Field) error {
|
|||||||
e = 0
|
e = 0
|
||||||
d = 0
|
d = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
slash = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if ws == -1 || (ws == 0 && we == len(b)) {
|
if ws == -1 || (ws == 0 && we == len(b)) {
|
||||||
|
19
jsn/strip.go
19
jsn/strip.go
@ -12,12 +12,19 @@ func Strip(b []byte, path [][]byte) []byte {
|
|||||||
pm := false
|
pm := false
|
||||||
state := expectKey
|
state := expectKey
|
||||||
instr := false
|
instr := false
|
||||||
|
slash := 0
|
||||||
|
|
||||||
for i := 0; i < len(b); i++ {
|
for i := 0; i < len(b); i++ {
|
||||||
|
if instr && b[i] == '\\' {
|
||||||
|
slash++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if b[i] == '"' && (slash%2 == 0) {
|
||||||
|
instr = !instr
|
||||||
|
}
|
||||||
|
|
||||||
if state == expectObjClose || state == expectListClose {
|
if state == expectObjClose || state == expectListClose {
|
||||||
if b[i-1] != '\\' && b[i] == '"' {
|
|
||||||
instr = !instr
|
|
||||||
}
|
|
||||||
if !instr {
|
if !instr {
|
||||||
switch b[i] {
|
switch b[i] {
|
||||||
case '{', '[':
|
case '{', '[':
|
||||||
@ -33,7 +40,7 @@ func Strip(b []byte, path [][]byte) []byte {
|
|||||||
state = expectKeyClose
|
state = expectKeyClose
|
||||||
s = i
|
s = i
|
||||||
|
|
||||||
case state == expectKeyClose && (b[i-1] != '\\' && b[i] == '"'):
|
case state == expectKeyClose && (b[i] == '"' && (slash%2 == 0)):
|
||||||
state = expectColon
|
state = expectColon
|
||||||
if pi == len(path) {
|
if pi == len(path) {
|
||||||
pi = 0
|
pi = 0
|
||||||
@ -50,7 +57,7 @@ func Strip(b []byte, path [][]byte) []byte {
|
|||||||
state = expectString
|
state = expectString
|
||||||
s = i
|
s = i
|
||||||
|
|
||||||
case state == expectString && (b[i-1] != '\\' && b[i] == '"'):
|
case state == expectString && (b[i] == '"' && (slash%2 == 0)):
|
||||||
e = i
|
e = i
|
||||||
|
|
||||||
case state == expectValue && b[i] == '[':
|
case state == expectValue && b[i] == '[':
|
||||||
@ -107,6 +114,8 @@ func Strip(b []byte, path [][]byte) []byte {
|
|||||||
state = expectKey
|
state = expectKey
|
||||||
e = 0
|
e = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
slash = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
return ob
|
return ob
|
||||||
|
7
jsn/test8.json
Normal file
7
jsn/test8.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"slug": "javapackage-mainimport-nethttp-strings-githubcomgi-2786",
|
||||||
|
"published": true,
|
||||||
|
"body": "# \n\n```java\npackage main\n\nimport (\n \"net/http\"\n \"strings\"\n\n \"github.com/gin-gonic/gin\"\n)\n\nfunc main() {\n r := gin.Default()\n r.LoadHTMLGlob(\"templates/*\")\n\n r.GET(\"/\", handleIndex)\n r.GET(\"/to/:name\", handleIndex)\n r.Run()\n}\n\n// Hello is page data for the template\ntype Hello struct {\n Name string\n}\n\nfunc handleIndex(c *gin.Context) {\n name := c.Param(\"name\")\n if name != \"\" {\n name = strings.TrimPrefix(c.Param(\"name\"), \"/\")\n }\n c.HTML(http.StatusOK, \"hellofly.tmpl\", gin.H{\"Name\": name})\n}\n```\n\n\\"
|
||||||
|
}
|
||||||
|
}
|
@ -35,33 +35,37 @@ func (c *compilerContext) renderBaseColumns(
|
|||||||
c.renderComma(i)
|
c.renderComma(i)
|
||||||
realColsRendered = append(realColsRendered, n)
|
realColsRendered = append(realColsRendered, n)
|
||||||
colWithTable(c.w, ti.Name, cn)
|
colWithTable(c.w, ti.Name, cn)
|
||||||
i++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if isSearch && !isRealCol {
|
} else {
|
||||||
switch {
|
switch {
|
||||||
case cn == "search_rank":
|
case isSearch && cn == "search_rank":
|
||||||
if err := c.renderColumnSearchRank(sel, ti, col, i); err != nil {
|
if err := c.renderColumnSearchRank(sel, ti, col, i); err != nil {
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
i++
|
|
||||||
|
|
||||||
case strings.HasPrefix(cn, "search_headline_"):
|
case isSearch && strings.HasPrefix(cn, "search_headline_"):
|
||||||
if err := c.renderColumnSearchHeadline(sel, ti, col, i); err != nil {
|
if err := c.renderColumnSearchHeadline(sel, ti, col, i); err != nil {
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
i++
|
|
||||||
|
|
||||||
}
|
case cn == "__typename":
|
||||||
} else {
|
if err := c.renderColumnTypename(sel, ti, col, i); err != nil {
|
||||||
if err := c.renderColumnFunction(sel, ti, col, i); err != nil {
|
return nil, false, err
|
||||||
return nil, false, err
|
}
|
||||||
}
|
|
||||||
isAgg = true
|
|
||||||
i++
|
|
||||||
|
|
||||||
|
case strings.HasSuffix(cn, "_cursor"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
default:
|
||||||
|
if err := c.renderColumnFunction(sel, ti, col, i); err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
isAgg = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
i++
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if isCursorPaged {
|
if isCursorPaged {
|
||||||
@ -148,6 +152,20 @@ func (c *compilerContext) renderColumnSearchHeadline(sel *qcode.Select, ti *DBTa
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *compilerContext) renderColumnTypename(sel *qcode.Select, ti *DBTableInfo, col qcode.Column, columnsRendered int) error {
|
||||||
|
if isColumnBlocked(sel, col.Name) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
c.renderComma(columnsRendered)
|
||||||
|
io.WriteString(c.w, `(`)
|
||||||
|
squoted(c.w, ti.Name)
|
||||||
|
io.WriteString(c.w, ` :: text)`)
|
||||||
|
alias(c.w, col.Name)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *compilerContext) renderColumnFunction(sel *qcode.Select, ti *DBTableInfo, col qcode.Column, columnsRendered int) error {
|
func (c *compilerContext) renderColumnFunction(sel *qcode.Select, ti *DBTableInfo, col qcode.Column, columnsRendered int) error {
|
||||||
pl := funcPrefixLen(col.Name)
|
pl := funcPrefixLen(col.Name)
|
||||||
// if pl == 0 {
|
// if pl == 0 {
|
||||||
@ -168,7 +186,7 @@ func (c *compilerContext) renderColumnFunction(sel *qcode.Select, ti *DBTableInf
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fn := cn[0 : pl-1]
|
fn := col.Name[:pl-1]
|
||||||
|
|
||||||
c.renderComma(columnsRendered)
|
c.renderComma(columnsRendered)
|
||||||
|
|
||||||
|
@ -17,6 +17,10 @@ const (
|
|||||||
closeBlock = 500
|
closeBlock = 500
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrAllTablesSkipped = errors.New("all tables skipped. cannot render query")
|
||||||
|
)
|
||||||
|
|
||||||
type Variables map[string]json.RawMessage
|
type Variables map[string]json.RawMessage
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
@ -89,7 +93,7 @@ func (co *Compiler) compileQuery(qc *qcode.QCode, w io.Writer, vars Variables) (
|
|||||||
io.WriteString(c.w, `SELECT json_build_object(`)
|
io.WriteString(c.w, `SELECT json_build_object(`)
|
||||||
for _, id := range qc.Roots {
|
for _, id := range qc.Roots {
|
||||||
root := &qc.Selects[id]
|
root := &qc.Selects[id]
|
||||||
if root.SkipRender {
|
if root.SkipRender || len(root.Cols) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,7 +111,7 @@ func (co *Compiler) compileQuery(qc *qcode.QCode, w io.Writer, vars Variables) (
|
|||||||
io.WriteString(c.w, `) as "__root" FROM `)
|
io.WriteString(c.w, `) as "__root" FROM `)
|
||||||
|
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
return 0, errors.New("all tables skipped. cannot render query")
|
return 0, ErrAllTablesSkipped
|
||||||
}
|
}
|
||||||
|
|
||||||
var ignored uint32
|
var ignored uint32
|
||||||
@ -122,6 +126,10 @@ func (co *Compiler) compileQuery(qc *qcode.QCode, w io.Writer, vars Variables) (
|
|||||||
if id < closeBlock {
|
if id < closeBlock {
|
||||||
sel := &c.s[id]
|
sel := &c.s[id]
|
||||||
|
|
||||||
|
if len(sel.Cols) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
ti, err := c.schema.GetTable(sel.Name)
|
ti, err := c.schema.GetTable(sel.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
@ -502,22 +510,25 @@ func (c *compilerContext) renderJoinByName(table, parent string, id int32) error
|
|||||||
|
|
||||||
func (c *compilerContext) renderColumns(sel *qcode.Select, ti *DBTableInfo, skipped uint32) error {
|
func (c *compilerContext) renderColumns(sel *qcode.Select, ti *DBTableInfo, skipped uint32) error {
|
||||||
i := 0
|
i := 0
|
||||||
|
var cn string
|
||||||
|
|
||||||
for _, col := range sel.Cols {
|
for _, col := range sel.Cols {
|
||||||
n := funcPrefixLen(col.Name)
|
if n := funcPrefixLen(col.Name); n != 0 {
|
||||||
if n != 0 {
|
|
||||||
if !sel.Functions {
|
if !sel.Functions {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if len(sel.Allowed) != 0 {
|
cn = col.Name[n:]
|
||||||
if _, ok := sel.Allowed[col.Name[n:]]; !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if len(sel.Allowed) != 0 {
|
cn = col.Name
|
||||||
if _, ok := sel.Allowed[col.Name]; !ok {
|
|
||||||
continue
|
if strings.HasSuffix(cn, "_cursor") {
|
||||||
}
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(sel.Allowed) != 0 {
|
||||||
|
if _, ok := sel.Allowed[cn]; !ok {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -569,9 +580,6 @@ func (c *compilerContext) renderJoinColumns(sel *qcode.Select, ti *DBTableInfo,
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
childSel := &c.s[id]
|
childSel := &c.s[id]
|
||||||
if childSel.SkipRender {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if i != 0 {
|
if i != 0 {
|
||||||
io.WriteString(c.w, ", ")
|
io.WriteString(c.w, ", ")
|
||||||
@ -579,6 +587,11 @@ func (c *compilerContext) renderJoinColumns(sel *qcode.Select, ti *DBTableInfo,
|
|||||||
|
|
||||||
squoted(c.w, childSel.FieldName)
|
squoted(c.w, childSel.FieldName)
|
||||||
|
|
||||||
|
if childSel.SkipRender {
|
||||||
|
io.WriteString(c.w, `, NULL`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
io.WriteString(c.w, `, "__sel_`)
|
io.WriteString(c.w, `, "__sel_`)
|
||||||
int2string(c.w, childSel.ID)
|
int2string(c.w, childSel.ID)
|
||||||
io.WriteString(c.w, `"."json"`)
|
io.WriteString(c.w, `"."json"`)
|
||||||
|
@ -327,7 +327,7 @@ func jsonColumnAsTable(t *testing.T) {
|
|||||||
compileGQLToPSQL(t, gql, nil, "admin")
|
compileGQLToPSQL(t, gql, nil, "admin")
|
||||||
}
|
}
|
||||||
|
|
||||||
func skipUserIDForAnonRole(t *testing.T) {
|
func nullForAuthRequiredInAnon(t *testing.T) {
|
||||||
gql := `query {
|
gql := `query {
|
||||||
products {
|
products {
|
||||||
id
|
id
|
||||||
@ -387,7 +387,7 @@ func TestCompileQuery(t *testing.T) {
|
|||||||
t.Run("multiRoot", multiRoot)
|
t.Run("multiRoot", multiRoot)
|
||||||
t.Run("jsonColumnAsTable", jsonColumnAsTable)
|
t.Run("jsonColumnAsTable", jsonColumnAsTable)
|
||||||
t.Run("withCursor", withCursor)
|
t.Run("withCursor", withCursor)
|
||||||
t.Run("skipUserIDForAnonRole", skipUserIDForAnonRole)
|
t.Run("nullForAuthRequiredInAnon", nullForAuthRequiredInAnon)
|
||||||
t.Run("blockedQuery", blockedQuery)
|
t.Run("blockedQuery", blockedQuery)
|
||||||
t.Run("blockedFunctions", blockedFunctions)
|
t.Run("blockedFunctions", blockedFunctions)
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,14 @@ func NewDBSchema(info *DBInfo, aliases map[string][]string) (*DBSchema, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i, t := range info.Tables {
|
for i, t := range info.Tables {
|
||||||
err := schema.updateRelationships(t, info.Columns[i])
|
err := schema.firstDegreeRels(t, info.Columns[i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, t := range info.Tables {
|
||||||
|
err := schema.secondDegreeRels(t, info.Columns[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -131,8 +138,7 @@ func (s *DBSchema) addTable(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DBSchema) updateRelationships(t DBTable, cols []DBColumn) error {
|
func (s *DBSchema) firstDegreeRels(t DBTable, cols []DBColumn) error {
|
||||||
jcols := make([]DBColumn, 0, len(cols))
|
|
||||||
ct := t.Key
|
ct := t.Key
|
||||||
cti, ok := s.t[ct]
|
cti, ok := s.t[ct]
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -230,6 +236,51 @@ func (s *DBSchema) updateRelationships(t DBTable, cols []DBColumn) error {
|
|||||||
if err := s.SetRel(ft, ct, rel2); err != nil {
|
if err := s.SetRel(ft, ct, rel2); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DBSchema) secondDegreeRels(t DBTable, cols []DBColumn) error {
|
||||||
|
jcols := make([]DBColumn, 0, len(cols))
|
||||||
|
ct := t.Key
|
||||||
|
cti, ok := s.t[ct]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("invalid foreign key table '%s'", ct)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range cols {
|
||||||
|
c := cols[i]
|
||||||
|
|
||||||
|
if len(c.FKeyTable) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Foreign key column name
|
||||||
|
ft := strings.ToLower(c.FKeyTable)
|
||||||
|
|
||||||
|
ti, ok := s.t[ft]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("invalid foreign key table '%s'", ft)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is an embedded relationship like when a json/jsonb column
|
||||||
|
// is exposed as a table
|
||||||
|
if c.Name == c.FKeyTable && len(c.FKeyColID) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.FKeyColID) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Foreign key column id
|
||||||
|
fcid := c.FKeyColID[0]
|
||||||
|
|
||||||
|
if _, ok := ti.ColIDMap[fcid]; !ok {
|
||||||
|
return fmt.Errorf("invalid foreign key column id '%d' for table '%s'",
|
||||||
|
fcid, ti.Name)
|
||||||
|
}
|
||||||
|
|
||||||
jcols = append(jcols, c)
|
jcols = append(jcols, c)
|
||||||
}
|
}
|
||||||
@ -322,6 +373,9 @@ func (s *DBSchema) GetTable(table string) (*DBTableInfo, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *DBSchema) SetRel(child, parent string, rel *DBRel) error {
|
func (s *DBSchema) SetRel(child, parent string, rel *DBRel) error {
|
||||||
|
sp := strings.ToLower(flect.Singularize(parent))
|
||||||
|
pp := strings.ToLower(flect.Pluralize(parent))
|
||||||
|
|
||||||
sc := strings.ToLower(flect.Singularize(child))
|
sc := strings.ToLower(flect.Singularize(child))
|
||||||
pc := strings.ToLower(flect.Pluralize(child))
|
pc := strings.ToLower(flect.Pluralize(child))
|
||||||
|
|
||||||
@ -333,9 +387,6 @@ func (s *DBSchema) SetRel(child, parent string, rel *DBRel) error {
|
|||||||
s.rm[pc] = make(map[string]*DBRel)
|
s.rm[pc] = make(map[string]*DBRel)
|
||||||
}
|
}
|
||||||
|
|
||||||
sp := strings.ToLower(flect.Singularize(parent))
|
|
||||||
pp := strings.ToLower(flect.Pluralize(parent))
|
|
||||||
|
|
||||||
if _, ok := s.rm[sc][sp]; !ok {
|
if _, ok := s.rm[sc][sp]; !ok {
|
||||||
s.rm[sc][sp] = rel
|
s.rm[sc][sp] = rel
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,10 @@ func (rt RelType) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (re *DBRel) String() string {
|
func (re *DBRel) String() string {
|
||||||
|
if re.Type == RelOneToManyThrough {
|
||||||
|
return fmt.Sprintf("'%s.%s' --(Through: %s)--> '%s.%s'",
|
||||||
|
re.Left.Table, re.Left.Col, re.Through, re.Right.Table, re.Right.Col)
|
||||||
|
}
|
||||||
return fmt.Sprintf("'%s.%s' --(%s)--> '%s.%s'",
|
return fmt.Sprintf("'%s.%s' --(%s)--> '%s.%s'",
|
||||||
re.Left.Table, re.Left.Col, re.Type, re.Right.Table, re.Right.Col)
|
re.Left.Table, re.Left.Col, re.Type, re.Right.Table, re.Right.Col)
|
||||||
}
|
}
|
||||||
|
@ -92,7 +92,14 @@ func getTestSchema() *DBSchema {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i, t := range tables {
|
for i, t := range tables {
|
||||||
err := schema.updateRelationships(t, columns[i])
|
err := schema.firstDegreeRels(t, columns[i])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, t := range tables {
|
||||||
|
err := schema.secondDegreeRels(t, columns[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -69,13 +69,13 @@ SELECT json_build_object('products', "__sel_0"."json") as "__root" FROM (SELECT
|
|||||||
=== RUN TestCompileQuery/manyToManyReverse
|
=== RUN TestCompileQuery/manyToManyReverse
|
||||||
SELECT json_build_object('customers', "__sel_0"."json") as "__root" FROM (SELECT coalesce(json_agg("__sel_0"."json"), '[]') as "json" FROM (SELECT json_build_object('email', "customers_0"."email", 'full_name', "customers_0"."full_name", 'products', "__sel_1"."json") AS "json" 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_1"."json"), '[]') as "json" FROM (SELECT json_build_object('name', "products_1"."name") AS "json" FROM (SELECT "products"."name" FROM "products" LEFT OUTER JOIN "purchases" ON (("purchases"."customer_id") = ("customers_0"."id")) WHERE ((("products"."id") = ("purchases"."product_id")) AND ((("products"."price") > '0' :: numeric(7,2)) AND (("products"."price") < '8' :: numeric(7,2)))) LIMIT ('20') :: integer) AS "products_1") AS "__sel_1") AS "__sel_1" ON ('true')) AS "__sel_0") AS "__sel_0"
|
SELECT json_build_object('customers', "__sel_0"."json") as "__root" FROM (SELECT coalesce(json_agg("__sel_0"."json"), '[]') as "json" FROM (SELECT json_build_object('email', "customers_0"."email", 'full_name', "customers_0"."full_name", 'products', "__sel_1"."json") AS "json" 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_1"."json"), '[]') as "json" FROM (SELECT json_build_object('name', "products_1"."name") AS "json" FROM (SELECT "products"."name" FROM "products" LEFT OUTER JOIN "purchases" ON (("purchases"."customer_id") = ("customers_0"."id")) WHERE ((("products"."id") = ("purchases"."product_id")) AND ((("products"."price") > '0' :: numeric(7,2)) AND (("products"."price") < '8' :: numeric(7,2)))) LIMIT ('20') :: integer) AS "products_1") AS "__sel_1") AS "__sel_1" ON ('true')) AS "__sel_0") AS "__sel_0"
|
||||||
=== RUN TestCompileQuery/aggFunction
|
=== RUN TestCompileQuery/aggFunction
|
||||||
SELECT json_build_object('products', "__sel_0"."json") as "__root" FROM (SELECT coalesce(json_agg("__sel_0"."json"), '[]') as "json" FROM (SELECT json_build_object('name', "products_0"."name", 'count_price', "products_0"."count_price") AS "json" FROM (SELECT "products"."name", price("products"."price") AS "count_price" FROM "products" WHERE (((("products"."price") > '0' :: numeric(7,2)) AND (("products"."price") < '8' :: numeric(7,2)))) GROUP BY "products"."name" LIMIT ('20') :: integer) AS "products_0") AS "__sel_0") AS "__sel_0"
|
SELECT json_build_object('products', "__sel_0"."json") as "__root" FROM (SELECT coalesce(json_agg("__sel_0"."json"), '[]') as "json" FROM (SELECT json_build_object('name', "products_0"."name", 'count_price', "products_0"."count_price") AS "json" FROM (SELECT "products"."name", count("products"."price") AS "count_price" FROM "products" WHERE (((("products"."price") > '0' :: numeric(7,2)) AND (("products"."price") < '8' :: numeric(7,2)))) GROUP BY "products"."name" LIMIT ('20') :: integer) AS "products_0") AS "__sel_0") AS "__sel_0"
|
||||||
=== RUN TestCompileQuery/aggFunctionBlockedByCol
|
=== RUN TestCompileQuery/aggFunctionBlockedByCol
|
||||||
SELECT json_build_object('products', "__sel_0"."json") as "__root" FROM (SELECT coalesce(json_agg("__sel_0"."json"), '[]') as "json" FROM (SELECT json_build_object('name', "products_0"."name") AS "json" FROM (SELECT "products"."name" FROM "products" GROUP BY "products"."name" LIMIT ('20') :: integer) AS "products_0") AS "__sel_0") AS "__sel_0"
|
SELECT json_build_object('products', "__sel_0"."json") as "__root" FROM (SELECT coalesce(json_agg("__sel_0"."json"), '[]') as "json" FROM (SELECT json_build_object('name', "products_0"."name") AS "json" FROM (SELECT "products"."name" FROM "products" GROUP BY "products"."name" LIMIT ('20') :: integer) AS "products_0") AS "__sel_0") AS "__sel_0"
|
||||||
=== RUN TestCompileQuery/aggFunctionDisabled
|
=== RUN TestCompileQuery/aggFunctionDisabled
|
||||||
SELECT json_build_object('products', "__sel_0"."json") as "__root" FROM (SELECT coalesce(json_agg("__sel_0"."json"), '[]') as "json" FROM (SELECT json_build_object('name', "products_0"."name") AS "json" FROM (SELECT "products"."name" FROM "products" GROUP BY "products"."name" LIMIT ('20') :: integer) AS "products_0") AS "__sel_0") AS "__sel_0"
|
SELECT json_build_object('products', "__sel_0"."json") as "__root" FROM (SELECT coalesce(json_agg("__sel_0"."json"), '[]') as "json" FROM (SELECT json_build_object('name', "products_0"."name") AS "json" FROM (SELECT "products"."name" FROM "products" GROUP BY "products"."name" LIMIT ('20') :: integer) AS "products_0") AS "__sel_0") AS "__sel_0"
|
||||||
=== RUN TestCompileQuery/aggFunctionWithFilter
|
=== RUN TestCompileQuery/aggFunctionWithFilter
|
||||||
SELECT json_build_object('products', "__sel_0"."json") as "__root" FROM (SELECT coalesce(json_agg("__sel_0"."json"), '[]') as "json" FROM (SELECT json_build_object('id', "products_0"."id", 'max_price', "products_0"."max_price") AS "json" FROM (SELECT "products"."id", pri("products"."price") AS "max_price" FROM "products" WHERE ((((("products"."price") > '0' :: numeric(7,2)) AND (("products"."price") < '8' :: numeric(7,2))) AND (("products"."id") > '10' :: bigint))) GROUP BY "products"."id" LIMIT ('20') :: integer) AS "products_0") AS "__sel_0") AS "__sel_0"
|
SELECT json_build_object('products', "__sel_0"."json") as "__root" FROM (SELECT coalesce(json_agg("__sel_0"."json"), '[]') as "json" FROM (SELECT json_build_object('id', "products_0"."id", 'max_price', "products_0"."max_price") AS "json" FROM (SELECT "products"."id", max("products"."price") AS "max_price" FROM "products" WHERE ((((("products"."price") > '0' :: numeric(7,2)) AND (("products"."price") < '8' :: numeric(7,2))) AND (("products"."id") > '10' :: bigint))) GROUP BY "products"."id" LIMIT ('20') :: integer) AS "products_0") AS "__sel_0") AS "__sel_0"
|
||||||
=== RUN TestCompileQuery/syntheticTables
|
=== RUN TestCompileQuery/syntheticTables
|
||||||
SELECT json_build_object('me', "__sel_0"."json") as "__root" FROM (SELECT json_build_object() AS "json" FROM (SELECT "users"."email" FROM "users" WHERE ((("users"."id") = '{{user_id}}' :: bigint)) LIMIT ('1') :: integer) AS "users_0") AS "__sel_0"
|
SELECT json_build_object('me', "__sel_0"."json") as "__root" FROM (SELECT json_build_object() AS "json" FROM (SELECT "users"."email" FROM "users" WHERE ((("users"."id") = '{{user_id}}' :: bigint)) LIMIT ('1') :: integer) AS "users_0") AS "__sel_0"
|
||||||
=== RUN TestCompileQuery/queryWithVariables
|
=== RUN TestCompileQuery/queryWithVariables
|
||||||
@ -88,13 +88,13 @@ SELECT json_build_object('customer', "__sel_0"."json", 'user', "__sel_1"."json",
|
|||||||
SELECT json_build_object('products', "__sel_0"."json") as "__root" FROM (SELECT coalesce(json_agg("__sel_0"."json"), '[]') as "json" FROM (SELECT json_build_object('id', "products_0"."id", 'name', "products_0"."name", 'tag_count', "__sel_1"."json") AS "json" FROM (SELECT "products"."id", "products"."name" FROM "products" LIMIT ('20') :: integer) AS "products_0" LEFT OUTER JOIN LATERAL (SELECT json_build_object('count', "tag_count_1"."count", 'tags', "__sel_2"."json") AS "json" FROM (SELECT "tag_count"."count", "tag_count"."tag_id" FROM "products", json_to_recordset("products"."tag_count") AS "tag_count"(tag_id bigint, count int) WHERE ((("products"."id") = ("products_0"."id"))) LIMIT ('1') :: integer) AS "tag_count_1" LEFT OUTER JOIN LATERAL (SELECT coalesce(json_agg("__sel_2"."json"), '[]') as "json" FROM (SELECT json_build_object('name', "tags_2"."name") AS "json" FROM (SELECT "tags"."name" FROM "tags" WHERE ((("tags"."id") = ("tag_count_1"."tag_id"))) LIMIT ('20') :: integer) AS "tags_2") AS "__sel_2") AS "__sel_2" ON ('true')) AS "__sel_1" ON ('true')) AS "__sel_0") AS "__sel_0"
|
SELECT json_build_object('products', "__sel_0"."json") as "__root" FROM (SELECT coalesce(json_agg("__sel_0"."json"), '[]') as "json" FROM (SELECT json_build_object('id', "products_0"."id", 'name', "products_0"."name", 'tag_count', "__sel_1"."json") AS "json" FROM (SELECT "products"."id", "products"."name" FROM "products" LIMIT ('20') :: integer) AS "products_0" LEFT OUTER JOIN LATERAL (SELECT json_build_object('count', "tag_count_1"."count", 'tags', "__sel_2"."json") AS "json" FROM (SELECT "tag_count"."count", "tag_count"."tag_id" FROM "products", json_to_recordset("products"."tag_count") AS "tag_count"(tag_id bigint, count int) WHERE ((("products"."id") = ("products_0"."id"))) LIMIT ('1') :: integer) AS "tag_count_1" LEFT OUTER JOIN LATERAL (SELECT coalesce(json_agg("__sel_2"."json"), '[]') as "json" FROM (SELECT json_build_object('name', "tags_2"."name") AS "json" FROM (SELECT "tags"."name" FROM "tags" WHERE ((("tags"."id") = ("tag_count_1"."tag_id"))) LIMIT ('20') :: integer) AS "tags_2") AS "__sel_2") AS "__sel_2" ON ('true')) AS "__sel_1" ON ('true')) AS "__sel_0") AS "__sel_0"
|
||||||
=== RUN TestCompileQuery/withCursor
|
=== RUN TestCompileQuery/withCursor
|
||||||
SELECT json_build_object('products', "__sel_0"."json", 'products_cursor', "__sel_0"."cursor") as "__root" FROM (SELECT coalesce(json_agg("__sel_0"."json"), '[]') as "json", CONCAT_WS(',', max("__cur_0"), max("__cur_1")) as "cursor" FROM (SELECT json_build_object('name', "products_0"."name") AS "json", LAST_VALUE("products_0"."price") OVER() AS "__cur_0", LAST_VALUE("products_0"."id") OVER() AS "__cur_1" FROM (WITH "__cur" AS (SELECT a[1] as "price", a[2] as "id" FROM string_to_array('{{cursor}}', ',') as a) SELECT "products"."name", "products"."id", "products"."price" FROM "products", "__cur" WHERE (((("products"."price") < "__cur"."price" :: numeric(7,2)) OR ((("products"."price") = "__cur"."price" :: numeric(7,2)) AND (("products"."id") > "__cur"."id" :: bigint)))) ORDER BY "products"."price" DESC, "products"."id" ASC LIMIT ('20') :: integer) AS "products_0") AS "__sel_0") AS "__sel_0"
|
SELECT json_build_object('products', "__sel_0"."json", 'products_cursor', "__sel_0"."cursor") as "__root" FROM (SELECT coalesce(json_agg("__sel_0"."json"), '[]') as "json", CONCAT_WS(',', max("__cur_0"), max("__cur_1")) as "cursor" FROM (SELECT json_build_object('name', "products_0"."name") AS "json", LAST_VALUE("products_0"."price") OVER() AS "__cur_0", LAST_VALUE("products_0"."id") OVER() AS "__cur_1" FROM (WITH "__cur" AS (SELECT a[1] as "price", a[2] as "id" FROM string_to_array('{{cursor}}', ',') as a) SELECT "products"."name", "products"."id", "products"."price" FROM "products", "__cur" WHERE (((("products"."price") < "__cur"."price" :: numeric(7,2)) OR ((("products"."price") = "__cur"."price" :: numeric(7,2)) AND (("products"."id") > "__cur"."id" :: bigint)))) ORDER BY "products"."price" DESC, "products"."id" ASC LIMIT ('20') :: integer) AS "products_0") AS "__sel_0") AS "__sel_0"
|
||||||
=== RUN TestCompileQuery/skipUserIDForAnonRole
|
=== RUN TestCompileQuery/nullForAuthRequiredInAnon
|
||||||
SELECT json_build_object('products', "__sel_0"."json") as "__root" FROM (SELECT coalesce(json_agg("__sel_0"."json"), '[]') as "json" FROM (SELECT json_build_object('id', "products_0"."id", 'name', "products_0"."name") AS "json" FROM (SELECT "products"."id", "products"."name", "products"."user_id" FROM "products" LIMIT ('20') :: integer) AS "products_0") AS "__sel_0") AS "__sel_0"
|
SELECT json_build_object('products', "__sel_0"."json") as "__root" FROM (SELECT coalesce(json_agg("__sel_0"."json"), '[]') as "json" FROM (SELECT json_build_object('id', "products_0"."id", 'name', "products_0"."name", 'user', NULL) AS "json" FROM (SELECT "products"."id", "products"."name", "products"."user_id" FROM "products" LIMIT ('20') :: integer) AS "products_0") AS "__sel_0") AS "__sel_0"
|
||||||
=== RUN TestCompileQuery/blockedQuery
|
=== RUN TestCompileQuery/blockedQuery
|
||||||
SELECT json_build_object('user', "__sel_0"."json") as "__root" FROM (SELECT json_build_object('id', "users_0"."id", 'full_name', "users_0"."full_name", 'email', "users_0"."email") AS "json" FROM (SELECT "users"."id", "users"."full_name", "users"."email" FROM "users" WHERE (false) LIMIT ('1') :: integer) AS "users_0") AS "__sel_0"
|
SELECT json_build_object('user', "__sel_0"."json") as "__root" FROM (SELECT json_build_object('id', "users_0"."id", 'full_name', "users_0"."full_name", 'email', "users_0"."email") AS "json" FROM (SELECT "users"."id", "users"."full_name", "users"."email" FROM "users" WHERE (false) LIMIT ('1') :: integer) AS "users_0") AS "__sel_0"
|
||||||
=== RUN TestCompileQuery/blockedFunctions
|
=== RUN TestCompileQuery/blockedFunctions
|
||||||
SELECT json_build_object('users', "__sel_0"."json") as "__root" FROM (SELECT coalesce(json_agg("__sel_0"."json"), '[]') as "json" FROM (SELECT json_build_object('email', "users_0"."email") AS "json" FROM (SELECT , "users"."email" FROM "users" WHERE (false) GROUP BY "users"."email" LIMIT ('20') :: integer) AS "users_0") AS "__sel_0") AS "__sel_0"
|
SELECT json_build_object('users', "__sel_0"."json") as "__root" FROM (SELECT coalesce(json_agg("__sel_0"."json"), '[]') as "json" FROM (SELECT json_build_object('email', "users_0"."email") AS "json" FROM (SELECT , "users"."email" FROM "users" WHERE (false) GROUP BY "users"."email" LIMIT ('20') :: integer) AS "users_0") AS "__sel_0") AS "__sel_0"
|
||||||
--- PASS: TestCompileQuery (0.02s)
|
--- PASS: TestCompileQuery (0.03s)
|
||||||
--- PASS: TestCompileQuery/withComplexArgs (0.00s)
|
--- PASS: TestCompileQuery/withComplexArgs (0.00s)
|
||||||
--- PASS: TestCompileQuery/withWhereAndList (0.00s)
|
--- PASS: TestCompileQuery/withWhereAndList (0.00s)
|
||||||
--- PASS: TestCompileQuery/withWhereIsNull (0.00s)
|
--- PASS: TestCompileQuery/withWhereIsNull (0.00s)
|
||||||
@ -116,7 +116,7 @@ SELECT json_build_object('users', "__sel_0"."json") as "__root" FROM (SELECT coa
|
|||||||
--- PASS: TestCompileQuery/multiRoot (0.00s)
|
--- PASS: TestCompileQuery/multiRoot (0.00s)
|
||||||
--- PASS: TestCompileQuery/jsonColumnAsTable (0.00s)
|
--- PASS: TestCompileQuery/jsonColumnAsTable (0.00s)
|
||||||
--- PASS: TestCompileQuery/withCursor (0.00s)
|
--- PASS: TestCompileQuery/withCursor (0.00s)
|
||||||
--- PASS: TestCompileQuery/skipUserIDForAnonRole (0.00s)
|
--- PASS: TestCompileQuery/nullForAuthRequiredInAnon (0.00s)
|
||||||
--- PASS: TestCompileQuery/blockedQuery (0.00s)
|
--- PASS: TestCompileQuery/blockedQuery (0.00s)
|
||||||
--- PASS: TestCompileQuery/blockedFunctions (0.00s)
|
--- PASS: TestCompileQuery/blockedFunctions (0.00s)
|
||||||
=== RUN TestCompileUpdate
|
=== RUN TestCompileUpdate
|
||||||
@ -125,8 +125,8 @@ WITH "_sg_input" AS (SELECT '{{update}}' :: json AS j), "products" AS (UPDATE "p
|
|||||||
=== RUN TestCompileUpdate/simpleUpdateWithPresets
|
=== RUN TestCompileUpdate/simpleUpdateWithPresets
|
||||||
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") = '{{user_id}}' :: bigint) RETURNING "products".*) SELECT json_build_object('product', "__sel_0"."json") as "__root" FROM (SELECT json_build_object('id', "products_0"."id") AS "json" FROM (SELECT "products"."id" FROM "products" LIMIT ('1') :: integer) AS "products_0") AS "__sel_0"
|
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") = '{{user_id}}' :: bigint) RETURNING "products".*) SELECT json_build_object('product', "__sel_0"."json") as "__root" FROM (SELECT json_build_object('id', "products_0"."id") AS "json" FROM (SELECT "products"."id" FROM "products" LIMIT ('1') :: integer) AS "products_0") AS "__sel_0"
|
||||||
=== RUN TestCompileUpdate/nestedUpdateManyToMany
|
=== RUN TestCompileUpdate/nestedUpdateManyToMany
|
||||||
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") = '{{id}}' :: bigint) 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_build_object('purchase', "__sel_0"."json") as "__root" FROM (SELECT json_build_object('sale_type', "purchases_0"."sale_type", 'quantity', "purchases_0"."quantity", 'due_date', "purchases_0"."due_date", 'product', "__sel_1"."json", 'customer', "__sel_2"."json") AS "json" 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 json_build_object('id', "customers_2"."id", 'full_name', "customers_2"."full_name", 'email', "customers_2"."email") AS "json" FROM (SELECT "customers"."id", "customers"."full_name", "customers"."email" FROM "customers" WHERE ((("customers"."id") = ("purchases_0"."customer_id"))) LIMIT ('1') :: integer) AS "customers_2") AS "__sel_2" ON ('true') LEFT OUTER JOIN LATERAL (SELECT json_build_object('id', "products_1"."id", 'name', "products_1"."name", 'price', "products_1"."price") AS "json" FROM (SELECT "products"."id", "products"."name", "products"."price" FROM "products" WHERE ((("products"."id") = ("purchases_0"."product_id"))) LIMIT ('1') :: integer) AS "products_1") AS "__sel_1" ON ('true')) AS "__sel_0"
|
|
||||||
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") = '{{id}}' :: bigint) 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_build_object('purchase', "__sel_0"."json") as "__root" FROM (SELECT json_build_object('sale_type', "purchases_0"."sale_type", 'quantity', "purchases_0"."quantity", 'due_date', "purchases_0"."due_date", 'product', "__sel_1"."json", 'customer', "__sel_2"."json") AS "json" 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 json_build_object('id', "customers_2"."id", 'full_name', "customers_2"."full_name", 'email', "customers_2"."email") AS "json" FROM (SELECT "customers"."id", "customers"."full_name", "customers"."email" FROM "customers" WHERE ((("customers"."id") = ("purchases_0"."customer_id"))) LIMIT ('1') :: integer) AS "customers_2") AS "__sel_2" ON ('true') LEFT OUTER JOIN LATERAL (SELECT json_build_object('id', "products_1"."id", 'name', "products_1"."name", 'price', "products_1"."price") AS "json" FROM (SELECT "products"."id", "products"."name", "products"."price" FROM "products" WHERE ((("products"."id") = ("purchases_0"."product_id"))) LIMIT ('1') :: integer) AS "products_1") AS "__sel_1" ON ('true')) AS "__sel_0"
|
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") = '{{id}}' :: bigint) 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_build_object('purchase', "__sel_0"."json") as "__root" FROM (SELECT json_build_object('sale_type', "purchases_0"."sale_type", 'quantity', "purchases_0"."quantity", 'due_date', "purchases_0"."due_date", 'product', "__sel_1"."json", 'customer', "__sel_2"."json") AS "json" 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 json_build_object('id', "customers_2"."id", 'full_name', "customers_2"."full_name", 'email', "customers_2"."email") AS "json" FROM (SELECT "customers"."id", "customers"."full_name", "customers"."email" FROM "customers" WHERE ((("customers"."id") = ("purchases_0"."customer_id"))) LIMIT ('1') :: integer) AS "customers_2") AS "__sel_2" ON ('true') LEFT OUTER JOIN LATERAL (SELECT json_build_object('id', "products_1"."id", 'name', "products_1"."name", 'price', "products_1"."price") AS "json" FROM (SELECT "products"."id", "products"."name", "products"."price" FROM "products" WHERE ((("products"."id") = ("purchases_0"."product_id"))) LIMIT ('1') :: integer) AS "products_1") AS "__sel_1" ON ('true')) AS "__sel_0"
|
||||||
|
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") = '{{id}}' :: bigint) 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_build_object('purchase', "__sel_0"."json") as "__root" FROM (SELECT json_build_object('sale_type', "purchases_0"."sale_type", 'quantity', "purchases_0"."quantity", 'due_date', "purchases_0"."due_date", 'product', "__sel_1"."json", 'customer', "__sel_2"."json") AS "json" 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 json_build_object('id', "customers_2"."id", 'full_name', "customers_2"."full_name", 'email', "customers_2"."email") AS "json" FROM (SELECT "customers"."id", "customers"."full_name", "customers"."email" FROM "customers" WHERE ((("customers"."id") = ("purchases_0"."customer_id"))) LIMIT ('1') :: integer) AS "customers_2") AS "__sel_2" ON ('true') LEFT OUTER JOIN LATERAL (SELECT json_build_object('id', "products_1"."id", 'name', "products_1"."name", 'price', "products_1"."price") AS "json" FROM (SELECT "products"."id", "products"."name", "products"."price" FROM "products" WHERE ((("products"."id") = ("purchases_0"."product_id"))) LIMIT ('1') :: integer) AS "products_1") AS "__sel_1" ON ('true')) AS "__sel_0"
|
||||||
=== RUN TestCompileUpdate/nestedUpdateOneToMany
|
=== RUN TestCompileUpdate/nestedUpdateOneToMany
|
||||||
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") = '8' :: bigint) 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"= ((i.j->'product'->'where'->>'id'))::bigint) RETURNING "products".*) SELECT json_build_object('user', "__sel_0"."json") as "__root" FROM (SELECT json_build_object('id', "users_0"."id", 'full_name', "users_0"."full_name", 'email', "users_0"."email", 'product', "__sel_1"."json") AS "json" FROM (SELECT "users"."id", "users"."full_name", "users"."email" FROM "users" LIMIT ('1') :: integer) AS "users_0" LEFT OUTER JOIN LATERAL (SELECT json_build_object('id', "products_1"."id", 'name', "products_1"."name", 'price', "products_1"."price") AS "json" FROM (SELECT "products"."id", "products"."name", "products"."price" FROM "products" WHERE ((("products"."user_id") = ("users_0"."id"))) LIMIT ('1') :: integer) AS "products_1") AS "__sel_1" ON ('true')) AS "__sel_0"
|
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") = '8' :: bigint) 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"= ((i.j->'product'->'where'->>'id'))::bigint) RETURNING "products".*) SELECT json_build_object('user', "__sel_0"."json") as "__root" FROM (SELECT json_build_object('id', "users_0"."id", 'full_name', "users_0"."full_name", 'email', "users_0"."email", 'product', "__sel_1"."json") AS "json" FROM (SELECT "users"."id", "users"."full_name", "users"."email" FROM "users" LIMIT ('1') :: integer) AS "users_0" LEFT OUTER JOIN LATERAL (SELECT json_build_object('id', "products_1"."id", 'name', "products_1"."name", 'price', "products_1"."price") AS "json" FROM (SELECT "products"."id", "products"."name", "products"."price" FROM "products" WHERE ((("products"."user_id") = ("users_0"."id"))) LIMIT ('1') :: integer) AS "products_1") AS "__sel_1" ON ('true')) AS "__sel_0"
|
||||||
=== RUN TestCompileUpdate/nestedUpdateOneToOne
|
=== RUN TestCompileUpdate/nestedUpdateOneToOne
|
||||||
@ -148,4 +148,4 @@ WITH "_sg_input" AS (SELECT '{{data}}' :: json AS j), "_x_users" AS (SELECT * FR
|
|||||||
--- PASS: TestCompileUpdate/nestedUpdateOneToOneWithConnect (0.00s)
|
--- PASS: TestCompileUpdate/nestedUpdateOneToOneWithConnect (0.00s)
|
||||||
--- PASS: TestCompileUpdate/nestedUpdateOneToOneWithDisconnect (0.00s)
|
--- PASS: TestCompileUpdate/nestedUpdateOneToOneWithDisconnect (0.00s)
|
||||||
PASS
|
PASS
|
||||||
ok github.com/dosco/super-graph/psql 0.716s
|
ok github.com/dosco/super-graph/psql (cached)
|
||||||
|
@ -222,6 +222,10 @@ func (c *compilerContext) renderDelete(qc *qcode.QCode, w io.Writer,
|
|||||||
quoted(c.w, ti.Name)
|
quoted(c.w, ti.Name)
|
||||||
io.WriteString(c.w, ` WHERE `)
|
io.WriteString(c.w, ` WHERE `)
|
||||||
|
|
||||||
|
if root.Where == nil {
|
||||||
|
return 0, errors.New("'where' clause missing in delete mutation")
|
||||||
|
}
|
||||||
|
|
||||||
if err := c.renderWhere(root, ti); err != nil {
|
if err := c.renderWhere(root, ti); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ type parserType int32
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
maxFields = 100
|
maxFields = 100
|
||||||
maxArgs = 10
|
maxArgs = 25
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -242,7 +242,8 @@ func (p *Parser) parseOp() (*Operation, error) {
|
|||||||
|
|
||||||
if p.peek(itemArgsOpen) {
|
if p.peek(itemArgsOpen) {
|
||||||
p.ignore()
|
p.ignore()
|
||||||
op.Args, err = p.parseArgs(op.Args)
|
|
||||||
|
op.Args, err = p.parseOpParams(op.Args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -338,6 +339,13 @@ func (p *Parser) parseFields(fields []Field) ([]Field, error) {
|
|||||||
if p.peek(itemObjOpen) {
|
if p.peek(itemObjOpen) {
|
||||||
p.ignore()
|
p.ignore()
|
||||||
st.Push(f.ID)
|
st.Push(f.ID)
|
||||||
|
|
||||||
|
} else if p.peek(itemObjClose) {
|
||||||
|
if st.Len() == 0 {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -371,6 +379,22 @@ func (p *Parser) parseField(f *Field) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Parser) parseOpParams(args []Arg) ([]Arg, error) {
|
||||||
|
for {
|
||||||
|
if len(args) >= maxArgs {
|
||||||
|
return nil, fmt.Errorf("too many args (max %d)", maxArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.peek(itemArgsClose) {
|
||||||
|
p.ignore()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
return args, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Parser) parseArgs(args []Arg) ([]Arg, error) {
|
func (p *Parser) parseArgs(args []Arg) ([]Arg, error) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
@ -383,6 +407,7 @@ func (p *Parser) parseArgs(args []Arg) ([]Arg, error) {
|
|||||||
p.ignore()
|
p.ignore()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if !p.peek(itemName) {
|
if !p.peek(itemName) {
|
||||||
return nil, errors.New("expecting an argument name")
|
return nil, errors.New("expecting an argument name")
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
if [ $1 = "secrets" ]
|
if [ $1 = "secrets" ]
|
||||||
then
|
then
|
||||||
sops --config ./config "${@:2}"
|
./sops --config ./config "${@:2}"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -26,11 +26,13 @@ type config struct {
|
|||||||
EnableTracing bool `mapstructure:"enable_tracing"`
|
EnableTracing bool `mapstructure:"enable_tracing"`
|
||||||
UseAllowList bool `mapstructure:"use_allow_list"`
|
UseAllowList bool `mapstructure:"use_allow_list"`
|
||||||
Production bool
|
Production bool
|
||||||
WatchAndReload bool `mapstructure:"reload_on_config_change"`
|
WatchAndReload bool `mapstructure:"reload_on_config_change"`
|
||||||
AuthFailBlock bool `mapstructure:"auth_fail_block"`
|
AuthFailBlock bool `mapstructure:"auth_fail_block"`
|
||||||
SeedFile string `mapstructure:"seed_file"`
|
SeedFile string `mapstructure:"seed_file"`
|
||||||
MigrationsPath string `mapstructure:"migrations_path"`
|
MigrationsPath string `mapstructure:"migrations_path"`
|
||||||
SecretKey string `mapstructure:"secret_key"`
|
SecretKey string `mapstructure:"secret_key"`
|
||||||
|
AllowedOrigins []string `mapstructure:"cors_allowed_origins"`
|
||||||
|
DebugCORS bool `mapstructure:"cors_debug"`
|
||||||
|
|
||||||
Inflections map[string]string
|
Inflections map[string]string
|
||||||
|
|
||||||
@ -205,8 +207,8 @@ func newConfig(name string) *viper.Viper {
|
|||||||
vi.SetDefault("env", "development")
|
vi.SetDefault("env", "development")
|
||||||
|
|
||||||
vi.BindEnv("env", "GO_ENV") //nolint: errcheck
|
vi.BindEnv("env", "GO_ENV") //nolint: errcheck
|
||||||
vi.BindEnv("HOST", "HOST") //nolint: errcheck
|
vi.BindEnv("host", "HOST") //nolint: errcheck
|
||||||
vi.BindEnv("PORT", "PORT") //nolint: errcheck
|
vi.BindEnv("port", "PORT") //nolint: errcheck
|
||||||
|
|
||||||
vi.SetDefault("auth.rails.max_idle", 80)
|
vi.SetDefault("auth.rails.max_idle", 80)
|
||||||
vi.SetDefault("auth.rails.max_active", 12000)
|
vi.SetDefault("auth.rails.max_active", 12000)
|
||||||
|
@ -152,6 +152,10 @@ func (c *coreContext) resolvePreparedSQL() ([]byte, *stmt, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if root, err = encryptCursor(ps.st.qc, root); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return root, &ps.st, nil
|
return root, &ps.st, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,12 +59,6 @@ func buildRoleStmt(gql, vars []byte, role string) ([]stmt, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// For the 'anon' role in production only compile
|
|
||||||
// queries for tables defined in the config file.
|
|
||||||
if conf.Production && ro.Name == "anon" && !hasTablesWithConfig(qc, ro) {
|
|
||||||
return nil, errors.New("query contains tables with no 'anon' role config")
|
|
||||||
}
|
|
||||||
|
|
||||||
stmts := []stmt{stmt{role: ro, qc: qc}}
|
stmts := []stmt{stmt{role: ro, qc: qc}}
|
||||||
w := &bytes.Buffer{}
|
w := &bytes.Buffer{}
|
||||||
|
|
||||||
@ -90,7 +84,7 @@ func buildMultiStmt(gql, vars []byte) ([]stmt, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(conf.RolesQuery) == 0 {
|
if len(conf.RolesQuery) == 0 {
|
||||||
return buildRoleStmt(gql, vars, "user")
|
return nil, errors.New("roles_query not defined")
|
||||||
}
|
}
|
||||||
|
|
||||||
stmts := make([]stmt, 0, len(conf.Roles))
|
stmts := make([]stmt, 0, len(conf.Roles))
|
||||||
@ -99,6 +93,7 @@ func buildMultiStmt(gql, vars []byte) ([]stmt, error) {
|
|||||||
for i := 0; i < len(conf.Roles); i++ {
|
for i := 0; i < len(conf.Roles); i++ {
|
||||||
role := &conf.Roles[i]
|
role := &conf.Roles[i]
|
||||||
|
|
||||||
|
// skip anon as it's not included in the combined multi-statement
|
||||||
if role.Name == "anon" {
|
if role.Name == "anon" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
18
serv/http.go
18
serv/http.go
@ -8,6 +8,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/cors"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -61,6 +63,20 @@ type resolver struct {
|
|||||||
Duration time.Duration `json:"duration"`
|
Duration time.Duration `json:"duration"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func apiV1Handler() http.Handler {
|
||||||
|
h := withAuth(http.HandlerFunc(apiV1), conf.Auth)
|
||||||
|
|
||||||
|
if len(conf.AllowedOrigins) != 0 {
|
||||||
|
c := cors.New(cors.Options{
|
||||||
|
AllowedOrigins: conf.AllowedOrigins,
|
||||||
|
AllowCredentials: true,
|
||||||
|
Debug: conf.DebugCORS,
|
||||||
|
})
|
||||||
|
h = c.Handler(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
return h
|
||||||
|
}
|
||||||
func apiV1(w http.ResponseWriter, r *http.Request) {
|
func apiV1(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := &coreContext{Context: r.Context()}
|
ctx := &coreContext{Context: r.Context()}
|
||||||
|
|
||||||
@ -101,7 +117,7 @@ func apiV1(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errlog.Error().Err(err).Msg("failed to handle request")
|
errlog.Error().Err(err).Msg(ctx.req.Query)
|
||||||
errorResp(w, err)
|
errorResp(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/dosco/super-graph/allow"
|
"github.com/dosco/super-graph/allow"
|
||||||
|
"github.com/dosco/super-graph/psql"
|
||||||
"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/jackc/pgx/v4"
|
||||||
@ -82,12 +83,6 @@ func prepareStmt(item allow.Item) error {
|
|||||||
qt := qcode.GetQType(gql)
|
qt := qcode.GetQType(gql)
|
||||||
q := []byte(gql)
|
q := []byte(gql)
|
||||||
|
|
||||||
if len(vars) == 0 {
|
|
||||||
logger.Debug().Msgf("Prepared statement:\n%s\n", gql)
|
|
||||||
} else {
|
|
||||||
logger.Debug().Msgf("Prepared statement:\n%s\n%s\n", vars, gql)
|
|
||||||
}
|
|
||||||
|
|
||||||
tx, err := db.Begin(context.Background())
|
tx, err := db.Begin(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -109,7 +104,7 @@ func prepareStmt(item allow.Item) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Debug().Msg("Prepared statement role: user")
|
logger.Debug().Msgf("Prepared statement 'query %s' (user)", item.Name)
|
||||||
|
|
||||||
err = prepare(tx, stmts1, stmtHash(item.Name, "user"))
|
err = prepare(tx, stmts1, stmtHash(item.Name, "user"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -117,9 +112,12 @@ func prepareStmt(item allow.Item) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if conf.isAnonRoleDefined() {
|
if conf.isAnonRoleDefined() {
|
||||||
logger.Debug().Msg("Prepared statement for role: anon")
|
logger.Debug().Msgf("Prepared statement 'query %s' (anon)", item.Name)
|
||||||
|
|
||||||
stmts2, err := buildRoleStmt(q, vars, "anon")
|
stmts2, err := buildRoleStmt(q, vars, "anon")
|
||||||
|
if err == psql.ErrAllTablesSkipped {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -132,11 +130,17 @@ func prepareStmt(item allow.Item) error {
|
|||||||
|
|
||||||
case qcode.QTMutation:
|
case qcode.QTMutation:
|
||||||
for _, role := range conf.Roles {
|
for _, role := range conf.Roles {
|
||||||
logger.Debug().Msgf("Prepared statement for role: %s", role.Name)
|
logger.Debug().Msgf("Prepared statement 'mutation %s' (%s)", item.Name, role.Name)
|
||||||
|
|
||||||
stmts, err := buildRoleStmt(q, vars, role.Name)
|
stmts, err := buildRoleStmt(q, vars, role.Name)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
if len(item.Vars) == 0 {
|
||||||
|
logger.Warn().Err(err).Msg(item.Query)
|
||||||
|
} else {
|
||||||
|
logger.Warn().Err(err).Msgf("%s %s", item.Vars, item.Query)
|
||||||
|
}
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
err = prepare(tx, stmts, stmtHash(item.Name, role.Name))
|
err = prepare(tx, stmts, stmtHash(item.Name, role.Name))
|
||||||
|
@ -108,7 +108,11 @@ 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 != nil && !conf.Production && strings.HasSuffix(event.Name, "/allow.list") {
|
if conf != nil && strings.HasSuffix(event.Name, "/allow.list") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if conf.Production {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,7 +154,7 @@ func routeHandler() (http.Handler, error) {
|
|||||||
|
|
||||||
routes := map[string]http.Handler{
|
routes := map[string]http.Handler{
|
||||||
"/health": http.HandlerFunc(health),
|
"/health": http.HandlerFunc(health),
|
||||||
"/api/v1/graphql": withAuth(http.HandlerFunc(apiV1), conf.Auth),
|
"/api/v1/graphql": apiV1Handler(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := setActionRoutes(routes); err != nil {
|
if err := setActionRoutes(routes); err != nil {
|
||||||
|
@ -36,6 +36,15 @@ migrations_path: ./config/migrations
|
|||||||
# encrypting the cursor data
|
# encrypting the cursor data
|
||||||
secret_key: supercalifajalistics
|
secret_key: supercalifajalistics
|
||||||
|
|
||||||
|
# CORS: A list of origins a cross-domain request can be executed from.
|
||||||
|
# If the special * value is present in the list, all origins will be allowed.
|
||||||
|
# An origin may contain a wildcard (*) to replace 0 or more
|
||||||
|
# characters (i.e.: http://*.domain.com).
|
||||||
|
cors_allowed_origins: ["*"]
|
||||||
|
|
||||||
|
# Debug Cross Origin Resource Sharing requests
|
||||||
|
cors_debug: false
|
||||||
|
|
||||||
# Postgres related environment Variables
|
# Postgres related environment Variables
|
||||||
# SG_DATABASE_HOST
|
# SG_DATABASE_HOST
|
||||||
# SG_DATABASE_PORT
|
# SG_DATABASE_PORT
|
||||||
|
@ -24,7 +24,11 @@ auth_fail_block: true
|
|||||||
# Latency tracing for database queries and remote joins
|
# Latency tracing for database queries and remote joins
|
||||||
# the resulting latency information is returned with the
|
# the resulting latency information is returned with the
|
||||||
# response
|
# response
|
||||||
enable_tracing: true
|
enable_tracing: false
|
||||||
|
|
||||||
|
# Watch the config folder and reload Super Graph
|
||||||
|
# with the new configs when a change is detected
|
||||||
|
reload_on_config_change: false
|
||||||
|
|
||||||
# File that points to the database seeding script
|
# File that points to the database seeding script
|
||||||
# seed_file: seed.js
|
# seed_file: seed.js
|
||||||
@ -36,6 +40,15 @@ enable_tracing: true
|
|||||||
# encrypting the cursor data
|
# encrypting the cursor data
|
||||||
# secret_key: supercalifajalistics
|
# secret_key: supercalifajalistics
|
||||||
|
|
||||||
|
# CORS: A list of origins a cross-domain request can be executed from.
|
||||||
|
# If the special * value is present in the list, all origins will be allowed.
|
||||||
|
# An origin may contain a wildcard (*) to replace 0 or more
|
||||||
|
# characters (i.e.: http://*.domain.com).
|
||||||
|
# cors_allowed_origins: ["*"]
|
||||||
|
|
||||||
|
# Debug Cross Origin Resource Sharing requests
|
||||||
|
# cors_debug: false
|
||||||
|
|
||||||
# Postgres related environment Variables
|
# Postgres related environment Variables
|
||||||
# SG_DATABASE_HOST
|
# SG_DATABASE_HOST
|
||||||
# SG_DATABASE_PORT
|
# SG_DATABASE_PORT
|
||||||
|
Reference in New Issue
Block a user