diff --git a/psql/columns.go b/psql/columns.go index e08da43..fa4027f 100644 --- a/psql/columns.go +++ b/psql/columns.go @@ -37,30 +37,33 @@ func (c *compilerContext) renderBaseColumns( colWithTable(c.w, ti.Name, cn) i++ continue - } - if isSearch && !isRealCol { + } else { switch { - case cn == "search_rank": + case isSearch && cn == "search_rank": if err := c.renderColumnSearchRank(sel, ti, col, i); err != nil { 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 { return nil, false, err } i++ + case cn == "__typename": + if err := c.renderColumnTypename(sel, ti, col, i); err != nil { + return nil, false, err + } + i++ + default: + if err := c.renderColumnFunction(sel, ti, col, i); err != nil { + return nil, false, err + } + isAgg = true + i++ } - } else { - if err := c.renderColumnFunction(sel, ti, col, i); err != nil { - return nil, false, err - } - isAgg = true - i++ - } } @@ -148,6 +151,20 @@ func (c *compilerContext) renderColumnSearchHeadline(sel *qcode.Select, ti *DBTa 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 { pl := funcPrefixLen(col.Name) // if pl == 0 { @@ -168,7 +185,7 @@ func (c *compilerContext) renderColumnFunction(sel *qcode.Select, ti *DBTableInf return nil } - fn := cn[0 : pl-1] + fn := col.Name[:pl-1] c.renderComma(columnsRendered) diff --git a/psql/query.go b/psql/query.go index ed01627..24983fc 100644 --- a/psql/query.go +++ b/psql/query.go @@ -506,22 +506,21 @@ func (c *compilerContext) renderJoinByName(table, parent string, id int32) error func (c *compilerContext) renderColumns(sel *qcode.Select, ti *DBTableInfo, skipped uint32) error { i := 0 + var cn string + for _, col := range sel.Cols { - n := funcPrefixLen(col.Name) - if n != 0 { + if n := funcPrefixLen(col.Name); n != 0 { if !sel.Functions { continue } - if len(sel.Allowed) != 0 { - if _, ok := sel.Allowed[col.Name[n:]]; !ok { - continue - } - } + cn = col.Name[n:] } else { - if len(sel.Allowed) != 0 { - if _, ok := sel.Allowed[col.Name]; !ok { - continue - } + cn = col.Name + } + + if len(sel.Allowed) != 0 { + if _, ok := sel.Allowed[cn]; !ok { + continue } } @@ -573,9 +572,6 @@ func (c *compilerContext) renderJoinColumns(sel *qcode.Select, ti *DBTableInfo, continue } childSel := &c.s[id] - if childSel.SkipRender { - continue - } if i != 0 { io.WriteString(c.w, ", ") @@ -583,6 +579,11 @@ func (c *compilerContext) renderJoinColumns(sel *qcode.Select, ti *DBTableInfo, squoted(c.w, childSel.FieldName) + if childSel.SkipRender { + io.WriteString(c.w, `, NULL`) + continue + } + io.WriteString(c.w, `, "__sel_`) int2string(c.w, childSel.ID) io.WriteString(c.w, `"."json"`) diff --git a/psql/query_test.go b/psql/query_test.go index fdb399c..be7a653 100644 --- a/psql/query_test.go +++ b/psql/query_test.go @@ -327,7 +327,7 @@ func jsonColumnAsTable(t *testing.T) { compileGQLToPSQL(t, gql, nil, "admin") } -func skipUserIDForAnonRole(t *testing.T) { +func nullForAuthRequiredInAnon(t *testing.T) { gql := `query { products { id @@ -387,7 +387,7 @@ func TestCompileQuery(t *testing.T) { t.Run("multiRoot", multiRoot) t.Run("jsonColumnAsTable", jsonColumnAsTable) t.Run("withCursor", withCursor) - t.Run("skipUserIDForAnonRole", skipUserIDForAnonRole) + t.Run("nullForAuthRequiredInAnon", nullForAuthRequiredInAnon) t.Run("blockedQuery", blockedQuery) t.Run("blockedFunctions", blockedFunctions) } diff --git a/psql/tests.sql b/psql/tests.sql index 9e42e79..9e12915 100644 --- a/psql/tests.sql +++ b/psql/tests.sql @@ -69,13 +69,13 @@ SELECT json_build_object('products', "__sel_0"."json") as "__root" FROM (SELECT === 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" === 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 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 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 -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 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 @@ -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" === 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" -=== RUN TestCompileQuery/skipUserIDForAnonRole -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" +=== 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", '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 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 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/withWhereAndList (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/jsonColumnAsTable (0.00s) --- PASS: TestCompileQuery/withCursor (0.00s) - --- PASS: TestCompileQuery/skipUserIDForAnonRole (0.00s) + --- PASS: TestCompileQuery/nullForAuthRequiredInAnon (0.00s) --- PASS: TestCompileQuery/blockedQuery (0.00s) --- PASS: TestCompileQuery/blockedFunctions (0.00s) === RUN TestCompileUpdate @@ -125,8 +125,8 @@ WITH "_sg_input" AS (SELECT '{{update}}' :: json AS j), "products" AS (UPDATE "p === 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" === 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".*), "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 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 @@ -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/nestedUpdateOneToOneWithDisconnect (0.00s) PASS -ok github.com/dosco/super-graph/psql 0.716s +ok github.com/dosco/super-graph/psql (cached) diff --git a/qcode/parse.go b/qcode/parse.go index 6c2ce92..581bebc 100644 --- a/qcode/parse.go +++ b/qcode/parse.go @@ -17,7 +17,7 @@ type parserType int32 const ( maxFields = 100 - maxArgs = 10 + maxArgs = 25 ) const ( @@ -242,7 +242,8 @@ func (p *Parser) parseOp() (*Operation, error) { if p.peek(itemArgsOpen) { p.ignore() - op.Args, err = p.parseArgs(op.Args) + + op.Args, err = p.parseOpParams(op.Args) if err != nil { return nil, err } @@ -371,6 +372,22 @@ func (p *Parser) parseField(f *Field) error { 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) { var err error @@ -383,6 +400,7 @@ func (p *Parser) parseArgs(args []Arg) ([]Arg, error) { p.ignore() break } + if !p.peek(itemName) { return nil, errors.New("expecting an argument name") } diff --git a/serv/http.go b/serv/http.go index 2ed5011..89e7406 100644 --- a/serv/http.go +++ b/serv/http.go @@ -117,7 +117,7 @@ func apiV1(w http.ResponseWriter, r *http.Request) { } if err != nil { - errlog.Error().Err(err).Msg("failed to handle request") + errlog.Error().Err(err).Msg(ctx.req.Query) errorResp(w, err) return }