<commit_before>package graphql

import (
	"context"
	"fmt"
	"reflect"
	"runtime"
	"sync"

	"github.com/hashicorp/go-multierror"

	"github.com/samsarahq/thunder"
	"github.com/samsarahq/thunder/reactive"
)

// PrepareQuery checks that the given selectionSet matches the schema typ, and
// parses the args in selectionSet
func PrepareQuery(typ Type, selectionSet *SelectionSet) error {
	switch typ := typ.(type) {
	case *Scalar:
		if selectionSet != nil {
			return NewSafeError("scalar field must have no selections")
		}
		return nil

	case *Object:
		if selectionSet == nil {
			return NewSafeError("object field must have selections")
		}
		for _, selection := range selectionSet.Selections {
			field, ok := typ.Fields[selection.Name]
			if !ok {
				return NewSafeError(`unknown field "%s"`, selection.Name)
			}

			parsed, err := field.ParseArguments(selection.Args)
			if err != nil {
				return NewSafeError(`error parsing args for "%s": %s`, selection.Name, err)
			}
			selection.Args = parsed

			if err := PrepareQuery(field.Type, selection.SelectionSet); err != nil {
				return err
			}
		}
		for _, fragment := range selectionSet.Fragments {
			if err := PrepareQuery(typ, fragment.SelectionSet); err != nil {
				return err
			}
		}
		return nil

	case *List:
		return PrepareQuery(typ.Type, selectionSet)

	default:
		panic("unknown type kind")
	}
}

type panicError struct {
	message string
}

func (p panicError) Error() string {
	return p.message
}

func safeResolve(ctx context.Context, field *Field, source, args interface{}, selectionSet *SelectionSet) (result interface{}, err error) {
	defer func() {
		if panicErr := recover(); panicErr != nil {
			const size = 64 << 10
			buf := make([]byte, size)
			buf = buf[:runtime.Stack(buf, false)]
			result, err = nil, fmt.Errorf("graphql: panic: %v\n%s", panicErr, buf)
		}
	}()
	return field.Resolve(ctx, source, args, selectionSet)
}

type objectCacheKey struct {
	typ          *Object
	source       interface{}
	selectionSet *SelectionSet
}

// executeObject executes an object query
func executeObject(ctx context.Context, typ *Object, source interface{}, selectionSet *SelectionSet) (interface{}, error) {
	value := reflect.ValueOf(source)
	if value.Kind() == reflect.Ptr && value.IsNil() {
		return nil, nil
	}

	// cache the body of executeObject so that if the source doesn't change, we
	// don't need to recompute
	key := objectCacheKey{typ: typ, source: source, selectionSet: selectionSet}

	// some types can't be put in a map; for those, use a always different value
	// as source
	if value.IsValid() && !value.Type().Comparable() {
		key.source = new(struct{})
	}

	return reactive.Cache(ctx, key, func(ctx context.Context) (interface{}, error) {
		selections := Flatten(selectionSet)

		fields := make(map[string]interface{})

		// for every selection, resolve the value and store it in the output object
		for _, selection := range selections {
			field := typ.Fields[selection.Name]
			value, err := safeResolve(ctx, field, source, selection.Args, selection.SelectionSet)
			if err != nil {
				return nil, err
			}

			resolved, err := execute(ctx, field.Type, value, selection.SelectionSet)
			if err != nil {
				return nil, err
			}
			fields[selection.Alias] = resolved
		}
<commit_msg>graphql: fix bug in cache key for incomparable source objects<commit_after>package graphql

import (
	"context"
	"fmt"
	"reflect"
	"runtime"
	"sync"

	"github.com/hashicorp/go-multierror"

	"github.com/samsarahq/thunder"
	"github.com/samsarahq/thunder/reactive"
)

// PrepareQuery checks that the given selectionSet matches the schema typ, and
// parses the args in selectionSet
func PrepareQuery(typ Type, selectionSet *SelectionSet) error {
	switch typ := typ.(type) {
	case *Scalar:
		if selectionSet != nil {
			return NewSafeError("scalar field must have no selections")
		}
		return nil

	case *Object:
		if selectionSet == nil {
			return NewSafeError("object field must have selections")
		}
		for _, selection := range selectionSet.Selections {
			field, ok := typ.Fields[selection.Name]
			if !ok {
				return NewSafeError(`unknown field "%s"`, selection.Name)
			}

			parsed, err := field.ParseArguments(selection.Args)
			if err != nil {
				return NewSafeError(`error parsing args for "%s": %s`, selection.Name, err)
			}
			selection.Args = parsed

			if err := PrepareQuery(field.Type, selection.SelectionSet); err != nil {
				return err
			}
		}
		for _, fragment := range selectionSet.Fragments {
			if err := PrepareQuery(typ, fragment.SelectionSet); err != nil {
				return err
			}
		}
		return nil

	case *List:
		return PrepareQuery(typ.Type, selectionSet)

	default:
		panic("unknown type kind")
	}
}

type panicError struct {
	message string
}

func (p panicError) Error() string {
	return p.message
}

func safeResolve(ctx context.Context, field *Field, source, args interface{}, selectionSet *SelectionSet) (result interface{}, err error) {
	defer func() {
		if panicErr := recover(); panicErr != nil {
			const size = 64 << 10
			buf := make([]byte, size)
			buf = buf[:runtime.Stack(buf, false)]
			result, err = nil, fmt.Errorf("graphql: panic: %v\n%s", panicErr, buf)
		}
	}()
	return field.Resolve(ctx, source, args, selectionSet)
}

type objectCacheKey struct {
	typ          *Object
	source       interface{}
	selectionSet *SelectionSet
}

// executeObject executes an object query
func executeObject(ctx context.Context, typ *Object, source interface{}, selectionSet *SelectionSet) (interface{}, error) {
	value := reflect.ValueOf(source)
	if value.Kind() == reflect.Ptr && value.IsNil() {
		return nil, nil
	}

	// cache the body of executeObject so that if the source doesn't change, we
	// don't need to recompute
	key := objectCacheKey{typ: typ, source: source, selectionSet: selectionSet}

	// some types can't be put in a map; for those, use a always different value
	// as source
	if value.IsValid() && !value.Type().Comparable() {
		key.source = new(byte)
	}

	return reactive.Cache(ctx, key, func(ctx context.Context) (interface{}, error) {
		selections := Flatten(selectionSet)

		fields := make(map[string]interface{})

		// for every selection, resolve the value and store it in the output object
		for _, selection := range selections {
			field := typ.Fields[selection.Name]
			value, err := safeResolve(ctx, field, source, selection.Args, selection.SelectionSet)
			if err != nil {
				return nil, err
			}

			resolved, err := execute(ctx, field.Type, value, selection.SelectionSet)
			if err != nil {
				return nil, err
			}
			fields[selection.Alias] = resolved
		}

{'fraction_non_alphanumeric': 0.12140042437102153, 'fraction_numerical': 0.0012124886329190664, 'mean_word_length': 3.8027656477438136, 'pattern_counts': {'":': 2, '<': 7, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6916921', 'n_tokens_mistral': 2240, 'n_tokens_neox': 2013, 'n_words': 641}