Skip to content
Open
79 changes: 70 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,8 @@ err := client.SetSchema(context.TODO(), dgo.RootNamespace, sch)

### Running a Mutation

To run a mutation, use the `RunDQL` function.
To run a mutation, use the `RunDQL` function. Note that the namespace is set via the `WithNamespace`
option, if not set, the global namespace is used.

```go
mutationDQL := `{
Expand All @@ -189,10 +190,17 @@ mutationDQL := `{
_:alice <age> "29" .
}
}`
resp, err := client.RunDQL(context.TODO(), dgo.RootNamespace, mutationDQL)
resp, err := client.RunDQL(context.TODO(), mutationDQL)
// Handle error
// Print map of blank UIDs
fmt.Printf("%+v\n", resp.BlankUids)

// Perform this mutation in a already created namespace named "finance-graph"
resp, err = client.RunDQL(context.TODO(), mutationDQL, dgo.WithNamespace("finance-graph"))
// Handle error
// Print map of blank UIDs
fmt.Printf("%+v\n", resp.BlankUids)

```

### Running a Query
Expand All @@ -207,7 +215,7 @@ queryDQL := `{
age
}
}`
resp, err := client.RunDQL(context.TODO(), dgo.RootNamespace, queryDQL)
resp, err := client.RunDQL(context.TODO(), queryDQL)
// Handle error
fmt.Printf("%s\n", resp.QueryResult)
```
Expand All @@ -225,7 +233,7 @@ queryDQL = `query Alice($name: string) {
}
}`
vars := map[string]string{"$name": "Alice"}
resp, err := client.RunDQLWithVars(context.TODO(), dgo.RootNamespace, queryDQL, vars)
resp, err := client.RunDQLWithVars(context.TODO(), queryDQL, vars)
// Handle error
fmt.Printf("%s\n", resp.QueryResult)
```
Expand All @@ -242,7 +250,7 @@ queryDQL := `{
age
}
}`
resp, err := client.RunDQL(context.TODO(), dgo.RootNamespace, queryDQL, dgo.WithBestEffort())
resp, err := client.RunDQL(context.TODO(), queryDQL, dgo.WithBestEffort())
// Handle error
fmt.Printf("%s\n", resp.QueryResult)
```
Expand All @@ -259,7 +267,24 @@ queryDQL := `{
age
}
}`
resp, err := client.RunDQL(context.TODO(), dgo.RootNamespace, queryDQL, dgo.WithReadOnly())
resp, err := client.RunDQL(context.TODO(), queryDQL, dgo.WithReadOnly())
// Handle error
fmt.Printf("%s\n", resp.QueryResult)
```

### Running a Query in a Namespace

To run a query in a namespace, use the same `RunDQL` function with `TxnOption`.

```go
queryDQL := `{
alice(func: eq(name, "Alice")) {
name
email
age
}
}`
resp, err := client.RunDQL(context.TODO(), queryDQL, dgo.WithNamespace("finance-graph"), dgo.WithReadOnly())
// Handle error
fmt.Printf("%s\n", resp.QueryResult)
```
Expand Down Expand Up @@ -359,6 +384,42 @@ namespaces, err := client.ListNamespaces(context.TODO())
fmt.Printf("%+v\n", namespaces)
```

### v2 Transactions

Transactions can be created using the `NewTxn` function. This is the same function that is available
in the v1 APIs -- v2 adds a number of new options to the transaction including namepaces via the
`WithNamespace` option.

```go
txn := dgraphClient.NewTxn()
defer txn.Discard(ctx)
```

#### v2 Transactions with Namespaces

```go
txn := dgraphClient.NewTxn(dgo.WithNamespace("finance-graph"))
defer txn.Discard(ctx)
```

Note that the namespace needs to exist in order for operations on this transaction to succeed.
Operations may not explicitly fail if the namespace does not exist, and will operate on the global
namespace instead. You can ensure that the namespace exists by using the `ListNamespaces` function.

#### v2 Transactions with ReadOnly and BestEffort Options

```go
txn := dgraphClient.NewTxn(dgo.WithReadOnly())
defer txn.Discard(ctx)
```

```go
txn := dgraphClient.NewTxn(dgo.WithBestEffort())
defer txn.Discard(ctx)
```

Note that `WithBestEffort` implies a `ReadOnly` transaction, so `WithReadOnly` is not needed.

## v1 APIs

### Creating a Client
Expand Down Expand Up @@ -675,9 +736,9 @@ dg.Alter(ctx, &op)

### Running tests

Make sure you have `dgraph` installed in your GOPATH before you run the tests. The dgo test suite
requires that a Dgraph cluster with ACL enabled be running locally. To start such a cluster, you may
use the docker compose file located in the testing directory `t`.
Make sure you have a linux-compatible `dgraph` binary installed in your $GOPATH/bin before you run
the tests. The dgo test suite requires that a Dgraph cluster with ACL enabled be running locally. To
start such a cluster, you may use the docker compose file located in the testing directory `t`.

```sh
docker compose -f t/docker-compose.yml up -d
Expand Down
21 changes: 20 additions & 1 deletion examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import (
)

const (
dgraphAddress = "127.0.0.1:9180"
dgraphAddress = "127.0.0.1:9180"
dgraphAddressNoAcl = "127.0.0.1:9084"
)

type CancelFunc func()
Expand All @@ -41,6 +42,24 @@ func getDgraphClient() (*dgo.Dgraph, CancelFunc) {
return dg, func() { dg.Close() }
}

func getDgraphClientNoAcl() (*dgo.Dgraph, CancelFunc) {
var (
err error
dg *dgo.Dgraph
)
for {
dg, err = dgo.Open(fmt.Sprintf("dgraph://%s?sslmode=disable", dgraphAddressNoAcl))
if err == nil || !strings.Contains(err.Error(), "Please retry") {
break
}
time.Sleep(time.Second)
}
if err != nil {
log.Fatalf("Error while trying to open client %v", err.Error())
}
return dg, func() { dg.Close() }
}

func ExampleDgraph_Alter_dropAll() {
dg, cancel := getDgraphClient()
defer cancel()
Expand Down
31 changes: 25 additions & 6 deletions nsv2.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type txnOptions struct {
readOnly bool
bestEffort bool
respFormat apiv2.RespFormat
namespace string
}

// TxnOption is a function that modifies the txn options.
Expand All @@ -38,7 +39,10 @@ func WithReadOnly() TxnOption {
}
}

// WithBestEffort sets the txn to be best effort.
// WithBestEffort sets the txn to be best effort. BestEffort queries will ask the Dgraph Alpha to
// get timestamps from memory in a best effort to reduce the number of outbound requests to
// Zero. This may yield improved latencies in read-bound datasets. BestEffort queries imply
// read-only transactions.
func WithBestEffort() TxnOption {
return func(o *txnOptions) error {
o.readOnly = true
Expand All @@ -47,6 +51,16 @@ func WithBestEffort() TxnOption {
}
}

// WithNamespace sets the namespace for the transaction. If the namespace is non-existent,
// operations on the transaction will operate on the global namespace. You can ensure that the
// namespace exists by using the `ListNamespaces` function.
func WithNamespace(namespace string) TxnOption {
return func(o *txnOptions) error {
o.namespace = namespace
return nil
}
}

func buildTxnOptions(opts ...TxnOption) (*txnOptions, error) {
topts := &txnOptions{}
for _, opt := range opts {
Expand All @@ -61,22 +75,27 @@ func buildTxnOptions(opts ...TxnOption) (*txnOptions, error) {
return topts, nil
}

// RunDQL runs a DQL query in the given namespace. A DQL query could be a mutation
// or a query or an upsert which is a combination of mutations and queries.
func (d *Dgraph) RunDQL(ctx context.Context, nsName string, q string, opts ...TxnOption) (
// RunDQL runs a DQL query. A DQL query could be a mutation or a query or an upsert which is a
// combination of mutations and queries. The namespace is set via the WithNamespace option, if
// not set, the global namespace is used.
func (d *Dgraph) RunDQL(ctx context.Context, q string, opts ...TxnOption) (
*apiv2.RunDQLResponse, error) {

return d.RunDQLWithVars(ctx, nsName, q, nil, opts...)
return d.RunDQLWithVars(ctx, q, nil, opts...)
}

// RunDQLWithVars is like RunDQL with variables.
func (d *Dgraph) RunDQLWithVars(ctx context.Context, nsName string, q string,
func (d *Dgraph) RunDQLWithVars(ctx context.Context, q string,
vars map[string]string, opts ...TxnOption) (*apiv2.RunDQLResponse, error) {

topts, err := buildTxnOptions(opts...)
if err != nil {
return nil, err
}
nsName := RootNamespace
if topts.namespace != "" {
nsName = topts.namespace
}

req := &apiv2.RunDQLRequest{NsName: nsName, DqlQuery: q, Vars: vars,
ReadOnly: topts.readOnly, BestEffort: topts.bestEffort, RespFormat: topts.respFormat}
Expand Down
31 changes: 31 additions & 0 deletions t/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,34 @@ services:
command:
/gobin/dgraph zero -o 100 --raft='idx=1' --my=zero1:5180 --replicas=3 --logtostderr -v=2
--bindall

zero_noacl:
image: dgraph/dgraph:local
container_name: zero_noacl
working_dir: /zero_noacl_data
ports:
- 5081:5080
- 6081:6080
volumes:
- type: bind
source: $GOPATH/bin
target: /gobin
read_only: true
command: >
/gobin/dgraph zero --my=zero_noacl:5080 --logtostderr -v=2 --bindall

alpha_noacl:
image: dgraph/dgraph:local
container_name: alpha_noacl
working_dir: /alpha_noacl_data
ports:
- 8084:8080
- 9084:9080
volumes:
- type: bind
source: $GOPATH/bin
target: /gobin
read_only: true
command: >
/gobin/dgraph alpha --my=alpha_noacl:7080 --zero=zero_noacl:5080 --logtostderr -v=2 --security
whitelist=0.0.0.0/0 --bindall
43 changes: 39 additions & 4 deletions txn.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ var (
// is a no-op if Commit has already been called, so it's safe to defer a call
// to Discard immediately after NewTxn.
type Txn struct {
context *api.TxnContext
context *api.TxnContext
namespace string

keys map[string]struct{}
preds map[string]struct{}
Expand All @@ -50,18 +51,39 @@ type Txn struct {
dc api.DgraphClient
}

// NewTxn creates a new transaction.
func (d *Dgraph) NewTxn() *Txn {
return &Txn{
// NewTxn creates a new transaction with optional configuration.
// Options include WithReadOnly(), WithBestEffort(), and WithNamespace(namespace).
func (d *Dgraph) NewTxn(opts ...TxnOption) *Txn {
txn := &Txn{
dg: d,
dc: d.anyClient(),
context: &api.TxnContext{},
keys: make(map[string]struct{}),
preds: make(map[string]struct{}),
}

// Apply options if provided
if len(opts) > 0 {
topts, err := buildTxnOptions(opts...)
if err != nil {
// In order to keep the API backwards compatible, we'll panic if an error is returned.
// At the moment, no TxnOption returns an error.
panic(err)
} else {
if !topts.readOnly && topts.bestEffort {
panic("Best effort only works for read-only queries.")
}
txn.readOnly = topts.readOnly
txn.bestEffort = topts.bestEffort
txn.namespace = topts.namespace
}
}

return txn
}

// NewReadOnlyTxn sets the txn to readonly transaction.
// @deprecated Use NewTxn(dgo.WithReadOnly()) instead.
func (d *Dgraph) NewReadOnlyTxn() *Txn {
txn := d.NewTxn()
txn.readOnly = true
Expand All @@ -74,6 +96,7 @@ func (d *Dgraph) NewReadOnlyTxn() *Txn {
//
// This method will panic if the transaction is not read-only.
// Returns the transaction itself.
// @deprecated Use NewTxn(dgo.WithBestEffort()) instead.
func (txn *Txn) BestEffort() *Txn {
if !txn.readOnly {
panic("Best effort only works for read-only queries.")
Expand Down Expand Up @@ -165,6 +188,10 @@ func (txn *Txn) Do(ctx context.Context, req *api.Request) (*api.Response, error)
req.StartTs = txn.context.StartTs
req.Hash = txn.context.Hash

if txn.namespace != "" {
ctx = metadata.AppendToOutgoingContext(ctx, "namespace-str", txn.namespace)
}

// Append the GRPC Response headers to the responses. Needed for Cloud.
appendHdr := func(hdrs *metadata.MD, resp *api.Response) {
if resp != nil {
Expand Down Expand Up @@ -294,6 +321,10 @@ func (txn *Txn) commitOrAbort(ctx context.Context) error {
}

ctx = txn.dg.getContext(ctx)
if txn.namespace != "" {
ctx = metadata.AppendToOutgoingContext(ctx, "namespace-str", txn.namespace)
}

_, err := txn.dc.CommitOrAbort(ctx, txn.context)

if isJwtExpired(err) {
Expand All @@ -303,6 +334,10 @@ func (txn *Txn) commitOrAbort(ctx context.Context) error {
}

ctx = txn.dg.getContext(ctx)
if txn.namespace != "" {
ctx = metadata.AppendToOutgoingContext(ctx, "namespace-str", txn.namespace)
}

_, err = txn.dc.CommitOrAbort(ctx, txn.context)
}

Expand Down
Loading
Loading