Git Product home page Git Product logo

go-driver's Introduction

ArangoDB-Logo

ArangoDB GO Driver

This project contains the official Go driver for the ArangoDB database.

CircleCI GoDoc

Warning V1 is deprecated and will not receive any new features. Please use V2 instead.

Supported Go Versions

Go 1.19 Go 1.20 Go 1.21
1.5.0-1.6.1 - -
1.6.2
2.1.0
master

Supported Versions

ArangoDB 3.10 ArangoDB 3.11 ArangoDB 3.12
1.5.0 - -
1.6.0 -
2.1.0
master + + +

Key:

  • Exactly the same features in both driver and the ArangoDB version.
  • + Features included in driver may be not present in the ArangoDB API. Calls to the ArangoDB may result in unexpected responses (404).
  • - The ArangoDB has features which are not supported by driver.

go-driver's People

Contributors

ajanikow avatar dependabot[bot] avatar elvuel avatar ewoutp avatar fceller avatar ftknox avatar gnusi avatar graetzer avatar howjmay avatar informalict avatar jsteemann avatar jwierzbo avatar kvahed avatar kvs85 avatar lephenix47 avatar maierlars avatar maxkernbach avatar neunhoef avatar nicolasgb avatar nikita-vanyasin avatar ph0tonic avatar phifty avatar ruidscampos avatar simran-b avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

go-driver's Issues

Edge document with user provided key is inserted as many times as the number of shards

ArangoDB edition: 3.3.10 Enterprise
ArangoDB go-driver master branch commit c2828ac

I have an edge collection with the following options:

keyOptions := &driver.CollectionKeyOptions{
	AllowUserKeys: true,
}

options := &driver.CreateCollectionOptions{
	ReplicationFactor: 2,
	NumberOfShards:    20,
	Type:              driver.CollectionTypeEdge,
	IndexBuckets:      64,
	KeyOptions:        keyOptions,
}

Then I try with a custom key to add the following document:

LET edge = ( 
	INSERT {
		_key: @key,
		_from: @fromID,
		_to: @toID
	} IN @@edgeCollection 
	RETURN NEW)

RETURN edge

The result is that I have 20 documents added in the collection (equal to the number of shards). If I change the number of shards, the number of the inserted documents still matches the number of shards.

However if I do not use a user provided key the insertion works as expected.

EDIT: I checked the same query through the ArangoDB UI and it seems to work properly, so I presume that this has to do with the driver.

illegal document key when supplied with empty `_key`

I am facing an issue wherein whenever I try to save a struct, it says illegal document key. Here is what the struct looks like:

type User struct {
    Email     string    `json:"email"`
    Name      string    `json:"name"`
    Password  string    `json:"password"`
    DBKey     string    `json:"_key,omitmepty"`
    DBID      string    `json:"_id,omitmepty"`
    DBRev     string    `json:"_rev,omitmepty"`
    CreatedAt time.Time `json:"createdAt"`
    UpdatedAt time.Time `json:"updatedAt"`
    DeletedAt time.Time `json:"deletedAt"`
}

I am guessing it is because _key comes as an empty string when I pass along an object of this struct. I do not want to generate the keys at my end and want to rely on arango for the same.
Is there a workaround to this?

We welcome your feedback!

This ArangoDB Go driver is still under heavy development, but the concepts behind the API and most of the API functions & types are getting close to what we want them to be.

Note: WE DO NOT GUARANTEE ANY API STABILITY YET.

Before we finalize the API we welcome everyone to to give their feedback on the API. All questions, remarks & suggestions are really appreciated.

To discuss topics, please create an issue in this repo. We'll try to respond as quickly as possible.

Graph Support

Hi,

Do you have any examples of graph creation and support. I would like to work with Go but I could not find real examples about graph support

Thanks

cursor implementation when running queries with non-Document return values

First off thank you for this driver and especially the comprehensive errors returned.
One thing though, when running a query with return value other than the document like:

cursor, err := db.Query(
        ctx,
        "insert @doc in @@col return NEW.field_of_type_string",
        map[string]interface{}{
                "doc": someDoc,
                "@col": collectionName,
       },
)

Results in an error:
json: cannot unmarshal string into Go value of type driver.DocumentMeta

I was thinking of ignoring the error returned here. That way in case the user returns a value
with DocumentMeta fields, it doesn't fail.

Unable to connect to server in macos

Tried below example , it does not work, unable to connect


package main

import (
        "fmt"
        "log"

        driver "github.com/arangodb/go-driver"
        "github.com/arangodb/go-driver/http"
)

func ExampleNewClient() {
        // Create an HTTP connection to the database
        conn, err := http.NewConnection(http.ConnectionConfig{
                Endpoints: []string{"http://localhost:8529"},
        })
        if err != nil {
                log.Fatalf("Failed to create HTTP connection: %v", err)
        }
        // Create a client
        c, err := driver.NewClient(driver.ClientConfig{
                Connection: conn,
        })
        // Ask the version of the server
        versionInfo, err := c.Version(nil)
        if err != nil {
                log.Fatalf("Failed to get version info: %v", err)
        }
        fmt.Printf("Database has version '%s' and license '%s'\n", versionInfo.Version, versionInfo.License)
}

func main(){
        ExampleNewClient()
}

I am using macos sierra & Arango 3.1.22

Multi-tenancy connection management

Hey guys, do you have any recommendations on how to approach this task?
I need to manage a pool of connections in a multi-tenancy environment, means I have multiple DBs to talk to. Since I cannot afford creating a new Db connection upon each request, I have to create a pool of connections.

The way how DB connection is meant to be created is to:

  1. conn, err := graphHTTP.NewConnection(...)
  2. c, err := graphDriver.NewClient(...)
  3. db, err := p.cl.Database(...)

Implementation of the #3 uses a copy of the conn from #1. What it means to me is that even if I maintain my own pool of the db objects, every single one will hold a copy of the identical connections (in fact it's a connection pool). Since Database constructor is defined as private (newDatabase), even if I want to write my own implementation of #3 (with a pointer to the conn), I cannot bypass it.

Any suggestion on how to address the issue?

net/http: request canceled

Hi. I'm running a local ArangoDB in a docker container and accesing the DB with your code. It appears when I try to run multiple queries in a loop I'm spiratically getting this error:

net/http: request canceled

Sometimes the querys run fine sometimes I get that error. Have you seen this before? Let me know if you need anything else.

Querying documents +

i use your https://godoc.org/github.com/arangodb/go-driver#CollectionDocuments for get 50.000 documents. That takes 12 seconds. That's too long for me :) Or is there another function?

```

ctx := context.Background()
query := FOR d IN user FILTER d.Del==@del RETURN d
bind := map[string]interface{}{
"del": 0,
}
cursor, err := arangodb.Db.Query(ctx, query, bind)
helper.CheckErr(err)

defer cursor.Close()

var result []models.UserAttributes

for {
	var l models.UserAttributes
	_, err := cursor.ReadDocument(ctx, &l)
	if driver.IsNoMoreDocuments(err) {
		break
	} else if err != nil {
		return
	}
	result = append(result, l)
}

Improve performance of Connection.Do for large results

Hi guys,

It's been now a long time I'm using this driver and just wanted to thank you for the work done.

My Application

I'm stuck in an issue concerning queries that returns around 350k documents (more of less same situation than these issues found here #76 and here #70 ). When there's a lot of document to retrieve, the request is way too long to proceed.

I tried both way using a Cursor and, in another hand, directly using client connection to proceed with an export request but still got the same latency issue of a really long document retrievement.

Here how I was making my request when using client connection.

// [...] Connection with entrypoints a list of arango instances (just one address for the moment)

conn, err := http.NewConnection(http.ConnectionConfig{
	Endpoints: entrypoints,
	TLSConfig: &tls.Config{InsecureSkipVerify: true},
})
if err != nil {
	panic("[PANIC] When connecting a HTTP connection : " + err.Error())
}

// [...] Client Connection

client, err := driver.NewClient(driver.ClientConfig{
	Connection:     conn,
	Authentication: driver.BasicAuthentication(user, password),
})
if err != nil {
	panic("[PANIC] Could not instantiate new Client : " + err.Error())
}

// [...] Requesting Database

conn := client.Connection()
req, err := conn.NewRequest("PUT",  "/_db/dbtest/_api/export?collection=collectiontest")
if err != nil {
	return nil, fmt.Errorf("could not retrieve documents")
}
req.SetBody(struct {
	BatchSize int  `json:"batchSize"`
	Count     bool `json:"count"`
	Flush     bool `json:"flush"`
	TTL       int  `json:"ttl"`
}{50000, true, true, 360})

t := time.Now()
resp, err := conn.Do(ctx, req)
log.Printf("Since %s", time.Since(t))

if err != nil {
	return nil, fmt.Errorf("could not retrieve")
}
return resp, nil

// [...] Request next batch if more

conn := client.Connection()
var err error
req, err := conn.NewRequest("PUT",  "/_db/dbtest/_api/export?collection=collectiontest")
if err != nil {
	return nil, fmt.Errorf("could not retrieve next batch")
}
ctxTimeout, cancel := context.WithTimeout(ctx, 6*60*time.Second)
defer cancel()
var resp driver.Response
done := make(chan bool, 0)
go func(resp *driver.Response, obj interface{}, err *error) {
	*resp, *err = conn.Do(ctxTimeout, req)
	close(done)
}(&resp, obj, &err)

select {
case <-done:
	if err != nil {
		return nil, fmt.Errorf("could not retrieve documents")
	}
	return resp, nil
case <-ctxTimeout.Done():
	if attempt <= 0 {
		return nil, fmt.Errorf("could not retrieve all documents")
	}
}

Even if I tried the request with a batch size of 5000 / 50000 / 500000, retrievement time is the same, even worse when we decrease it more.

The best time I made for 350k documents was 20 seconds.

I was then questioning myself about what could really decelerate result retrieving then I went deeper into arangodb/go-driver sources.

My investigation

I found out this piece of code ( in file github.com/arangodb/go-driver/http/connection.go ) which is the source used when I'm calling conn.Do from your driver:

// Do performs a given request, returning its response.
func (c *httpConnection) Do(ctx context.Context, req driver.Request) (driver.Response, error) {
	httpReq, ok := req.(httpRequest)
	if !ok {
		return nil, driver.WithStack(driver.InvalidArgumentError{Message: "request is not a httpRequest"})
	}
	r, err := httpReq.createHTTPRequest(c.endpoint)
	rctx := ctx
	if rctx == nil {
		rctx = context.Background()
	}
	rctx = httptrace.WithClientTrace(rctx, &httptrace.ClientTrace{
		WroteRequest: func(info httptrace.WroteRequestInfo) {
			httpReq.WroteRequest(info)
		},
	})
	r = r.WithContext(rctx)
	if err != nil {
		return nil, driver.WithStack(err)
	}

	// Block on too many concurrent connections
	if c.connPool != nil {
		select {
		case t := <-c.connPool:
			// Ok, we're allowed to continue
			defer func() {
				// Give back token
				c.connPool <- t
			}()
		case <-rctx.Done():
			// Context cancelled or expired
			return nil, driver.WithStack(rctx.Err())
		}
	}

	resp, err := c.client.Do(r)
	if err != nil {
		return nil, driver.WithStack(err)
	}
	var rawResponse *[]byte
	if ctx != nil {
		if v := ctx.Value(keyRawResponse); v != nil {
			if buf, ok := v.(*[]byte); ok {
				rawResponse = buf
			}
		}
	}

	// Read response body
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, driver.WithStack(err)
	}
	if rawResponse != nil {
		*rawResponse = body
	}

	ct := resp.Header.Get("Content-Type")
	var httpResp driver.Response
	switch strings.Split(ct, ";")[0] {
	case "application/json", "application/x-arango-dump":
		httpResp = &httpJSONResponse{resp: resp, rawResponse: body}
	case "application/x-velocypack":
		httpResp = &httpVPackResponse{resp: resp, rawResponse: body}
	default:
		if resp.StatusCode == http.StatusUnauthorized {
			// When unauthorized the server sometimes return a `text/plain` response.
			return nil, driver.WithStack(driver.ArangoError{
				HasError:     true,
				Code:         resp.StatusCode,
				ErrorMessage: string(body),
			})
		}
		// Handle empty 'text/plain' body as empty JSON object
		if len(body) == 0 {
			body = []byte("{}")
			if rawResponse != nil {
				*rawResponse = body
			}
			httpResp = &httpJSONResponse{resp: resp, rawResponse: body}
		} else {
			return nil, driver.WithStack(fmt.Errorf("Unsupported content type '%s' with status %d and content '%s'", ct, resp.StatusCode, string(body)))
		}
	}
	if ctx != nil {
		if v := ctx.Value(keyResponse); v != nil {
			if respPtr, ok := v.(*driver.Response); ok {
				*respPtr = httpResp
			}
		}
	}
	return httpResp, nil
}

I measured the time elapsed from this tiny piece of code from above :

	resp, err := c.client.Do(r)
	if err != nil {
		return nil, driver.WithStack(err)
	}

which has an acceptable execution time (like 10% of the total 20 second latency, about 2-3 seconds). I was then wondering where the other 90% went?

Then I found out this :

	// Read response body
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, driver.WithStack(err)
	}

which take the other 90% of time of latency.

Doing some research about ioutil.ReadAll ended up like not being a good practice to use it. Especially that it reallocates every 512 bytes the buffer and redo a copy of the precedent informations + the new data to append.

More the body is big and longer is the treatment of it.

Here are the official sources concerning ioutil.ReadAll :

// readAll reads from r until an error or EOF and returns the data it read
// from the internal buffer allocated with a specified capacity.
func readAll(r io.Reader, capacity int64) (b []byte, err error) {
	var buf bytes.Buffer
	// If the buffer overflows, we will get bytes.ErrTooLarge.
	// Return that as an error. Any other panic remains.
	defer func() {
		e := recover()
		if e == nil {
			return
		}
		if panicErr, ok := e.(error); ok && panicErr == bytes.ErrTooLarge {
			err = panicErr
		} else {
			panic(e)
		}
	}()
	if int64(int(capacity)) == capacity {
		buf.Grow(int(capacity))
	}
	_, err = buf.ReadFrom(r)
	return buf.Bytes(), err
}

// ReadAll reads from r until an error or EOF and returns the data it read.
// A successful call returns err == nil, not err == EOF. Because ReadAll is
// defined to read from src until EOF, it does not treat an EOF from Read
// as an error to be reported.
func ReadAll(r io.Reader) ([]byte, error) {
	return readAll(r, bytes.MinRead)
}

With bytes.MinRead = 512 and Grow() defined here : https://golang.org/src/bytes/buffer.go

Every 512 bytes read, a reallocation + deep copy is processed.

I tried to propose a way to change how to access data by making a pull request but I did not know how to modify your sources without having incidence on other file but it would be really good to be able to withdraw ioutil.ReadAll from use here especially when there's requests case like returning a results with a lot of document responses.

We would here being able to have a significant improve of requests execution time client side.

Can we bufferized more Body to fill once its content thanks ContentLength set previously ?

stable and full feature?

Is this driver stable enough for production? And does it support full feature of the latest version of ArangoDB?

Why only use the current server node each time in cluster.Do()?

conn, _ := http.NewConnection(http.ConnectionConfig{
	Endpoints: []string{"endpoint_a", "endpoint_b", "endpoint_c"},
})
c, _ := driver.NewClient(driver.ClientConfig{
	Connection:     conn,
	Authentication: driver.BasicAuthentication("user", "passwd"),
})
ctx := context.Background()
db, _ := c.Database(ctx, "mydatabase")
found, _ := db.CollectionExists(ctx, "mycollection")

As we can see the code above, i have three endpoints: endpoint_a, endpoint_b and endpoint_c. When the line "found, _ := db.CollectionExists(ctx, "mycollection")" is executed, it will call the function cluster.Do() in the file cluster/cluster.go, the main flow of cluster.Do() is as follows and the getNextServer() function return the server node saved by the current variable:

if s == nil {
    s = c.getCurrentServer()
}
for {
    // if success, return the resp as result

    // Failed, try next server
    attempt++
    if specificServer != nil {
        // A specific server was specified, no failover.
        return nil, driver.WithStack(err)
    }
    if attempt > len(c.servers) {
        // We've tried all servers. Giving up.
        return nil, driver.WithStack(err)
    }
    s = c.getNextServer()
}

We only use the current server node(such as endpoint_a) and use the next one until the current has failed, if the endpoint_a is running success all the time, the other endpoints is idle. So, I want to know why not random pick a server node in the c.servers, or use a load balance method for these endpoints?
I'm looking forward to your explanation for my puzzle. Thank you!

driver does not seem to decode numeric timestamps to time.Time

I have json data in arango with times stored as numeric time (milliseconds elapsed since Unix epoch)
When I use the driver to try to read data into a struct with field time.Time, I get error

2018/03/16 23:32:17 parsing time "1507900413446" as ""2006-01-02T15:04:05Z07:00"": cannot parse "1507900413446" as """

Support for raw string in query return

Assume the return of AQL query is:

[
  "325663",
  "326235",
  "325720",
  "325668",
  "326240",
  "325725"
]

My code looking like

cursor, _ := myDB.Query(nil, qry, nil)
for {
		meta, err := cursor.ReadDocument(nil, &resd)
		if driver.IsNoMoreDocuments(err) {
			break
		} else if err != nil {
			fmt.Println("for error", err)
		}
		fmt.Printf("Got doc with key '%#v' from query\n", meta)
	}

And it returns:

for error json: cannot unmarshal object into Go value of type []string

For example, JAVA driver for arango supports input a class(e.g String.class) as parameters in the query method. And that will solve this issue.

Use unique struct comment tag name

It would be beneficial and make it more inline with many other go drivers for other databases to use a unique name for the json marshal/unmarshalling. One specific use case is have a particular field used as a query field but I don't want that field saved to the database. In the example below i may have an http request with "parent_container_id" populated to have it search for a particular container, but I don't need that data saved into arangodb. So currently I am forced to build an additional struct just to omit that field.

type Container {
 ID string `json:"_key"`
}
type Item {
  *Container `json:"container"`
  ParentContainerID string `json:"parent_container_id"`
}

// something like this would be preferable
type Item {
  *Container `json:"container"`
  ParentContainerID string `arangodb:"-" json:"parent_container_id"`
}

Can't connect because of 'Unsupported content type'?

Hi,

I'm trying to use ArangoDB for a Go project, when I use the following code on go version go1.8.3 darwin/amd64 I get the error below. Help is appreciated.

conn, err := http.NewConnection(http.ConnectionConfig{
		Endpoints: []string{"http://localhost:8529"},
})
if err != nil {
	panic(err)
}

c, err := driver.NewClient(driver.ClientConfig{
	Connection: conn,
})
if err != nil {
	panic(err)
}

db, err := c.Database(nil, "mydatabase")
if err != nil {
	panic(err)
}
panic: Unsupported content type: text/plain; charset=utf-8

goroutine 1 [running]:
main.main()
	/Users/myuser/myprojectpath/myproject/main.go:29 +0x228
exit status 2

Nested structs decoding issue

I was unable to use a nested struct like below without patching http/response_json.go. I am not sure if this is the best/correct way, but it seems to work correctly.

type (
  Location struct {
    ID string `json:"_key"`
  }
  Container struct {
    *Location `json:"location,omitempty"`
  }
)

container := new(Container)
cursor.ReadDocument(nil, &container)
diff --git a/http/response_json.go b/http/response_json.go
index 9be996a..fa31296 100644
--- a/http/response_json.go
+++ b/http/response_json.go
@@ -150,9 +150,10 @@ func parseBody(bodyObject map[string]*json.RawMessage, field string, result inte
 // decodeObjectFields decodes fields from the given body into a objValue of kind struct.
 func decodeObjectFields(objValue reflect.Value, body map[string]*json.RawMessage) error {
        objValueType := objValue.Type()
+       
        for i := 0; i != objValue.NumField(); i++ {
                f := objValueType.Field(i)
-               if f.Anonymous {
+               if f.Anonymous && f.Type.Kind() == reflect.Struct {
                        // Recurse into fields of anonymous field
                        if err := decodeObjectFields(objValue.Field(i), body); err != nil {
                                return driver.WithStack(err)
@@ -174,6 +175,7 @@ func decodeObjectFields(objValue reflect.Value, body map[string]*json.RawMessage
                        }
                }
        }
+
        return nil
 }

Structs with key specified don't read the key

The following creates the book document but does not use the key. Is this intended behavior?

book := Book{
	_key:    234234343,
	Title:   "ArangoDB Cookbook",
	NoPages: 257,
}

meta, err := collection.CreateDocument(nil, book)

An example of a transactional request

Documentation does not say anything about how a transactional request would look like.

Looked into the driver implementation and found this
// Transaction performs a javascript transaction. The result of the transaction function is returned. Transaction(ctx context.Context, action string, options *TransactionOptions) (interface{}, error)
Is it just a typo Transaction performs a javascript transaction? What is action supposed to be?

I kind of get an idea of how transactions are implemented in ArangoDB in general but it would be nice to see a go-driver specific example.

correct way to get multiple documents ?

I try to do something like this:

`
query := "FOR d IN scenario RETURN d"
cursor, err := storage.ArangoDb.Query(storage.Ctx, query, nil)

if err != nil {
	fmt.Println(err)
}

var scenarios []Scenario
_, err = cursor.ReadDocument(storage.Ctx, &scenarios)

`

which of course gives following error:
cannot unmarshal object into Go value of type []model.Scenario
as cursor.ReadDocument get's only 1 document.

What is the correct way to get full array?

collection with "Wait for sync:" setting, Create-/UpdateDocument return: ArangoError: Code 0, ErrorNum 0

How to reproduce:

create a collection with "Wait for sync:" enables and try to create or update a document
meta, err = collection.CreateDocument(ctx, doc)
meta, err = collection.UpdateDocument(ctx, doc.Key, doc)

err return always an error object with "ArangoError: Code 0, ErrorNum 0". When I disable "Wait for sync:" it works as expected.

from UpdateDocument
if err := resp.CheckStatus(cs.okStatus(201, 202)); err != nil {
ArangoDB returns 201 but WaitForSync is not set so CheckStatus uses 202 for check and no opts are set, I use c := db.Collection(ctx, dbCollectionName)

ArangoDB Version: 3.3.3
mmfiles ENGINE

cursor.Count malfunction

Expected Behavior

I expect the cursor returned by db.Query() to return the number of results from cursor.Count() because the documentation states that it'll default to true, even if WithQueryCount is not set.

Actual Behavior

Meanwhile the following code...

var tm TeamMember
_, err := dbTeam.ReadDocument(ctx, "a0000000000000000000000000000002", &tm)
log.Print("READ DOCUMENT: ", err, " | ", tm)

// Query document
cursor, err := db.Query(
    ctx,
    `FOR t IN team
        FILTER t.email == @email
        RETURN t`,
    varMap{
        "email": "[email protected]",
    },
)
if err != nil {
    return nil, fmt.Errorf("Query failed: %s", err)
}
defer cursor.Close()

// Check whether found or not
cursorCount := cursor.Count()
log.Print("CURSOR COUNT: ", cursorCount)
if cursorCount < 1 {
    return nil, nil
}

produces the following logs:

READ DOCUMENT: <nil> | {{[160 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2]} {[email protected]} TestFirstName TestLastName 2018-08-06 02:01:51 +0000 UTC [sampleRole] testpassword}
2018/08/06 04:01:51 CURSOR COUNT: 0

It seems that even though cursor.ReadDocument() reads the correct document - cursor.Count() still returns 0. It'll only return 1 when a context produced by WithQueryCount(ctx, true) is excplicitly passed to the query method. This behavior doesn't match the documentation though and looks like a bug.

Environment

ArangoDB version 3.3.13
arangodb/go-driver 2b539b8
Platform Windows 10 x86_64 (Docker, arangodb:latest)

Don't use context.Context for optional parameters

Problem

You seem to be using context.Context for optional parameters such as in database.Query() which can be considered an API design flaw for the following arguments:

Official recommendations violation:

The recommendations in the official package documentation clearly state:

Use context Values only for request-scoped data that transits processes and APIs, not for passing optional parameters to functions.

And this statement makes total sense if you consider the disadvantages described below.

Performance impact

Contexts are dynamic entities and for every optional parameter passed (for every query we perform) we're creating a new context object every time we're making a query also involving dynamic type casting. This has an obvious performance impact and produces unnecessary garbage that's then to be collected by the GC. Even though those impacts are rather small - this is not how we write code in a statically typed language like Go.

Usability issues (non-reusable options)

As the documentation clearly state: contexts are request scoped and must thus not be stored.

Do not store Contexts inside a struct type; instead, pass a Context explicitly to each function that needs it.

Though we tend to reuse optional parameters (declaring them globally, or reusing them for multiple queries in a row etc.) to avoid allocating them over and over again for each request for either usability and/or performance reasons.

Readability issues

Even though this is rather a matter of taste - most would still prefer something like:

db.Query(ctx, `query`, nil, arango.QueryOptions{
	BatchID: "someid",
	QueryBatchSize: 64,
})

Rather than:

ctx = arango.WithBatchID(context.Background(), "someid")
ctx = arango.WithQueryBatchSize(ctx, 64)
arango.db.Query(ctx, `query`, nil)

Solution Proposal

Specialized methods

Keep in mind, that some optional parameters like WaitForSync could be turned into separate specialized methods such as: db.QuerySync(context.Context, string, Parameters, Options) in this case. This only applies to rather substantial options.

Optional parameters interface

Working code example: https://play.golang.org/p/u-r-Ivu6f24

If a method really needs optional parameters then consider passing them by interfaces.

Options interface:

type Options interface {
	// Clone returns an exact deep copy of the options.
	// This helps avoiding mutability problems
	// since Go doesn't yet have immutable data structures
	Clone() Options

	// Validate returns nil, if all options are correct,
	// otherwise returns an error explaining the mistake
	Validate() error
	
	// SetDefaults sets the default values for unset options if necessary
	SetDefaults()
}

Database.Query method options:

// QueryOptions implements the Options interface
type QueryOptions struct {
	BatchID        string   // len < 1 when unset, always valid
	QueryBatchSize int64    // < 1 when unset, > 4096 when invalid
	ImportDetails  []string // nil when unset, < 1 when invalid
}

// Clone implements the Options interface
func (qo *QueryOptions) Clone() Options {
	// Copy the slice since it's a reference-type
	importDetails := make([]string, len(qo.ImportDetails))
	copy(importDetails, qo.ImportDetails)

	return &QueryOptions{
		BatchID:        qo.BatchID,
		QueryBatchSize: qo.QueryBatchSize,
		ImportDetails:  importDetails,
	}
}

// Validate implements the Options interface
func (qo *QueryOptions) Validate() error {
	if qo.ImportDetails != nil && len(qo.ImportDetails) < 1 {
		return fmt.Errorf(
			"Invalid value for option ImportDetails: empty",
		)
	}
	if qo.QueryBatchSize > 4096 {
		return fmt.Errorf(
			"Invalid QueryBatchSize option (%d): too big",
			qo.QueryBatchSize,
		)
	}
	return nil
}

// SetDefaults implements the Options interface
func (qo *QueryOptions) SetDefaults() {
	// Set default query batch size if it's not set
	if qo.QueryBatchSize < 1 {
		qo.QueryBatchSize = 32
	}
}

Reading:

func (db *database) Query(
	ctx context.Context,
	query string,
	bindVars map[string]interface{},
	options Options,
) (Cursor, error) {
	switch opts := options.(type) {
	case *QueryOptions:
		if opts == nil {
			return nil, fmt.Errorf("QueryOptions is nil")
		}

		// Validate the options
		if err := options.Validate(); err != nil {
			return nil, fmt.Errorf("Invalid options: %s", err)
		}

		// Get a copy of the options to avoid unexpected mutations
		options = options.Clone()

		// Set unset fields if necessary
		options.SetDefaults()

	case nil:
		// No options set, use default options
		options = &QueryOptions{
			BatchID:        "",
			QueryBatchSize: 32,
			ImportDetails:  nil,
		}

	default:
		// Unexpected options type
		return nil, fmt.Errorf(
			"Invalid options type: %s, expected: *QueryOptions",
			reflect.TypeOf(options),
		)
	}
	queryOptions := options.(*QueryOptions)

	// Use the options
	log.Print("BatchID: ", queryOptions.BatchID)
	log.Print("QueryBatchSize: ", queryOptions.QueryBatchSize)
	log.Print("ImportDetails: ", queryOptions.ImportDetails)

	return nil, nil
}

Usage:

db.Query(ctx, `...`, nil, arango.QueryOptions{
	QueryBatchSize: 128,
	ImportDetails: []string{"detail1", "detail2"},
})

Usage: (no options)

db.Query(ctx, `...`, nil, nil)

P.S.

I'm not fully aware of the library internals thus I can't claim for sure that you're abusing context.Context though it certainly looks so from the standpoint of a library user, because contexts should really only be used for 2 things:

  • cancelation & deadlines
  • passing data through (third-party) APIs

For example a good use-case would be passing connection authentication information from your network layer through a third-party GraphQL engine to your resolver functions, that's where contexts really shine, because otherwise this would not be possible due to the third-party library not being aware of the stuff you'd like to pass through it.
For anything else than that - contexts are really bad!

I didn't have the time to look through the implementation of the Database interface but I hope that it does respect cancelable and timed contexts, otherwise there's no need in taking the context in the first place.

verbatim error messages

Make sure all errors thrown contain human readable error messages.
while i.e. connecting a non running server returns a good message: panic: Get http://localhost:8529/_db/_system/_api/database/current: dial tcp [::1]:8529: getsockopt: connection refused
failing to authentificate with the server only gives:
panic: ArangoError: Code 401, ErrorNum 0
which (if the user doesn't know that this is a HTTP-Status code) doesn't have much value.

Please check other places for good error messages too.

[ArangoDB Server 3.1.6] Unsupported content type: client.DatabaseExists when Database already exists

Hi,

I began to use your GoLang Driver for ArangoDB and I realized that I got an awkward error when using the call DatabaseExists.

When a database does not exist and we ask for its existence, the behavior is totally normal and return false, nil.

When a database does exist and we ask for its existence, we have this error : Unsupported content type: text/plain; charset=utf-8.

I'm using actually this configuration

configHTTP := http.ConnectionConfig{
Endpoints: []string{"127.0.0.1:4242"},
TLSConfig: &tls.Config{InsecureSkipVerify: true},
ContentType: driver.ContentTypeJSON}

with a BasicAuthentication

Are you familiar with this issue?

Soufien

EDIT:

I am working with an ArangoDB server in a Docker container deployed in a Microsoft Azure VM.

I realized with my local ArangoDB server (3.1.21), it works pretty fine but on the Docker Image server (3.1.6) this issue occurs. Does it make sense to you ?

Add transaction examples

The ExampleRequests page doesn't list any multi-collection transaction examples so far. I think I'd make sense to add a few at least because there are some unclear situtations like this one for example: In the official ArangoDB documentation there's only JavaScript examples in which the affected collections are declared right within the JS code:

collections: {
	write: [ "users", "logins" ],
	read: [ "recommendations" ]
}

Though in the documentation of arangodb/go-driver the Database.Transaction method takes an optional TransactionOptions object, which also provides ReadCollections and WriteCollections options, which is obviously confusing.
I assume that Database.Transaction takes the code block of the action functor from the documentation examples and thus the options must not be declared in the JS code, but the library docs don't explain this properly.

To avoid such confusion - additional transactions examples would be very useful!

Edge Save: Wrong URL

There is an error in function Collection.SaveEdge - used wrong url to store edge in database (HTTP code 404)

There is
res, err = col.db.send("edge?collection="+col.Name+"&from="+from+"&to="+to, "", "POST", doc, &doc, &doc)

But should be
res, err = col.db.send("document?collection="+col.Name, "", "POST", doc, &doc, &doc)

document should be instead edge and additional parameters from, to are redundant

DB Session

Hi,

I connect again and again for a query to the database? Or do I open a connection and leave it open for the whole GO-instance?

Handle 401 status code before checking content type.

The server will return a content type of text/plain in certain cases where the caller has no access to an object. Currently this results in an Invalid content type error.
Instead it should result in an "you have no access" error.

See also #35 & #36

Query "return null" make the panic: runtime error

Please help:

I try to run this

	var result interface{}
	query = "return null"

	ctx := context.Background()
	cursor, err := connector.ArangoDB.Query(ctx, query, nil)
	if err != nil {
		log.Println("err1",err) //no error print here
	}
	defer cursor.Close()
	meta, err := cursor.ReadDocument(ctx, &result)
        //panic: runtime error: invalid memory address or nil pointer dereference

	if(err != nil){
		log.Println("err2",err) //no error print here either
	}
	log.Println("meta",meta)

then got error panic: runtime error: invalid memory address or nil pointer dereference.

Just for testing purpose, the query i change it into return null. The real query that return null is longer, and im sure the query run well on arangodb.

Make failover with cursors more explicit

In cursor operations, the requests have to go to the server that created the initial cursor.
If that is not the case due to failover, this should be detected and returned as an explicit error.

Type dispartity in JournalSize

driver.SetCollectionPropertiesOptions has an int64 while driver.CreateCollectionOptions has a simple int. I have to explicitly cast it. From a modeling perspective one needs to be chosen over the other.

Add non-context version of method calls

I should be able to do this and submit a PR but I wanted feedback first. I don't know if it's common to provide both a non context version of methods as well as a context version.

From my point of view it is similar to the idea of "program synchronous method calls and callers can worry about adding concurrency, but making a concurrent method synchronous is not always easy and complicates the methods".

I would still keep the context versions in this case and they would delegate to the non-context versions.

Any feedback?

FYI, haven't looked at the code yet, just the README and the interface definitions.

Move to modules, support versioning

With go 1.11, modules were introduced, which helps with versioning and package distribution.

To make writing code against arangodb easier, I want to request moving go-driver to use modules and start with semantic versioning, so that users have an easier time identifying breaking changes and fixing their go-driver versions to those that work for them.

The go 1.11 release notes contain some info on modules:

https://golang.org/doc/go1.11#modules

The go wiki has more extensive info, including on versioning:

https://github.com/golang/go/wiki/Modules

Is there an ArangoDB-Object on Struct?

How do I get _key, _rev, _id and very important the lastkey (lastid) from Arango?

type mystruct struc {
   arangodb.document
  .....
or

  _key
  _lastkey
   ....
}

Add active Close method.

There is no way to close a connection and it's resulting in memory leak
my 8gb RAM is filled within 1.5 minutes

Get http://localhost:8529/_db/redbus/_api/database/current: dial tcp 127.0.0.1:8529: connect: cannot assign requested address
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x70 pc=0x4bc8be]

I am doing something like this

for _,item := range SomeArray{
ic:=getCount(item)
}

func getCount(Item) (iCount int) {

conn, err := http.NewConnection(http.ConnectionConfig{
	Endpoints: []string{"http://localhost:8529"},
})
if err != nil {
	fmt.Println("Error in Arango Connection", err)
} else {
	// fmt.Println("Arangodb connection successfull")
}
c, err := driver.NewClient(driver.ClientConfig{
	Connection:     conn,
	Authentication: driver.BasicAuthentication("test", "test"),
})

if err != nil {
	fmt.Println("Error in Arango Client Creation", err)
} else {
	fmt.Println("Arangodb client creation successful")
}
db, err := c.Database(nil, "redbus")
if err != nil {
	fmt.Println("Error in database selection : ", err)
} else {
	fmt.Println("Selected Dataase ")
}
ctx := driver.WithQueryCount(context.Background())
query := `<some query to return count>`
cursor, err := db.Query(ctx, query, nil)
if err != nil {
	fmt.Println("ERROIR IN INTERLINKING COUNT CURSOR : ", err)
}
var interlinkingCount int
for {

	_, err := cursor.ReadDocument(ctx, &interlinkingCount)
	if driver.IsNoMoreDocuments(err) {
		fmt.Println("NO MORE INTERLINKIGN COUUNT DOCUMENTS : ", err)
		break
	} else if err != nil {
		fmt.Println(" Error in record interlinking count  : ", err)
	}

} /*for*/
defer cursor.Close()

return interlinkingCount

}

There is no way to close the connection object in this function resulting in leak

Possible concurrency issues, using VST connection

Reproduce using this code:

package main

import (
	"context"
	"crypto/tls"
	"fmt"
	"log"
	"sync"

	"github.com/arangodb/go-driver"
	"github.com/arangodb/go-driver/vst"
	"github.com/arangodb/go-driver/vst/protocol"
)

func createConnection() driver.Connection {
	endpoint := "vst://127.0.0.1:8529"
	config := vst.ConnectionConfig{
		Endpoints: []string{endpoint},
		Transport: protocol.TransportConfig{
			IdleConnTimeout: 0,
			Version:         protocol.Version1_1,
		},
	}

	conn, err := vst.NewConnection(config)

	if err != nil {
		panic(err)
	}

	return conn
}

func createClient() driver.Client {
	conn := createConnection()
	c, err := driver.NewClient(driver.ClientConfig{
		Connection: conn,
	})

	if err != nil {
		panic(err)
	}

	v, err := c.Version(nil)
	if err != nil {
		panic(err)
	}
	fmt.Printf("Version: %v\n", v)

	return c
}

func listAll(c driver.Client, page int) {
	ctx := context.Background()

	db, err := c.Database(ctx, "_system")
	if err != nil {
		panic(err)
	}

	limit := 100
	aql := fmt.Sprintf("FOR v IN Foo LIMIT %d, %d RETURN v", page, limit)
	ctx = driver.WithQueryBatchSize(ctx, 100)
	listAllCursor, err := db.Query(ctx, aql, nil)
	if err != nil {
		panic(err)
	}
	defer listAllCursor.Close()

	for listAllCursor.HasMore() {
		var ent map[string]interface{}
		if _, err := listAllCursor.ReadDocument(ctx, &ent); err != nil {
			panic(err)
		}
	}
}

func main() {
	c := createClient()

	wg := sync.WaitGroup{}
	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go func(i int) {
			defer wg.Done()

			for x := 0; x < 5; x++ {
				log.Printf("i=%d, x=%d\n", i, x)
				listAll(c, i)
			}
		}(i)
	}
	wg.Wait()
}

To run, initialise a Foo collection in _system database with this query:

FOR i IN 0..100000 
INSERT { x:i, foo1:"hello",foo2:"bar", foo3:"void", foo4:true} INTO Foo

It has been seen to fail with these errors:

Nil-reference:

context.(*timerCtx).Value(0xc420625da0, 0x699680, 0x73ae90, 0xc4293a9740, 0x2)

        <autogenerated>:1 +0x32

github.com/arangodb/go-driver/vst.(*vstConnection).do(0xc420114000, 0x85fcc0, 0xc420625da0, 0x861480, 0xc420a74870, 0x85bb40, 0xc42008c370, 0x893c20, 0x85fcc0, 0xc420625da0, ...)

        /src/github.com/arangodb/go-driver/vst/connection.go:170 +0x48c

github.com/arangodb/go-driver/vst.(*vstConnection).Do(0xc420114000, 0x85fcc0, 0xc420625da0, 0x861480, 0xc420a74870, 0xc4236424f0, 0x0, 0xc420472300, 0x718228)

        /src/github.com/arangodb/go-driver/vst/connection.go:136 +0x75

github.com/arangodb/go-driver/cluster.(*clusterConnection).Do(0xc420110000, 0x85fd00, 0xc4293a96b0, 0x861480, 0xc420a74870, 0xc420a74870, 0x0, 0x0, 0x0)

        /src/github.com/arangodb/go-driver/cluster/cluster.go:142 +0x249

github.com/arangodb/go-driver.(*database).Query(0xc422b8ec20, 0x85fd00, 0xc4293a96b0, 0xc42c206510, 0x25, 0x0, 0x25, 0x0, 0x0, 0xb)

        /src/github.com/arangodb/go-driver/database_impl.go:121 +0x295

Send on closed channel

github.com/arangodb/go-driver/vst/protocol.(*Connection).processChunk(0xc4200fe070, 0x6, 0x437, 0x20470, 0xc4277b8000, 0x77f0, 0x77f0)

        /src/github.com/arangodb/go-driver/vst/protocol/connection.go:256 +0x1a6

created by github.com/arangodb/go-driver/vst/protocol.(*Connection).readChunkLoop

        /src/github.com/arangodb/go-driver/vst/protocol/connection.go:229 +0x29d

Errors we're caught on linux with ArangoDB 3.2.9

Error when connecting to ArangoDB with SSL enabled

ArangoDB : 3.3.3
go-driver : 27122ab
Platform : Mac OSX 10.13.3 / Debian 8.10 Jessie x86_64

I have a simple arangodb cluster started with arangodb-starter (docker).

I have enabled the --auth.jwt-secret option and the --ssl.auto-key option.

All is working fine via the WebUI but via the go-driver I encounter some problems (I am using the VST protocol):
Client and Connection seems to be valid no error returned but when I use the Database function of the client it throw these errors :

With Basic authentication

panic: expected type 'Array', got 'None'
readChunkLoop error: &net.OpError{Op:"read", Net:"tcp", Source:(*net.TCPAddr)(0xc420190060), Addr:(*net.TCPAddr)(0xc420190090), Err:(*os.SyscallError)(0xc4201ae000)}

With JWT authentication

readChunkLoop error: &net.OpError{Op:"read", Net:"tcp", Source:(*net.TCPAddr)(0xc420011d10), Addr:(*net.TCPAddr)(0xc420011d40), Err:(*os.SyscallError)(0xc42014c000)}
readChunkLoop error: &net.OpError{Op:"read", Net:"tcp", Source:(*net.TCPAddr)(0xc420160060), Addr:(*net.TCPAddr)(0xc420160090), Err:(*os.SyscallError)(0xc420176000)}
readChunkLoop error: &net.OpError{Op:"read", Net:"tcp", Source:(*net.TCPAddr)(0xc420194060), Addr:(*net.TCPAddr)(0xc420194090), Err:(*os.SyscallError)(0xc4200e1260)}
panic: context deadline exceeded

If I disable the ssl option all is working fine

Cursor returned from a query with empty Count

AQL query
FOR c IN commit FILTER c.company_id == @company_id AND c.date >= DATE_TIMESTAMP(@start_date) AND c.date <= DATE_TIMESTAMP(@end_date) LET files = LENGTH(c.files) > 0 ? c.files : [] FOR f IN files COLLECT extensions = f.type RETURN extensions

Details
Query returns a non-empty result. However, the Cursor returned from a query has Count = 0
See the screenshot enclosed.

screen shot 2018-01-04 at 11 47 12 am

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.