diff --git a/core/internal/psql/query_test.go b/core/internal/psql/query_test.go index 113f84f..faf234b 100644 --- a/core/internal/psql/query_test.go +++ b/core/internal/psql/query_test.go @@ -381,6 +381,26 @@ func withFragment3(t *testing.T) { compileGQLToPSQL(t, gql, nil, "anon") } +func withInlineFragment(t *testing.T) { + gql := ` + query { + users { + ... on users { + id + email + } + created_at + ... on user { + first_name + last_name + } + } + } +` + + compileGQLToPSQL(t, gql, nil, "anon") +} + func withCursor(t *testing.T) { gql := `query { Products( @@ -477,6 +497,7 @@ func TestCompileQuery(t *testing.T) { t.Run("withFragment1", withFragment1) t.Run("withFragment2", withFragment2) t.Run("withFragment3", withFragment3) + t.Run("withInlineFragment", withInlineFragment) t.Run("jsonColumnAsTable", jsonColumnAsTable) t.Run("withCursor", withCursor) t.Run("nullForAuthRequiredInAnon", nullForAuthRequiredInAnon) diff --git a/core/internal/psql/tests.sql b/core/internal/psql/tests.sql index 53dfbd9..61b7dd9 100644 --- a/core/internal/psql/tests.sql +++ b/core/internal/psql/tests.sql @@ -8,8 +8,8 @@ WITH "_sg_input" AS (SELECT $1 :: json AS j), "products" AS (INSERT INTO "produc === RUN TestCompileInsert/simpleInsertWithPresets WITH "_sg_input" AS (SELECT $1 :: json AS j), "products" AS (INSERT INTO "products" ("name", "price", "created_at", "updated_at", "user_id") SELECT CAST( i.j ->>'name' AS character varying), CAST( i.j ->>'price' AS numeric(7,2)), 'now' :: timestamp without time zone, 'now' :: timestamp without time zone, $2 :: bigint FROM "_sg_input" i RETURNING *) SELECT jsonb_build_object('product', "__sj_0"."json") as "__root" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "products_0"."id" AS "id" FROM (SELECT "products"."id" FROM "products" LIMIT ('1') :: integer) AS "products_0") AS "__sr_0") AS "__sj_0" === RUN TestCompileInsert/nestedInsertManyToMany -WITH "_sg_input" AS (SELECT $1 :: json AS j), "customers" AS (INSERT INTO "customers" ("full_name", "email") SELECT CAST( i.j ->>'full_name' AS character varying), CAST( i.j ->>'email' AS character varying) FROM "_sg_input" i RETURNING *), "products" AS (INSERT INTO "products" ("name", "price") SELECT CAST( i.j ->>'name' AS character varying), CAST( i.j ->>'price' AS numeric(7,2)) FROM "_sg_input" i RETURNING *), "purchases" AS (INSERT INTO "purchases" ("sale_type", "quantity", "due_date", "product_id", "customer_id") SELECT CAST( i.j ->>'sale_type' AS character varying), CAST( i.j ->>'quantity' AS integer), CAST( i.j ->>'due_date' AS timestamp without time zone), "products"."id", "customers"."id" FROM "_sg_input" i, "products", "customers" RETURNING *) SELECT jsonb_build_object('purchase', "__sj_0"."json") as "__root" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "purchases_0"."sale_type" AS "sale_type", "purchases_0"."quantity" AS "quantity", "purchases_0"."due_date" AS "due_date", "__sj_1"."json" AS "product", "__sj_2"."json" AS "customer" 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 to_jsonb("__sr_2".*) AS "json"FROM (SELECT "customers_2"."id" AS "id", "customers_2"."full_name" AS "full_name", "customers_2"."email" AS "email" 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 "__sr_2") AS "__sj_2" ON ('true') LEFT OUTER JOIN LATERAL (SELECT to_jsonb("__sr_1".*) AS "json"FROM (SELECT "products_1"."id" AS "id", "products_1"."name" AS "name", "products_1"."price" AS "price" FROM (SELECT "products"."id", "products"."name", "products"."price" FROM "products" WHERE ((("products"."id") = ("purchases_0"."product_id"))) LIMIT ('1') :: integer) AS "products_1") AS "__sr_1") AS "__sj_1" ON ('true')) AS "__sr_0") AS "__sj_0" WITH "_sg_input" AS (SELECT $1 :: json AS j), "products" AS (INSERT INTO "products" ("name", "price") SELECT CAST( i.j ->>'name' AS character varying), CAST( i.j ->>'price' AS numeric(7,2)) FROM "_sg_input" i RETURNING *), "customers" AS (INSERT INTO "customers" ("full_name", "email") SELECT CAST( i.j ->>'full_name' AS character varying), CAST( i.j ->>'email' AS character varying) FROM "_sg_input" i RETURNING *), "purchases" AS (INSERT INTO "purchases" ("sale_type", "quantity", "due_date", "customer_id", "product_id") SELECT CAST( i.j ->>'sale_type' AS character varying), CAST( i.j ->>'quantity' AS integer), CAST( i.j ->>'due_date' AS timestamp without time zone), "customers"."id", "products"."id" FROM "_sg_input" i, "customers", "products" RETURNING *) SELECT jsonb_build_object('purchase', "__sj_0"."json") as "__root" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "purchases_0"."sale_type" AS "sale_type", "purchases_0"."quantity" AS "quantity", "purchases_0"."due_date" AS "due_date", "__sj_1"."json" AS "product", "__sj_2"."json" AS "customer" 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 to_jsonb("__sr_2".*) AS "json"FROM (SELECT "customers_2"."id" AS "id", "customers_2"."full_name" AS "full_name", "customers_2"."email" AS "email" 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 "__sr_2") AS "__sj_2" ON ('true') LEFT OUTER JOIN LATERAL (SELECT to_jsonb("__sr_1".*) AS "json"FROM (SELECT "products_1"."id" AS "id", "products_1"."name" AS "name", "products_1"."price" AS "price" FROM (SELECT "products"."id", "products"."name", "products"."price" FROM "products" WHERE ((("products"."id") = ("purchases_0"."product_id"))) LIMIT ('1') :: integer) AS "products_1") AS "__sr_1") AS "__sj_1" ON ('true')) AS "__sr_0") AS "__sj_0" +WITH "_sg_input" AS (SELECT $1 :: json AS j), "customers" AS (INSERT INTO "customers" ("full_name", "email") SELECT CAST( i.j ->>'full_name' AS character varying), CAST( i.j ->>'email' AS character varying) FROM "_sg_input" i RETURNING *), "products" AS (INSERT INTO "products" ("name", "price") SELECT CAST( i.j ->>'name' AS character varying), CAST( i.j ->>'price' AS numeric(7,2)) FROM "_sg_input" i RETURNING *), "purchases" AS (INSERT INTO "purchases" ("sale_type", "quantity", "due_date", "product_id", "customer_id") SELECT CAST( i.j ->>'sale_type' AS character varying), CAST( i.j ->>'quantity' AS integer), CAST( i.j ->>'due_date' AS timestamp without time zone), "products"."id", "customers"."id" FROM "_sg_input" i, "products", "customers" RETURNING *) SELECT jsonb_build_object('purchase', "__sj_0"."json") as "__root" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "purchases_0"."sale_type" AS "sale_type", "purchases_0"."quantity" AS "quantity", "purchases_0"."due_date" AS "due_date", "__sj_1"."json" AS "product", "__sj_2"."json" AS "customer" 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 to_jsonb("__sr_2".*) AS "json"FROM (SELECT "customers_2"."id" AS "id", "customers_2"."full_name" AS "full_name", "customers_2"."email" AS "email" 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 "__sr_2") AS "__sj_2" ON ('true') LEFT OUTER JOIN LATERAL (SELECT to_jsonb("__sr_1".*) AS "json"FROM (SELECT "products_1"."id" AS "id", "products_1"."name" AS "name", "products_1"."price" AS "price" FROM (SELECT "products"."id", "products"."name", "products"."price" FROM "products" WHERE ((("products"."id") = ("purchases_0"."product_id"))) LIMIT ('1') :: integer) AS "products_1") AS "__sr_1") AS "__sj_1" ON ('true')) AS "__sr_0") AS "__sj_0" === RUN TestCompileInsert/nestedInsertOneToMany WITH "_sg_input" AS (SELECT $1 :: json AS j), "users" AS (INSERT INTO "users" ("full_name", "email", "created_at", "updated_at") SELECT CAST( i.j ->>'full_name' AS character varying), CAST( i.j ->>'email' AS character varying), CAST( i.j ->>'created_at' AS timestamp without time zone), CAST( i.j ->>'updated_at' AS timestamp without time zone) FROM "_sg_input" i RETURNING *), "products" AS (INSERT INTO "products" ("name", "price", "created_at", "updated_at", "user_id") SELECT CAST( i.j ->>'name' AS character varying), CAST( i.j ->>'price' AS numeric(7,2)), CAST( i.j ->>'created_at' AS timestamp without time zone), CAST( i.j ->>'updated_at' AS timestamp without time zone), "users"."id" FROM "_sg_input" i, "users" RETURNING *) SELECT jsonb_build_object('user', "__sj_0"."json") as "__root" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "users_0"."id" AS "id", "users_0"."full_name" AS "full_name", "users_0"."email" AS "email", "__sj_1"."json" AS "product" FROM (SELECT "users"."id", "users"."full_name", "users"."email" FROM "users" LIMIT ('1') :: integer) AS "users_0" LEFT OUTER JOIN LATERAL (SELECT to_jsonb("__sr_1".*) AS "json"FROM (SELECT "products_1"."id" AS "id", "products_1"."name" AS "name", "products_1"."price" AS "price" FROM (SELECT "products"."id", "products"."name", "products"."price" FROM "products" WHERE ((("products"."user_id") = ("users_0"."id"))) LIMIT ('1') :: integer) AS "products_1") AS "__sr_1") AS "__sj_1" ON ('true')) AS "__sr_0") AS "__sj_0" === RUN TestCompileInsert/nestedInsertOneToOne @@ -20,7 +20,7 @@ WITH "_sg_input" AS (SELECT $1 :: json AS j), "users" AS (INSERT INTO "users" (" WITH "_sg_input" AS (SELECT $1 :: json AS j), "_x_users" AS (SELECT "id" FROM "_sg_input" i,"users" WHERE "users"."id"= ((i.j->'user'->'connect'->>'id'))::bigint LIMIT 1), "products" AS (INSERT INTO "products" ("name", "price", "created_at", "updated_at", "user_id") SELECT CAST( i.j ->>'name' AS character varying), CAST( i.j ->>'price' AS numeric(7,2)), CAST( i.j ->>'created_at' AS timestamp without time zone), CAST( i.j ->>'updated_at' AS timestamp without time zone), "_x_users"."id" FROM "_sg_input" i, "_x_users" RETURNING *) SELECT jsonb_build_object('product', "__sj_0"."json") as "__root" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name", "__sj_1"."json" AS "user", "__sj_2"."json" AS "tags" FROM (SELECT "products"."id", "products"."name", "products"."user_id", "products"."tags" FROM "products" LIMIT ('1') :: integer) AS "products_0" LEFT OUTER JOIN LATERAL (SELECT coalesce(jsonb_agg("__sj_2"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_2".*) AS "json"FROM (SELECT "tags_2"."id" AS "id", "tags_2"."name" AS "name" FROM (SELECT "tags"."id", "tags"."name" FROM "tags" WHERE ((("tags"."slug") = any ("products_0"."tags"))) LIMIT ('20') :: integer) AS "tags_2") AS "__sr_2") AS "__sj_2") AS "__sj_2" ON ('true') LEFT OUTER JOIN LATERAL (SELECT to_jsonb("__sr_1".*) AS "json"FROM (SELECT "users_1"."id" AS "id", "users_1"."full_name" AS "full_name", "users_1"."email" AS "email" FROM (SELECT "users"."id", "users"."full_name", "users"."email" FROM "users" WHERE ((("users"."id") = ("products_0"."user_id"))) LIMIT ('1') :: integer) AS "users_1") AS "__sr_1") AS "__sj_1" ON ('true')) AS "__sr_0") AS "__sj_0" === RUN TestCompileInsert/nestedInsertOneToOneWithConnectArray WITH "_sg_input" AS (SELECT $1 :: json AS j), "_x_users" AS (SELECT "id" FROM "_sg_input" i,"users" WHERE "users"."id" = ANY((select a::bigint AS list from json_array_elements_text((i.j->'user'->'connect'->>'id')::json) AS a)) LIMIT 1), "products" AS (INSERT INTO "products" ("name", "price", "created_at", "updated_at", "user_id") SELECT CAST( i.j ->>'name' AS character varying), CAST( i.j ->>'price' AS numeric(7,2)), CAST( i.j ->>'created_at' AS timestamp without time zone), CAST( i.j ->>'updated_at' AS timestamp without time zone), "_x_users"."id" FROM "_sg_input" i, "_x_users" RETURNING *) SELECT jsonb_build_object('product', "__sj_0"."json") as "__root" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name", "__sj_1"."json" AS "user" FROM (SELECT "products"."id", "products"."name", "products"."user_id" FROM "products" LIMIT ('1') :: integer) AS "products_0" LEFT OUTER JOIN LATERAL (SELECT to_jsonb("__sr_1".*) AS "json"FROM (SELECT "users_1"."id" AS "id", "users_1"."full_name" AS "full_name", "users_1"."email" AS "email" FROM (SELECT "users"."id", "users"."full_name", "users"."email" FROM "users" WHERE ((("users"."id") = ("products_0"."user_id"))) LIMIT ('1') :: integer) AS "users_1") AS "__sr_1") AS "__sj_1" ON ('true')) AS "__sr_0") AS "__sj_0" ---- PASS: TestCompileInsert (0.02s) +--- PASS: TestCompileInsert (0.03s) --- PASS: TestCompileInsert/simpleInsert (0.00s) --- PASS: TestCompileInsert/singleInsert (0.00s) --- PASS: TestCompileInsert/bulkInsert (0.00s) @@ -92,6 +92,8 @@ SELECT jsonb_build_object('users', "__sj_0"."json") as "__root" FROM (SELECT coa SELECT jsonb_build_object('users', "__sj_0"."json") as "__root" FROM (SELECT coalesce(jsonb_agg("__sj_0"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "users_0"."first_name" AS "first_name", "users_0"."last_name" AS "last_name", "users_0"."created_at" AS "created_at", "users_0"."id" AS "id", "users_0"."email" AS "email" FROM (SELECT , "users"."created_at", "users"."id", "users"."email" FROM "users" GROUP BY "users"."created_at", "users"."id", "users"."email" LIMIT ('20') :: integer) AS "users_0") AS "__sr_0") AS "__sj_0") AS "__sj_0" === RUN TestCompileQuery/withFragment3 SELECT jsonb_build_object('users', "__sj_0"."json") as "__root" FROM (SELECT coalesce(jsonb_agg("__sj_0"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "users_0"."first_name" AS "first_name", "users_0"."last_name" AS "last_name", "users_0"."created_at" AS "created_at", "users_0"."id" AS "id", "users_0"."email" AS "email" FROM (SELECT , "users"."created_at", "users"."id", "users"."email" FROM "users" GROUP BY "users"."created_at", "users"."id", "users"."email" LIMIT ('20') :: integer) AS "users_0") AS "__sr_0") AS "__sj_0") AS "__sj_0" +=== RUN TestCompileQuery/withInlineFragment +SELECT jsonb_build_object('users', "__sj_0"."json") as "__root" FROM (SELECT coalesce(jsonb_agg("__sj_0"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "users_0"."id" AS "id", "users_0"."email" AS "email", "users_0"."created_at" AS "created_at", "users_0"."first_name" AS "first_name", "users_0"."last_name" AS "last_name" FROM (SELECT "users"."id", "users"."email", "users"."created_at" FROM "users" GROUP BY "users"."id", "users"."email", "users"."created_at" LIMIT ('20') :: integer) AS "users_0") AS "__sr_0") AS "__sj_0") AS "__sj_0" === RUN TestCompileQuery/jsonColumnAsTable SELECT jsonb_build_object('products', "__sj_0"."json") as "__root" FROM (SELECT coalesce(jsonb_agg("__sj_0"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name", "__sj_1"."json" AS "tag_count" FROM (SELECT "products"."id", "products"."name" FROM "products" LIMIT ('20') :: integer) AS "products_0" LEFT OUTER JOIN LATERAL (SELECT to_jsonb("__sr_1".*) AS "json"FROM (SELECT "tag_count_1"."count" AS "count", "__sj_2"."json" AS "tags" 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(jsonb_agg("__sj_2"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_2".*) AS "json"FROM (SELECT "tags_2"."name" AS "name" FROM (SELECT "tags"."name" FROM "tags" WHERE ((("tags"."id") = ("tag_count_1"."tag_id"))) LIMIT ('20') :: integer) AS "tags_2") AS "__sr_2") AS "__sj_2") AS "__sj_2" ON ('true')) AS "__sr_1") AS "__sj_1" ON ('true')) AS "__sr_0") AS "__sj_0") AS "__sj_0" === RUN TestCompileQuery/withCursor @@ -126,6 +128,7 @@ SELECT jsonb_build_object('users', "__sj_0"."json") as "__root" FROM (SELECT coa --- PASS: TestCompileQuery/withFragment1 (0.00s) --- PASS: TestCompileQuery/withFragment2 (0.00s) --- PASS: TestCompileQuery/withFragment3 (0.00s) + --- PASS: TestCompileQuery/withInlineFragment (0.00s) --- PASS: TestCompileQuery/jsonColumnAsTable (0.00s) --- PASS: TestCompileQuery/withCursor (0.00s) --- PASS: TestCompileQuery/nullForAuthRequiredInAnon (0.00s) @@ -160,4 +163,4 @@ WITH "_sg_input" AS (SELECT $1 :: json AS j), "_x_users" AS (SELECT * FROM (VALU --- PASS: TestCompileUpdate/nestedUpdateOneToOneWithConnect (0.00s) --- PASS: TestCompileUpdate/nestedUpdateOneToOneWithDisconnect (0.00s) PASS -ok github.com/dosco/super-graph/core/internal/psql 0.374s +ok github.com/dosco/super-graph/core/internal/psql 0.371s diff --git a/core/internal/qcode/parse.go b/core/internal/qcode/parse.go index 6fe0eda..6a1d254 100644 --- a/core/internal/qcode/parse.go +++ b/core/internal/qcode/parse.go @@ -34,7 +34,8 @@ const ( NodeVar ) -type SelectionSet struct { +type Operation struct { + Type parserType Name string Args []Arg argsA [10]Arg @@ -42,11 +43,6 @@ type SelectionSet struct { fieldsA [10]Field } -type Operation struct { - Type parserType - SelectionSet -} - var zeroOperation = Operation{} func (o *Operation) Reset() { @@ -54,9 +50,10 @@ func (o *Operation) Reset() { } type Fragment struct { - Name string - On string - SelectionSet + Name string + On string + Fields []Field + fieldsA [10]Field } var zeroFragment = Fragment{} @@ -158,9 +155,11 @@ func Parse(gql []byte) (*Operation, error) { if p.peek(itemFragment) { p.ignore() - if err = p.parseFragment(op); err != nil { + if f, err := p.parseFragment(false); err != nil { + fragPool.Put(f) return nil, err } + } else { if !qf && p.peek(itemQuery, itemMutation, itemSub, itemObjOpen) { s = p.pos @@ -175,53 +174,62 @@ func Parse(gql []byte) (*Operation, error) { return nil, err } + for _, v := range p.frags { + fragPool.Put(v) + } + return op, nil } -func (p *Parser) parseFragment(op *Operation) error { +func (p *Parser) parseFragment(inline bool) (*Fragment, error) { + var err error + frag := fragPool.Get().(*Fragment) frag.Reset() - - frag.Fields = frag.SelectionSet.fieldsA[:0] - frag.Args = frag.SelectionSet.argsA[:0] + frag.Fields = frag.fieldsA[:0] if p.peek(itemName) { frag.Name = p.val(p.next()) + } else if !inline { + return frag, errors.New("fragment: missing name") } if p.peek(itemOn) { p.ignore() } else { - return errors.New("fragment: missing 'on' keyword") + return frag, errors.New("fragment: missing 'on' keyword") } if p.peek(itemName) { frag.On = p.vall(p.next()) } else { - return errors.New("fragment: missing table name after 'on' keyword") + return frag, errors.New("fragment: missing table name after 'on' keyword") } if p.peek(itemObjOpen) { p.ignore() } else { - return fmt.Errorf("fragment: expecting a '{', got: %s", p.next()) + return frag, fmt.Errorf("fragment: expecting a '{', got: %s", p.next()) } - if err := p.parseSelectionSet(&frag.SelectionSet); err != nil { - return fmt.Errorf("fragment: %v", err) + frag.Fields, err = p.parseFields(frag.Fields) + if err != nil { + return frag, fmt.Errorf("fragment: %v", err) } - if p.frags == nil { - p.frags = make(map[uint64]*Fragment) + if !inline { + if p.frags == nil { + p.frags = make(map[uint64]*Fragment) + } + + _, _ = p.h.WriteString(frag.Name) + k := p.h.Sum64() + p.h.Reset() + + p.frags[k] = frag } - _, _ = p.h.WriteString(frag.Name) - k := p.h.Sum64() - p.h.Reset() - - p.frags[k] = frag - - return nil + return frag, nil } func (p *Parser) parseOp(op *Operation) error { @@ -249,7 +257,7 @@ func (p *Parser) parseOp(op *Operation) error { break } - err = p.parseSelectionSet(&op.SelectionSet) + op.Fields, err = p.parseFields(op.Fields) if err != nil { return fmt.Errorf("%s: %v", op.Type, err) } @@ -273,7 +281,7 @@ func (p *Parser) parseOpTypeAndArgs(op *Operation) error { op.Type = opSub } - op.Args = op.SelectionSet.argsA[:0] + op.Args = op.argsA[:0] var err error @@ -293,17 +301,6 @@ func (p *Parser) parseOpTypeAndArgs(op *Operation) error { return nil } -func (p *Parser) parseSelectionSet(selset *SelectionSet) error { - var err error - - selset.Fields, err = p.parseFields(selset.Fields) - if err != nil { - return err - } - - return nil -} - func ParseArgValue(argVal string) (*Node, error) { l := lexPool.Get().(*lexer) l.Reset() @@ -324,6 +321,7 @@ func ParseArgValue(argVal string) (*Node, error) { } func (p *Parser) parseFields(fields []Field) ([]Field, error) { + var err error st := NewStack() if !p.peek(itemName, itemSpread) { @@ -358,86 +356,111 @@ func (p *Parser) parseFields(fields []Field) ([]Field, error) { isFrag = true } - if !p.peek(itemName) { - if isFrag { - return nil, fmt.Errorf("expecting a fragment name, got: %s", p.next()) - } else { - return nil, fmt.Errorf("expecting an alias or field name, got: %s", p.next()) - } - } - - var f *Field - if isFrag { - name := p.val(p.next()) - _, _ = p.h.WriteString(name) - id := p.h.Sum64() - p.h.Reset() - - fr, ok := p.frags[id] - if !ok { - return nil, fmt.Errorf("no fragment named '%s' defined", name) - } - - n := int32(len(fields)) - fields = append(fields, fr.Fields...) - - for i := 0; i < len(fr.Fields); i++ { - k := (n + int32(i)) - f := &fields[k] - f.ID = int32(k) - - // If this is the top-level point the parent to the parent of the - // previous field. - if f.ParentID == -1 { - pid := st.Peek() - f.ParentID = pid - if f.ParentID != -1 { - fields[pid].Children = append(fields[f.ParentID].Children, f.ID) - } - // Update all the other parents id's by our new place in this new array - } else { - f.ParentID += n - } - - f.Children = make([]int32, len(f.Children)) - copy(f.Children, fr.Fields[i].Children) - - f.Args = make([]Arg, len(f.Args)) - copy(f.Args, fr.Fields[i].Args) - - // Update all the children which is needed. - for j := range f.Children { - f.Children[j] += n - } - } - + fields, err = p.parseFragmentFields(st, fields) } else { - fields = append(fields, Field{ID: int32(len(fields))}) - - f = &fields[(len(fields) - 1)] - f.Args = f.argsA[:0] - f.Children = f.childrenA[:0] - - // Parse the field - if err := p.parseField(f); err != nil { - return nil, err - } - - if st.Len() == 0 { - f.ParentID = -1 - } else { - pid := st.Peek() - f.ParentID = pid - fields[pid].Children = append(fields[pid].Children, f.ID) - } + fields, err = p.parseNormalFields(st, fields) } - // The first opening curley brackets after this - // comes the columns or child fields - if p.peek(itemObjOpen) { - p.ignore() - st.Push(f.ID) + if err != nil { + return nil, err + } + } + + return fields, nil +} + +func (p *Parser) parseNormalFields(st *Stack, fields []Field) ([]Field, error) { + if !p.peek(itemName) { + return nil, fmt.Errorf("expecting an alias or field name, got: %s", p.next()) + } + + fields = append(fields, Field{ID: int32(len(fields))}) + + f := &fields[(len(fields) - 1)] + f.Args = f.argsA[:0] + f.Children = f.childrenA[:0] + + // Parse the field + if err := p.parseField(f); err != nil { + return nil, err + } + + if st.Len() == 0 { + f.ParentID = -1 + } else { + pid := st.Peek() + f.ParentID = pid + fields[pid].Children = append(fields[pid].Children, f.ID) + } + + // The first opening curley brackets after this + // comes the columns or child fields + if p.peek(itemObjOpen) { + p.ignore() + st.Push(f.ID) + } + + return fields, nil +} + +func (p *Parser) parseFragmentFields(st *Stack, fields []Field) ([]Field, error) { + var fr *Fragment + var err error + + if p.peek(itemOn) { + if fr, err = p.parseFragment(true); err != nil { + return nil, err + } + defer fragPool.Put(fr) + + } else { + var ok bool + + if !p.peek(itemName) { + return nil, fmt.Errorf("expecting a fragment name, got: %s", p.next()) + } + + name := p.val(p.next()) + _, _ = p.h.WriteString(name) + id := p.h.Sum64() + p.h.Reset() + + if fr, ok = p.frags[id]; !ok { + return nil, fmt.Errorf("no fragment named '%s' defined", name) + } + } + + n := int32(len(fields)) + fields = append(fields, fr.Fields...) + + for i := 0; i < len(fr.Fields); i++ { + k := (n + int32(i)) + f := &fields[k] + f.ID = int32(k) + + // If this is the top-level point the parent to the parent of the + // previous field. + if f.ParentID == -1 { + pid := st.Peek() + f.ParentID = pid + if f.ParentID != -1 { + fields[pid].Children = append(fields[f.ParentID].Children, f.ID) + } + // Update all the other parents id's by our new place in this new array + } else { + f.ParentID += n + } + + f.Children = make([]int32, len(f.Children)) + copy(f.Children, fr.Fields[i].Children) + + f.Args = make([]Arg, len(f.Args)) + copy(f.Args, fr.Fields[i].Args) + + // Update all the children which is needed. + for j := range f.Children { + f.Children[j] += n } } @@ -471,6 +494,24 @@ func (p *Parser) parseField(f *Field) error { return nil } +// func (p *Parser) parseInlineFragmentFields(st *Stack, fields []Field) ([]Field, error) { +// var err error + +// if p.peek(itemName) { +// p.ignore() +// // frag.On = p.vall(p.next()) +// } else { +// return nil, errors.New("inline fragment: missing table name after 'on' keyword") +// } + +// fields, err = p.parseNormalFields(st, fields) +// if err != nil { +// return nil, fmt.Errorf("inline fragment: %v", err) +// } + +// return fields, nil +// } + func (p *Parser) parseOpParams(args []Arg) ([]Arg, error) { for { if len(args) >= maxArgs {