resolve

package
v25.0.0-custom-qt-impr... Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 5, 2025 License: Apache-2.0 Imports: 25 Imported by: 0

Documentation

Index

Constants

View Source
const (
	MutationQueryVar    = "x"
	MutationQueryVarUID = "uid(x)"
)
View Source
const (
	ErrInternal = "Internal error"
)

Variables

This section is empty.

Functions

func NewXidMetadata

func NewXidMetadata() *xidMetadata

NewXidMetadata returns a new empty *xidMetadata for storing the metadata.

func RewriteUpsertQueryFromMutation

func RewriteUpsertQueryFromMutation(
	m schema.Mutation,
	authRw *authRewriter,
	mutationQueryVar string,
	queryAttribute string,
	nodeID string) []*dql.GraphQuery

Types

type AddRewriter

type AddRewriter struct {
	Rewriter
	// contains filtered or unexported fields
}

func (*AddRewriter) FromMutationResult

func (arw *AddRewriter) FromMutationResult(
	ctx context.Context,
	mutation schema.Mutation,
	assigned map[string]string,
	result map[string]interface{}) ([]*dql.GraphQuery, error)

FromMutationResult rewrites the query part of a GraphQL add mutation into a Dgraph query.

func (*AddRewriter) MutatedRootUIDs

func (arw *AddRewriter) MutatedRootUIDs(
	mutation schema.Mutation,
	assigned map[string]string,
	result map[string]interface{}) []string

func (*AddRewriter) Rewrite

func (arw *AddRewriter) Rewrite(
	ctx context.Context,
	m schema.Mutation,
	idExistence map[string]string) ([]*UpsertMutation, error)

Rewrite takes a GraphQL schema.Mutation add and builds a Dgraph upsert mutation. m must have a single argument called 'input' that carries the mutation data. The arguments also consist of idExistence map which is a map from Variable Name --> UID . This map is used to know which referenced nodes exists and whether to link the newly created node to existing node or create a new one.

That argument could have been passed in the mutation like:

addPost(input: { title: "...", ... })

or be passed in a GraphQL variable like:

addPost(input: $newPost)

Either way, the data needs to have type information added and have some rewriting done - for example, rewriting field names from the GraphQL view to what's stored in Dgraph, and rewriting ID fields from their names to uid.

For example, a GraphQL add mutation to add an object of type Author, with GraphQL input object (where country code is @id) :

{
  name: "A.N. Author",
  country: { code: "ind", name: "India" },
  posts: [ { title: "A Post", text: "Some text" }]
  friends: [ { id: "0x123" } ]
}

and idExistence

{
  "Country2": "0x234",
  "Person3": "0x123"
}

becomes an unconditional mutation.

{
  "uid":"_:Author1",
  "dgraph.type":["Author"],
  "Author.name":"A.N. Author",
  "Author.country": {
    "uid":"0x234"
  },
  "Author.posts": [ {
    "uid":"_:Post3"
    "dgraph.type":["Post"],
    "Post.text":"Some text",
    "Post.title":"A Post",
  } ],
  "Author.friends":[ {"uid":"0x123"} ],
}

func (*AddRewriter) RewriteQueries

func (arw *AddRewriter) RewriteQueries(
	ctx context.Context,
	m schema.Mutation) ([]*dql.GraphQuery, []string, error)

RewriteQueries takes a GraphQL schema.Mutation add and creates queries to find out if referenced nodes by XID and UID exist or not. m must have a single argument called 'input' that carries the mutation data.

For example, a GraphQL add mutation to add an object of type Author, with GraphQL input object (where country code is @id)

{
  name: "A.N. Author",
  country: { code: "ind", name: "India" },
  posts: [ { title: "A Post", text: "Some text" }]
  friends: [ { id: "0x123" } ]
}

The following queries would be generated

query {
  Country2(func: eq(Country.code, "ind")) @filter(type: Country) {
    uid
  }
  Person3(func: uid(0x123)) @filter(type: Person) {
    uid
  }
}

This query will be executed and depending on the result it would be decided whether to create a new country as part of this mutation or link it to an existing country. If it is found out that there is an existing country, no modifications are made to the country's attributes and its children. Mutations of the country's children are simply ignored. If it is found out that the Person with id 0x123 does not exist, the corresponding mutation will fail.

type CompletionFunc

type CompletionFunc func(ctx context.Context, resolved *Resolved)

CompletionFunc is an adapter that allows us to compose completions and build a ResultCompleter from a function. Based on the http.HandlerFunc pattern.

func (CompletionFunc) Complete

func (cf CompletionFunc) Complete(ctx context.Context, resolved *Resolved)

Complete calls cf(ctx, resolved)

type DgraphExecutor

type DgraphExecutor interface {
	// Execute performs the actual query/mutation and returns a Dgraph response. If an error
	// occurs, that indicates that the execution failed in some way significant enough
	// way as to not continue processing this query/mutation or others in the same request.
	Execute(ctx context.Context, req *dgoapi.Request, field schema.Field) (*dgoapi.Response, error)
	CommitOrAbort(ctx context.Context, tc *dgoapi.TxnContext) (*dgoapi.TxnContext, error)
}

A DgraphExecutor can execute a query/mutation and returns the request response and any errors.

func NewAdminExecutor

func NewAdminExecutor() DgraphExecutor

NewAdminExecutor builds a DgraphExecutor for proxying requests through dgraph.

func NewDgraphExecutor

func NewDgraphExecutor() DgraphExecutor

NewDgraphExecutor builds a DgraphExecutor for proxying requests through dgraph.

type DgraphExecutorFunc

type DgraphExecutorFunc func(ctx context.Context, req *dgoapi.Request) (*dgoapi.Response, error)

DgraphExecutorFunc is an adapter that allows us to compose dgraph execution and build a QueryExecuter from a function. Based on the http.HandlerFunc pattern.

func (DgraphExecutorFunc) Execute

func (ex DgraphExecutorFunc) Execute(
	ctx context.Context,
	req *dgoapi.Request) (*dgoapi.Response, error)

Execute calls qe(ctx, query)

type MutationMiddleware

type MutationMiddleware func(resolver MutationResolver) MutationResolver

MutationMiddleware represents a middleware for mutations

type MutationMiddlewares

type MutationMiddlewares []MutationMiddleware

MutationMiddlewares represents a list of middlewares for mutations, that get applied in the order they are present in the list. Inspired from: https://github.com/justinas/alice

func (MutationMiddlewares) Then

Then chains the middlewares and returns the final MutationResolver.

MutationMiddlewares{m1, m2, m3}.Then(r)

is equivalent to:

m1(m2(m3(r)))

When the request comes in, it will be passed to m1, then m2, then m3 and finally, the given resolverFunc (assuming every middleware calls the following one).

A chain can be safely reused by calling Then() several times.

commonMiddlewares := MutationMiddlewares{authMiddleware, loggingMiddleware}
backupResolver = commonMiddlewares.Then(resolveBackup)
configResolver = commonMiddlewares.Then(resolveConfig)

Note that middlewares are called on every call to Then() and thus several instances of the same middleware will be created when a chain is reused in this way. For proper middleware, this should cause no problems.

Then() treats nil as a MutationResolverFunc that resolves to (&Resolved{Field: mutation}, true)

type MutationResolver

type MutationResolver interface {
	Resolve(ctx context.Context, mutation schema.Mutation) (*Resolved, bool)
}

A MutationResolver can resolve a single mutation.

func AclOnlyMW4Mutation

func AclOnlyMW4Mutation(resolver MutationResolver) MutationResolver

func GuardianAuthMW4Mutation

func GuardianAuthMW4Mutation(resolver MutationResolver) MutationResolver

GuardianAuthMW4Mutation blocks the resolution of resolverFunc if there is no Guardian auth present in context, otherwise it lets the resolverFunc resolve the mutation.

func GuardianOfTheGalaxyAuthMW4Mutation

func GuardianOfTheGalaxyAuthMW4Mutation(resolver MutationResolver) MutationResolver

GuardianOfTheGalaxyAuthMW4Mutation blocks the resolution of resolverFunc if there is no Guardian of Galaxy auth present in context, otherwise it lets the resolverFunc resolve the mutation.

func IpWhitelistingMW4Mutation

func IpWhitelistingMW4Mutation(resolver MutationResolver) MutationResolver

func LoggingMWMutation

func LoggingMWMutation(resolver MutationResolver) MutationResolver

func NewDgraphResolver

func NewDgraphResolver(mr MutationRewriter, ex DgraphExecutor) MutationResolver

NewDgraphResolver creates a new mutation resolver. The resolver runs the pipeline: 1) rewrite the mutation using mr (return error if failed) 2) execute the mutation with me (return error if failed) 3) write a query for the mutation with mr (return error if failed) 4) execute the query with qe (return error if failed)

func NewHTTPMutationResolver

func NewHTTPMutationResolver(hc *http.Client) MutationResolver

NewHTTPMutationResolver creates a resolver that resolves GraphQL mutation from an HTTP endpoint

type MutationResolverFunc

type MutationResolverFunc func(ctx context.Context, m schema.Mutation) (*Resolved, bool)

MutationResolverFunc is an adapter that allows to build a MutationResolver from a function. Based on the http.HandlerFunc pattern.

func (MutationResolverFunc) Resolve

Resolve calls mr(ctx, mutation)

type MutationRewriter

type MutationRewriter interface {
	// RewriteQueries generates and rewrites GraphQL mutation m into DQL queries which
	// check if any referenced node by XID or ID exist or not.
	// Instead of filtering on dgraph.type like @filter(type(Parrot)), we query `dgraph.type` and
	// filter it on GraphQL side. @filter(type(Parrot)) is costly in terms of memory and cpu.
	// Example existence queries:
	// 1. Parrot1(func: uid(0x127)) {
	//      uid
	//      dgraph.type
	//    }
	// 2.  Computer2(func: eq(Computer.name, "computer1")) {
	//       uid
	//       dgraph.type
	//     }
	// These query will be created in case of Add or Update Mutation which references node
	// 0x127 or Computer of name "computer1"
	RewriteQueries(ctx context.Context, m schema.Mutation) ([]*dql.GraphQuery, []string, error)
	// Rewrite rewrites GraphQL mutation m into a Dgraph mutation - that could
	// be as simple as a single DelNquads, or could be a Dgraph upsert mutation
	// with a query and multiple mutations guarded by conditions.
	Rewrite(ctx context.Context, m schema.Mutation, idExistence map[string]string) ([]*UpsertMutation, error)
	// FromMutationResult takes a GraphQL mutation and the results of a Dgraph
	// mutation and constructs a Dgraph query.  It's used to find the return
	// value from a GraphQL mutation - i.e. we've run the mutation indicated by m
	// now we need to query Dgraph to satisfy all the result fields in m.
	FromMutationResult(
		ctx context.Context,
		m schema.Mutation,
		assigned map[string]string,
		result map[string]interface{}) ([]*dql.GraphQuery, error)
	// MutatedRootUIDs returns a list of Root UIDs that were mutated as part of the mutation.
	MutatedRootUIDs(
		mutation schema.Mutation,
		assigned map[string]string,
		result map[string]interface{}) []string
}

A MutationRewriter can transform a GraphQL mutation into a Dgraph mutation and can build a Dgraph dql.GraphQuery to follow a GraphQL mutation.

Mutations come in like:

mutation addAuthor($auth: AuthorInput!) {
  addAuthor(input: $auth) {
	   author {
	     id
	     name
	   }
  }
}

Where `addAuthor(input: $auth)` implies a mutation that must get run - written to a Dgraph mutation by Rewrite. The GraphQL following `addAuthor(...)`implies a query to run and return the newly created author, so the mutation query rewriting is dependent on the context set up by the result of the mutation.

func NewAddRewriter

func NewAddRewriter() MutationRewriter

NewAddRewriter returns new MutationRewriter for add & update mutations.

func NewDeleteRewriter

func NewDeleteRewriter() MutationRewriter

NewDeleteRewriter returns new MutationRewriter for delete mutations..

func NewUpdateRewriter

func NewUpdateRewriter() MutationRewriter

NewUpdateRewriter returns new MutationRewriter for add & update mutations.

type MutationType

type MutationType int

Enum passed on to rewriteObject function.

const (
	// Add Mutation
	Add MutationType = iota
	// Add Mutation with Upsert
	AddWithUpsert
	// Update Mutation used for to setting new nodes, edges.
	UpdateWithSet
	// Update Mutation used for removing edges.
	UpdateWithRemove
)

type QueryMiddleware

type QueryMiddleware func(resolver QueryResolver) QueryResolver

QueryMiddleware represents a middleware for queries

type QueryMiddlewares

type QueryMiddlewares []QueryMiddleware

QueryMiddlewares represents a list of middlewares for queries, that get applied in the order they are present in the list. Inspired from: https://github.com/justinas/alice

func (QueryMiddlewares) Then

func (mws QueryMiddlewares) Then(resolver QueryResolver) QueryResolver

Then chains the middlewares and returns the final QueryResolver.

QueryMiddlewares{m1, m2, m3}.Then(r)

is equivalent to:

m1(m2(m3(r)))

When the request comes in, it will be passed to m1, then m2, then m3 and finally, the given resolverFunc (assuming every middleware calls the following one).

A chain can be safely reused by calling Then() several times.

commonMiddlewares := QueryMiddlewares{authMiddleware, loggingMiddleware}
healthResolver = commonMiddlewares.Then(resolveHealth)
stateResolver = commonMiddlewares.Then(resolveState)

Note that middlewares are called on every call to Then() and thus several instances of the same middleware will be created when a chain is reused in this way. For proper middleware, this should cause no problems.

Then() treats nil as a QueryResolverFunc that resolves to &Resolved{Field: query}

type QueryResolver

type QueryResolver interface {
	Resolve(ctx context.Context, query schema.Query) *Resolved
}

A QueryResolver can resolve a single query.

func GuardianAuthMW4Query

func GuardianAuthMW4Query(resolver QueryResolver) QueryResolver

GuardianAuthMW4Query blocks the resolution of resolverFunc if there is no Guardian auth present in context, otherwise it lets the resolverFunc resolve the query.

func GuardianOfTheGalaxyAuthMW4Query

func GuardianOfTheGalaxyAuthMW4Query(resolver QueryResolver) QueryResolver

GuardianOfTheGalaxyAuthMW4Query blocks the resolution of resolverFunc if there is no Guardian of Galaxy auth present in context, otherwise it lets the resolverFunc resolve the query.

func IpWhitelistingMW4Query

func IpWhitelistingMW4Query(resolver QueryResolver) QueryResolver

func LoggingMWQuery

func LoggingMWQuery(resolver QueryResolver) QueryResolver

func NewCustomDQLQueryResolver

func NewCustomDQLQueryResolver(ex DgraphExecutor) QueryResolver

func NewEntitiesQueryResolver

func NewEntitiesQueryResolver(qr QueryRewriter, ex DgraphExecutor) QueryResolver

NewEntitiesQueryResolver creates a new query resolver for `_entities` query. It is introduced because result completion works little different for `_entities` query.

func NewHTTPQueryResolver

func NewHTTPQueryResolver(hc *http.Client) QueryResolver

NewHTTPQueryResolver creates a resolver that can resolve GraphQL query from an HTTP endpoint

func NewQueryResolver

func NewQueryResolver(qr QueryRewriter, ex DgraphExecutor) QueryResolver

NewQueryResolver creates a new query resolver. The resolver runs the pipeline: 1) rewrite the query using qr (return error if failed) 2) execute the rewritten query with ex (return error if failed) 3) process the result with rc

type QueryResolverFunc

type QueryResolverFunc func(ctx context.Context, query schema.Query) *Resolved

QueryResolverFunc is an adapter that allows to build a QueryResolver from a function. Based on the http.HandlerFunc pattern.

func (QueryResolverFunc) Resolve

func (qr QueryResolverFunc) Resolve(ctx context.Context, query schema.Query) *Resolved

Resolve calls qr(ctx, query)

type QueryRewriter

type QueryRewriter interface {
	Rewrite(ctx context.Context, q schema.Query) ([]*dql.GraphQuery, error)
}

A QueryRewriter can build a Dgraph dql.GraphQuery from a GraphQL query,

func NewQueryRewriter

func NewQueryRewriter() QueryRewriter

NewQueryRewriter returns a new QueryRewriter.

type RequestResolver

type RequestResolver struct {
	// contains filtered or unexported fields
}

RequestResolver can process GraphQL requests and write GraphQL JSON responses. A schema.Request may contain any number of queries or mutations (never both). RequestResolver.Resolve() resolves all of them by finding the resolved answers of the component queries/mutations and joining into a single schema.Response.

func New

func New(s schema.Schema, resolverFactory ResolverFactory) *RequestResolver

New creates a new RequestResolver.

func (*RequestResolver) Resolve

func (r *RequestResolver) Resolve(ctx context.Context, gqlReq *schema.Request) (resp *schema.Response)

Resolve processes r.GqlReq and returns a GraphQL response. r.GqlReq should be set with a request before Resolve is called and a schema and backend Dgraph should have been added. Resolve records any errors in the response's error field.

func (*RequestResolver) Schema

func (r *RequestResolver) Schema() schema.Schema

func (*RequestResolver) ValidateSubscription

func (r *RequestResolver) ValidateSubscription(req *schema.Request) error

ValidateSubscription will check the given subscription query is valid or not.

type Resolved

type Resolved struct {
	Data       []byte
	Field      schema.Field
	Err        error
	Extensions *schema.Extensions
}

A Resolved is the result of resolving a single field - generally a query or mutation.

func DataResult

func DataResult(f schema.Field, data map[string]interface{}, err error) *Resolved

func EmptyResult

func EmptyResult(f schema.Field, err error) *Resolved

type ResolverFactory

type ResolverFactory interface {

	// WithQueryResolver adds a new query resolver.  Each time query name is resolved
	// resolver is called to create a new instance of a QueryResolver to resolve the
	// query.
	WithQueryResolver(name string, resolver func(schema.Query) QueryResolver) ResolverFactory

	// WithMutationResolver adds a new query resolver.  Each time mutation name is resolved
	// resolver is called to create a new instance of a MutationResolver to resolve the
	// mutation.
	WithMutationResolver(
		name string, resolver func(schema.Mutation) MutationResolver) ResolverFactory

	// WithConventionResolvers adds a set of our convention based resolvers to the
	// factory.  The registration happens only once.
	WithConventionResolvers(s schema.Schema, fns *ResolverFns) ResolverFactory

	// WithQueryMiddlewareConfig adds the configuration to use to apply middlewares before resolving
	// queries. The config should be a mapping of the name of query to its middlewares.
	WithQueryMiddlewareConfig(config map[string]QueryMiddlewares) ResolverFactory

	// WithMutationMiddlewareConfig adds the configuration to use to apply middlewares before
	// resolving mutations. The config should be a mapping of the name of mutation to its
	// middlewares.
	WithMutationMiddlewareConfig(config map[string]MutationMiddlewares) ResolverFactory

	// WithSchemaIntrospection adds schema introspection capabilities to the factory.
	// So __schema and __type queries can be resolved.
	WithSchemaIntrospection() ResolverFactory
	// contains filtered or unexported methods
}

A ResolverFactory finds the right resolver for a query/mutation.

func NewResolverFactory

func NewResolverFactory(
	queryError QueryResolverFunc, mutationError MutationResolverFunc) ResolverFactory

NewResolverFactory returns a ResolverFactory that resolves requests via query/mutation rewriting and execution through Dgraph. If the factory gets asked to resolve a query/mutation it doesn't know how to rewrite, it uses the queryError/mutationError to build an error result.

type ResolverFns

type ResolverFns struct {
	Qrw QueryRewriter
	Arw func() MutationRewriter
	Urw func() MutationRewriter
	Drw MutationRewriter
	Ex  DgraphExecutor
}

ResolverFns is a convenience struct for passing blocks of rewriters and executors.

type ResultCompleter

type ResultCompleter interface {
	Complete(ctx context.Context, resolved *Resolved)
}

A ResultCompleter can take a []byte slice representing an intermediate result in resolving field and applies a completion step.

type Rewriter

type Rewriter struct {
	// VarGen is the VariableGenerator used accross RewriteQueries and Rewrite functions
	// for Mutation. It generates unique variable names for DQL queries and mutations.
	VarGen *VariableGenerator
	// XidMetadata stores data like seenUIDs and variableObjMap to be used across Rewrite
	// and RewriteQueries functions for Mutations.
	XidMetadata *xidMetadata
}

type UpdateRewriter

type UpdateRewriter struct {
	Rewriter
	// contains filtered or unexported fields
}

func (*UpdateRewriter) FromMutationResult

func (urw *UpdateRewriter) FromMutationResult(
	ctx context.Context,
	mutation schema.Mutation,
	assigned map[string]string,
	result map[string]interface{}) ([]*dql.GraphQuery, error)

FromMutationResult rewrites the query part of a GraphQL update mutation into a Dgraph query.

func (*UpdateRewriter) MutatedRootUIDs

func (urw *UpdateRewriter) MutatedRootUIDs(
	mutation schema.Mutation,
	assigned map[string]string,
	result map[string]interface{}) []string

func (*UpdateRewriter) Rewrite

func (urw *UpdateRewriter) Rewrite(
	ctx context.Context,
	m schema.Mutation,
	idExistence map[string]string) ([]*UpsertMutation, error)

Rewrite rewrites set and remove update patches into dql upsert mutations. The GraphQL updates look like:

input UpdateAuthorInput {
	filter: AuthorFilter!
	set: PatchAuthor
	remove: PatchAuthor
}

which gets rewritten in to a Dgraph upsert mutation - filter becomes the query - set becomes the Dgraph set mutation - remove becomes the Dgraph delete mutation

The semantics is the same as the Dgraph mutation semantics.

  • Any values in set become the new values for those predicates (or add to the existing values for lists)
  • Any nulls in set are ignored.
  • Explicit values in remove mean delete this if it is the actual value
  • Nulls in remove become like delete * for the corresponding predicate.

See AddRewriter for how the set and remove fragments get created.

func (*UpdateRewriter) RewriteQueries

func (urw *UpdateRewriter) RewriteQueries(
	ctx context.Context,
	m schema.Mutation) ([]*dql.GraphQuery, []string, error)

RewriteQueries creates and rewrites set and remove update patches queries. The GraphQL updates look like:

input UpdateAuthorInput {
	filter: AuthorFilter!
	set: PatchAuthor
	remove: PatchAuthor
}

which gets rewritten in to a DQL queries to check if - referenced UIDs and XIDs in set and remove exist or not.

Depending on the result of these executed queries, it is then decided whether to create new nodes or link to existing ones.

Note that queries rewritten using RewriteQueries don't include UIDs or XIDs referenced as part of filter argument.

See AddRewriter for how the rewritten queries look like.

type UpsertMutation

type UpsertMutation struct {
	Query     []*dql.GraphQuery
	Mutations []*dgoapi.Mutation
	NewNodes  map[string]schema.Type
}

An UpsertMutation is the query and mutations needed for a Dgraph upsert. The node types is a blank node name -> Type mapping of nodes that could be created by the upsert.

type VariableGenerator

type VariableGenerator struct {
	// contains filtered or unexported fields
}

A VariableGenerator generates unique variable names.

func NewVariableGenerator

func NewVariableGenerator() *VariableGenerator

func (*VariableGenerator) Next

func (v *VariableGenerator) Next(typ schema.Type, xidName, xidVal string, auth bool) string

Next gets the Next variable name for the given type and xid. So, if two objects of the same type have same value for xid field, then they will get same variable name.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL