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 } }
// 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 }
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> }
// 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 }
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 }