Files
graph/dql/parser_mutation.go
zeekay f5aaeb712e Hanzo Graph — the engine, on zapdb, ZAP-native
Derived from Dgraph (github.com/dgraph-io/dgraph) at commit 64804bd2, Apache-2.0.
See NOTICE.

WHAT THIS IS. The graph engine as a Go library: posting lists, the DQL parser,
the type and tokenizer system, the index and schema layers, and the wire types.
39,170 lines of the upstream's 166,337.

THE STORE is github.com/luxfi/zapdb, a Badger fork that kept the package name,
the subpackages (options, pb, y) and the API — Open, OpenManaged, DB, Options —
so the 74 import sites are aliased rather than rewritten.

THE CODEC is ZAP. zapdb serializes with ZAP, not protobuf: its pb package builds
the ZAP types by default and the protobuf ones only under `-tags grpc`, a path
that does not compile (table/builder.go calls pb.Marshal, which that tag
removes). One call site in posting needed converting; every other type here is
Hanzo's own protobuf and is untouched.

NO TRANSPORT. An embedded engine dials nothing, so nothing here imports
google.golang.org/grpc. Removed: 20 transport functions in x, the generated
service stubs for Raft, Zero and Worker (1,657 lines), and the JWT helper —
identity belongs to IAM. The core called none of them.

NOT KEPT: worker, conn, raftwal, the cluster commands, the GraphQL layer and the
test harnesses. `query`, the DQL execution engine, went with them because it
depends on worker; parsing DQL is available here, executing it is not.

The history of the upstream project lives at github.com/dgraph-io/dgraph. This
repository begins at the derivation because three quarters of that history
describes code this fork does not carry.
2026-08-20 21:26:27 -07:00

209 lines
5.2 KiB
Go

/*
* SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc.
* SPDX-License-Identifier: Apache-2.0
*/
package dql
import (
"github.com/dgraph-io/dgo/v250/protos/api"
"github.com/hanzoai/graph/lex"
)
// ParseMutation parses a block into a mutation. Returns an object with a mutation or
// an upsert block with mutation, otherwise returns nil with an error.
func ParseMutation(mutation string) (req *api.Request, err error) {
var lexer lex.Lexer
lexer.Reset(mutation)
lexer.Run(lexTopLevel)
if err := lexer.ValidateResult(); err != nil {
return nil, err
}
it := lexer.NewIterator()
if !it.Next() {
return nil, it.Errorf("Invalid mutation")
}
item := it.Item()
switch item.Typ {
case itemUpsertBlock:
if req, err = parseUpsertBlock(it); err != nil {
return nil, err
}
case itemLeftCurl:
mu, err := parseMutationBlock(it)
if err != nil {
return nil, err
}
req = &api.Request{Mutations: []*api.Mutation{mu}}
default:
return nil, it.Errorf("Unexpected token: [%s]", item.Val)
}
// mutations must be enclosed in a single block.
if it.Next() && it.Item().Typ != lex.ItemEOF {
return nil, it.Errorf("Unexpected %s after the end of the block", it.Item().Val)
}
return req, nil
}
// parseUpsertBlock parses the upsert block
func parseUpsertBlock(it *lex.ItemIterator) (*api.Request, error) {
var req *api.Request
var queryText, condText string
var queryFound bool
// ===>upsert<=== {...}
if !it.Next() {
return nil, it.Errorf("Unexpected end of upsert block")
}
// upsert ===>{<=== ....}
item := it.Item()
if item.Typ != itemLeftCurl {
return nil, it.Errorf("Expected { at the start of block. Got: [%s]", item.Val)
}
for it.Next() {
item = it.Item()
switch {
// upsert {... ===>}<===
case item.Typ == itemRightCurl:
switch {
case req == nil:
return nil, it.Errorf("Empty mutation block")
case !queryFound:
return nil, it.Errorf("Query op not found in upsert block")
default:
req.Query = queryText
return req, nil
}
// upsert { mutation{...} ===>query<==={...}}
case item.Typ == itemUpsertBlockOp && item.Val == "query":
if queryFound {
return nil, it.Errorf("Multiple query ops inside upsert block")
}
queryFound = true
if !it.Next() {
return nil, it.Errorf("Unexpected end of upsert block")
}
item = it.Item()
if item.Typ != itemUpsertBlockOpContent {
return nil, it.Errorf("Expecting brace, found '%s'", item.Val)
}
queryText += item.Val
// upsert { ===>mutation<=== {...} query{...}}
case item.Typ == itemUpsertBlockOp && item.Val == "mutation":
if !it.Next() {
return nil, it.Errorf("Unexpected end of upsert block")
}
// upsert { mutation ===>@if(...)<=== {....} query{...}}
item = it.Item()
if item.Typ == itemUpsertBlockOpContent {
condText = item.Val
if !it.Next() {
return nil, it.Errorf("Unexpected end of upsert block")
}
}
// upsert @if(...) ===>{<=== ....}
mu, err := parseMutationBlock(it)
if err != nil {
return nil, err
}
mu.Cond = condText
if req == nil {
req = &api.Request{Mutations: []*api.Mutation{mu}}
} else {
req.Mutations = append(req.Mutations, mu)
}
// upsert { mutation{...} ===>fragment<==={...}}
case item.Typ == itemUpsertBlockOp && item.Val == "fragment":
if !it.Next() {
return nil, it.Errorf("Unexpected end of upsert block")
}
item = it.Item()
if item.Typ != itemUpsertBlockOpContent {
return nil, it.Errorf("Expecting brace, found '%s'", item.Val)
}
queryText += "fragment" + item.Val
default:
return nil, it.Errorf("Unexpected token in upsert block [%s]", item.Val)
}
}
return nil, it.Errorf("Invalid upsert block")
}
// parseMutationBlock parses the mutation block
func parseMutationBlock(it *lex.ItemIterator) (*api.Mutation, error) {
var mu api.Mutation
item := it.Item()
if item.Typ != itemLeftCurl {
return nil, it.Errorf("Expected { at the start of block. Got: [%s]", item.Val)
}
for it.Next() {
item := it.Item()
if item.Typ == itemText {
continue
}
if item.Typ == itemRightCurl {
return &mu, nil
}
if item.Typ == itemMutationOp {
if err := parseMutationOp(it, item.Val, &mu); err != nil {
return nil, err
}
}
}
return nil, it.Errorf("Invalid mutation.")
}
// parseMutationOp parses and stores set or delete operation string in Mutation.
func parseMutationOp(it *lex.ItemIterator, op string, mu *api.Mutation) error {
parse := false
for it.Next() {
item := it.Item()
if item.Typ == itemText {
continue
}
if item.Typ == itemLeftCurl {
if parse {
return it.Errorf("Too many left curls in set mutation.")
}
parse = true
}
if item.Typ == itemMutationOpContent {
if !parse {
return it.Errorf("Mutation syntax invalid.")
}
switch op {
case "set":
mu.SetNquads = []byte(item.Val)
case "delete":
mu.DelNquads = []byte(item.Val)
case "schema":
return it.Errorf("Altering schema not supported through http client.")
case "dropall":
return it.Errorf("Dropall not supported through http client.")
default:
return it.Errorf("Invalid mutation operation.")
}
}
if item.Typ == itemRightCurl {
return nil
}
}
return it.Errorf("Invalid mutation formatting.")
}