Beispiel #1
0
func (s *Server) execQuery(ctx context.Context, compiler *ast.Compiler, txn storage.Transaction, query ast.Body, explainMode explainModeV1) (interface{}, error) {

	t := topdown.New(ctx, query, s.Compiler(), s.store, txn)

	var buf *topdown.BufferTracer

	if explainMode != explainOffV1 {
		buf = topdown.NewBufferTracer()
		t.Tracer = buf
	}

	resultSet := adhocQueryResultSetV1{}

	err := topdown.Eval(t, func(t *topdown.Topdown) error {
		result := map[string]interface{}{}
		var err error
		t.Locals.Iter(func(k, v ast.Value) bool {
			kv, ok := k.(ast.Var)
			if !ok {
				return false
			}
			if kv.IsWildcard() {
				return false
			}
			vv, e := topdown.ValueToInterface(v, t)
			if e != nil {
				err = e
				return true
			}
			result[string(kv)] = vv
			return false
		})
		if err != nil {
			return err
		}
		if len(result) > 0 {
			resultSet = append(resultSet, result)
		}
		return nil
	})

	if err != nil {
		return nil, err
	}

	switch explainMode {
	case explainFullV1:
		return newTraceV1(*buf), nil
	case explainTruthV1:
		answer, err := explain.Truth(compiler, *buf)
		if err != nil {
			return nil, err
		}
		return newTraceV1(answer), nil
	default:
		return resultSet, nil
	}
}
Beispiel #2
0
func explainQuery(data string, module string) ([]*topdown.Event, []*topdown.Event, error) {

	compiler := ast.NewCompiler()
	mods := map[string]*ast.Module{"": ast.MustParseModule(module)}

	if compiler.Compile(mods); compiler.Failed() {
		panic(compiler.Errors)
	}

	buf := topdown.NewBufferTracer()
	executeQuery(data, compiler, buf)

	answer, err := Truth(compiler, *buf)
	return answer, *buf, err
}
Beispiel #3
0
// evalTermSingleValue evaluates and prints terms in cases where the term evaluates to a
// single value, e.g., "1", true, [1,2,"foo"], [x | x = a[i], a = [1,2,3]], etc. Ground terms
// and comprehensions always evaluate to a single value. To handle references, this function
// still executes the query, except it does so by rewriting the body to assign the term
// to a variable. This allows the REPL to obtain the result even if the term is false.
func (r *REPL) evalTermSingleValue(ctx context.Context, compiler *ast.Compiler, request ast.Value, body ast.Body) error {

	term := body[0].Terms.(*ast.Term)
	outputVar := ast.Wildcard
	body = ast.NewBody(ast.Equality.Expr(term, outputVar))

	t := topdown.New(ctx, body, compiler, r.store, r.txn)
	t.Request = request

	var buf *topdown.BufferTracer

	if r.explain != explainOff {
		buf = topdown.NewBufferTracer()
		t.Tracer = buf
	}

	var result interface{}
	isTrue := false

	err := topdown.Eval(t, func(t *topdown.Topdown) error {
		p := t.Locals.Get(outputVar.Value)
		v, err := topdown.ValueToInterface(p, t)
		if err != nil {
			return err
		}
		result = v
		isTrue = true
		return nil
	})

	if buf != nil {
		r.printTrace(ctx, compiler, *buf)
	}

	if err != nil {
		return err
	}

	if isTrue {
		r.printJSON(result)
	} else if !r.undefinedDisabled {
		r.printUndefined()
	}

	return nil
}
Beispiel #4
0
func (s *Server) v1DataGet(w http.ResponseWriter, r *http.Request) {

	// Gather request parameters.
	ctx := r.Context()
	vars := mux.Vars(r)
	path := stringPathToDataRef(vars["path"])
	pretty := getPretty(r.URL.Query()["pretty"])
	explainMode := getExplain(r.URL.Query()["explain"])
	request, nonGround, err := parseRequest(r.URL.Query()[ParamRequestV1])

	if err != nil {
		handleError(w, 400, err)
		return
	}

	if nonGround && explainMode != explainOffV1 {
		handleError(w, 400, fmt.Errorf("explanations with non-ground request values not supported"))
		return
	}

	// Prepare for query.
	txn, err := s.store.NewTransaction(ctx)
	if err != nil {
		handleErrorAuto(w, err)
		return
	}

	defer s.store.Close(ctx, txn)

	compiler := s.Compiler()
	params := topdown.NewQueryParams(ctx, compiler, s.store, txn, request, path)

	var buf *topdown.BufferTracer
	if explainMode != explainOffV1 {
		buf = topdown.NewBufferTracer()
		params.Tracer = buf
	}

	// Execute query.
	qrs, err := topdown.Query(params)

	// Handle results.
	if err != nil {
		handleErrorAuto(w, err)
		return
	}

	if qrs.Undefined() {
		if explainMode == explainFullV1 {
			handleResponseJSON(w, 404, newTraceV1(*buf), pretty)
		} else {
			handleResponse(w, 404, nil)
		}
		return
	}

	if nonGround {
		handleResponseJSON(w, 200, newQueryResultSetV1(qrs), pretty)
		return
	}

	result := qrs[0].Result

	switch explainMode {
	case explainOffV1:
		handleResponseJSON(w, 200, result, pretty)
	case explainFullV1:
		handleResponseJSON(w, 200, newTraceV1(*buf), pretty)
	case explainTruthV1:
		answer, err := explain.Truth(compiler, *buf)
		if err != nil {
			handleErrorAuto(w, err)
			return
		}
		handleResponseJSON(w, 200, newTraceV1(answer), pretty)
	}
}
Beispiel #5
0
// evalTermMultiValue evaluates and prints terms in cases where the term may evaluate to multiple
// ground values, e.g., a[i], [servers[x]], etc.
func (r *REPL) evalTermMultiValue(ctx context.Context, compiler *ast.Compiler, request ast.Value, body ast.Body) error {

	// Mangle the expression in the same way we do for evalTermSingleValue. When handling the
	// evaluation result below, we will ignore this variable.
	term := body[0].Terms.(*ast.Term)
	outputVar := ast.Wildcard
	body = ast.NewBody(ast.Equality.Expr(term, outputVar))

	t := topdown.New(ctx, body, compiler, r.store, r.txn)
	t.Request = request

	var buf *topdown.BufferTracer

	if r.explain != explainOff {
		buf = topdown.NewBufferTracer()
		t.Tracer = buf
	}

	vars := map[string]struct{}{}
	results := []map[string]interface{}{}
	resultKey := string(term.Location.Text)

	// Do not include the value of the input term if the input term was a set reference. E.g.,
	// for "p[x]", the value users are interested in is "x" not p[x] which is always defined
	// as true.
	includeValue := !r.isSetReference(compiler, term)

	err := topdown.Eval(t, func(t *topdown.Topdown) error {

		result := map[string]interface{}{}

		var err error

		t.Locals.Iter(func(k, v ast.Value) bool {
			if k, ok := k.(ast.Var); ok {
				if k.IsWildcard() || k.Equal(outputVar.Value) {
					return false
				}
				x, e := topdown.ValueToInterface(v, t)
				if e != nil {
					err = e
					return true
				}
				s := string(k)
				result[s] = x
				vars[s] = struct{}{}
			}
			return false
		})

		if err != nil {
			return err
		}

		if includeValue {
			p := topdown.PlugTerm(term, t.Binding)
			v, err := topdown.ValueToInterface(p.Value, t)
			if err != nil {
				return err
			}
			result[resultKey] = v
		}

		results = append(results, result)

		return nil
	})

	if buf != nil {
		r.printTrace(ctx, compiler, *buf)
	}

	if err != nil {
		return err
	}

	if len(results) > 0 {
		keys := []string{}
		for v := range vars {
			keys = append(keys, v)
		}
		sort.Strings(keys)
		if includeValue {
			keys = append(keys, resultKey)
		}
		r.printResults(keys, results)
	} else if !r.undefinedDisabled {
		r.printUndefined()
	}

	return nil
}
Beispiel #6
0
func (r *REPL) evalBody(ctx context.Context, compiler *ast.Compiler, request ast.Value, body ast.Body) error {

	// Special case for positive, single term inputs.
	if len(body) == 1 {
		expr := body[0]
		if !expr.Negated {
			if _, ok := expr.Terms.(*ast.Term); ok {
				if singleValue(body) {
					return r.evalTermSingleValue(ctx, compiler, request, body)
				}
				return r.evalTermMultiValue(ctx, compiler, request, body)
			}
		}
	}

	t := topdown.New(ctx, body, compiler, r.store, r.txn)
	t.Request = request

	var buf *topdown.BufferTracer

	if r.explain != explainOff {
		buf = topdown.NewBufferTracer()
		t.Tracer = buf
	}

	// Flag indicates whether the query was defined for some context.
	// If the query does not include any ground terms, the results will
	// be empty, but we still want to output "true". If there are
	// no results, this will remain "false" and we will output "false".
	var isTrue = false

	// Store bindings as slice of maps where map keys are variables
	// and values are the underlying Go values.
	var results []map[string]interface{}

	// Execute query and accumulate results.
	err := topdown.Eval(t, func(t *topdown.Topdown) error {
		var err error
		row := map[string]interface{}{}
		t.Locals.Iter(func(k, v ast.Value) bool {
			kv, ok := k.(ast.Var)
			if !ok {
				return false
			}
			if kv.IsWildcard() {
				return false
			}
			r, e := topdown.ValueToInterface(v, t)
			if e != nil {
				err = e
				return true
			}
			row[k.String()] = r
			return false
		})

		if err != nil {
			return err
		}

		isTrue = true

		if len(row) > 0 {
			results = append(results, row)
		}

		return nil
	})

	if buf != nil {
		r.printTrace(ctx, compiler, *buf)
	}

	if err != nil {
		return err
	}

	if isTrue {
		if len(results) >= 1 {
			r.printResults(getHeaderForBody(body), results)
		} else {
			fmt.Fprintln(r.output, "true")
		}
	} else {
		fmt.Fprintln(r.output, "false")
	}

	return nil
}