Query error automatically discards the transaction

Moved from GitHub dgo/105

Posted by danielmai:

This is a behavior change from dgo v1.0 to dgo v2. In dgo v2.1.0 a query error discards the current transaction, so future queries using the same txn are not allowed. This used to work in dgo v1.

Steps to reproduce

This is reproducible with the following sample code that runs these two queries in the same txn:

  1. {me(){}me(){}}: The first query returns a query error. In this case, Duplicate aliases not allowed: me.
  2. {me(){}}: This is a valid query that I expect should succeed.

dgo/v2:

package main

import (
	"context"
	"flag"
	"fmt"
	"log"

	"github.com/dgraph-io/dgo/v2"
	"github.com/dgraph-io/dgo/v2/protos/api"
	"google.golang.org/grpc"
)

var (
	addr = flag.String("addr", "localhost:9180", "Dgraph Alpha address.")

	ctx = context.Background()

	query1 = `{me(){}me(){}}`
	query2 = `{me(){}}`
)

func main() {
	flag.Parse()
	conn, err := grpc.Dial(*addr, grpc.WithInsecure())
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()
	dg := dgo.NewDgraphClient(api.NewDgraphClient(conn))

	txn := dg.NewTxn()

	resp, err := txn.Query(ctx, query1)
	if err != nil {
		log.Printf("Query 1 error: %v\n", err)
	} else {
		log.Printf("Response: %v\n", string(resp.Json))
	}

	resp, err = txn.Query(ctx, query2)
	if err != nil {
		log.Printf("Query 2 error: %v\n", err)
	} else {
		log.Printf("Response: %v\n", string(resp.Json))
	}

	txn.Discard(ctx)

	fmt.Println("Done.")
}

Output:

2019/11/15 11:43:12 Query 1 error: rpc error: code = Unknown desc = Duplicate aliases not allowed: me
2019/11/15 11:43:12 Query 2 error: Transaction has already been committed or discarded
Done.

Expected behavior

In dgo v1 this behaves differently. We can use the same example code but change the import paths to use dgo v1.0.0:

dgo (v1):

package main

import (
	"context"
	"flag"
	"fmt"
	"log"

	"github.com/dgraph-io/dgo"
	"github.com/dgraph-io/dgo/protos/api"
	"google.golang.org/grpc"
)

var (
	ctx  = context.Background()
	addr = flag.String("addr", "localhost:9180", "Dgraph Alpha address.")

	query1 = `{me(){}me(){}}`
	query2 = `{me(){}}`
)

func main() {
	flag.Parse()
	conn, err := grpc.Dial(*addr, grpc.WithInsecure())
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()
	dg := dgo.NewDgraphClient(api.NewDgraphClient(conn))

	txn := dg.NewTxn()

	resp, err := txn.Query(ctx, query1)
	if err != nil {
		log.Printf("Query 1 error: %v\n", err)
	} else {
		log.Printf("Response: %v\n", string(resp.Json))
	}

	resp, err = txn.Query(ctx, query2)
	if err != nil {
		log.Printf("Query 2 error: %v\n", err)
	} else {
		log.Printf("Response: %v\n", string(resp.Json))
	}

	txn.Discard(ctx)

	fmt.Println("Done.")
}

Output:

2019/11/15 11:45:15 Query 1 error: rpc error: code = Unknown desc = Duplicate aliases not allowed: me
2019/11/15 11:45:15 Response: {"me":[]}
Done.

Additional notes

In dgo v2 the Query and QueryWithVars methods internally call Do. And Do calls txn.Discard if a query returns an error.

Discarding txn after an error is only documented for the Mutate method in both dgo v1 and dgo v2:

If the mutation fails, then the transaction is discarded and all future operations on it will fail.

But discarding the transaction after a query error was not and is not the documented behavior for the Query methods in dgo.