Exemple #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
	}
}
Exemple #2
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
}
Exemple #3
0
func ExampleEval() {
	// Initialize context for the example. Normally the caller would obtain the
	// context from an input parameter or instantiate their own.
	ctx := context.Background()

	compiler := ast.NewCompiler()

	// Define a dummy query and some data that the query will execute against.
	query, err := compiler.QueryCompiler().Compile(ast.MustParseBody("data.a[_] = x, x >= 2"))
	if err != nil {
		// Handle error.
	}

	var data map[string]interface{}

	// OPA uses Go's standard JSON library but assumes that numbers have been
	// decoded as json.Number instead of float64. You MUST decode with UseNumber
	// enabled.
	decoder := json.NewDecoder(bytes.NewBufferString(`{"a": [1,2,3,4]}`))
	decoder.UseNumber()

	if err := decoder.Decode(&data); err != nil {
		// Handle error.
	}

	// Instantiate the policy engine's storage layer.
	store := storage.New(storage.InMemoryWithJSONConfig(data))

	// Create a new transaction. Transactions allow the policy engine to
	// evaluate the query over a consistent snapshot fo the storage layer.
	txn, err := store.NewTransaction(ctx)
	if err != nil {
		// Handle error.
	}

	defer store.Close(ctx, txn)

	// Prepare the evaluation parameters. Evaluation executes against the policy
	// engine's storage. In this case, we seed the storage with a single array
	// of number. Other parameters such as the request, tracing configuration,
	// etc. can be set on the Topdown object.
	t := topdown.New(ctx, query, compiler, store, txn)

	result := []interface{}{}

	// Execute the query and provide a callbakc function to accumulate the results.
	err = topdown.Eval(t, func(t *topdown.Topdown) error {

		// Each variable in the query will have an associated "binding" in the context.
		x := t.Binding(ast.Var("x"))

		// The bindings are ast.Value types so we will convert to a native Go value here.
		v, err := topdown.ValueToInterface(x, t)
		if err != nil {
			return err
		}

		result = append(result, v)
		return nil
	})

	// Inspect the query result.
	fmt.Println("result:", result)
	fmt.Println("err:", err)

	// Output:
	// result: [2 3 4]
	// err: <nil>
}
Exemple #4
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
}
Exemple #5
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
}