1
0
Fork 0
mirror of https://github.com/arangodb/kube-arangodb.git synced 2024-12-14 11:57:37 +00:00

Updated Go-Driver to latest version.

This commit is contained in:
lamai93 2018-12-04 16:57:18 +01:00
parent 06d4af4fe8
commit c4623b5258
40 changed files with 1886 additions and 212 deletions

File diff suppressed because one or more lines are too long

View file

@ -1,8 +0,0 @@
export GOBUILDDIR=$(pwd)/.gobuild
export GOPATH=$GOBUILDDIR:$GOPATH
PATH_add $GOBUILDDIR/bin
if [ ! -e ${GOBUILDDIR} ]; then
mkdir -p ${GOBUILDDIR}/src/github.com/arangodb/
ln -s ../../../.. ${GOBUILDDIR}/src/github.com/arangodb/go-driver
fi

View file

@ -1 +0,0 @@
.gobuild

View file

@ -1,14 +0,0 @@
sudo: required
services:
- docker
language: go
env:
- TEST_SUITE=run-tests-http
- TEST_SUITE=run-tests-single ARANGODB=arangodb:3.2
- TEST_SUITE=run-tests-single ARANGODB=arangodb/arangodb:latest
- TEST_SUITE=run-tests-single ARANGODB=arangodb/arangodb-preview:latest
script: make $TEST_SUITE

View file

@ -1,37 +0,0 @@
// Place your settings in this file to overwrite default and user settings.
{
"fileHeaderComment.parameter":{
"*":{
"commentprefix": "//",
"company": "ArangoDB GmbH, Cologne, Germany",
"author": "Ewout Prangsma"
}
},
"fileHeaderComment.template":{
"*":[
"${commentprefix} ",
"${commentprefix} DISCLAIMER",
"${commentprefix} ",
"${commentprefix} Copyright ${year} ArangoDB GmbH, Cologne, Germany",
"${commentprefix} ",
"${commentprefix} Licensed under the Apache License, Version 2.0 (the \"License\");",
"${commentprefix} you may not use this file except in compliance with the License.",
"${commentprefix} You may obtain a copy of the License at",
"${commentprefix} ",
"${commentprefix} http://www.apache.org/licenses/LICENSE-2.0",
"${commentprefix} ",
"${commentprefix} Unless required by applicable law or agreed to in writing, software",
"${commentprefix} distributed under the License is distributed on an \"AS IS\" BASIS,",
"${commentprefix} WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.",
"${commentprefix} See the License for the specific language governing permissions and",
"${commentprefix} limitations under the License.",
"${commentprefix} ",
"${commentprefix} Copyright holder is ArangoDB GmbH, Cologne, Germany",
"${commentprefix} ",
"${commentprefix} Author ${author}",
"${commentprefix} ",
""
]
},
"go.gopath": "${workspaceRoot}/.gobuild"
}

View file

@ -40,6 +40,17 @@ type Client interface {
// This function requires ArangoDB 3.1.15 or up.
SynchronizeEndpoints(ctx context.Context) error
// SynchronizeEndpoints2 fetches all endpoints from an ArangoDB cluster and updates the
// connection to use those endpoints.
// When this client is connected to a single server, nothing happens.
// When this client is connected to a cluster of servers, the connection will be updated to reflect
// the layout of the cluster.
// Compared to SynchronizeEndpoints, this function expects a database name as additional parameter.
// This database name is used to call `_db/<dbname>/_api/cluster/endpoints`. SynchronizeEndpoints uses
// the default database, i.e. `_system`. In the case the user does not have access to `_system`,
// SynchronizeEndpoints does not work with earlier versions of arangodb.
SynchronizeEndpoints2(ctx context.Context, dbname string) error
// Connection returns the connection used by this client
Connection() Connection

View file

@ -24,6 +24,7 @@ package driver
import (
"context"
"path"
"time"
"github.com/arangodb/go-driver/util"
@ -67,19 +68,28 @@ func (c *client) Connection() Connection {
// When this client is connected to a cluster of servers, the connection will be updated to reflect
// the layout of the cluster.
func (c *client) SynchronizeEndpoints(ctx context.Context) error {
role, err := c.ServerRole(ctx)
if err != nil {
return WithStack(err)
}
if role == ServerRoleSingle {
// Standalone server, do nothing
return nil
}
return c.SynchronizeEndpoints2(ctx, "")
}
// SynchronizeEndpoints2 fetches all endpoints from an ArangoDB cluster and updates the
// connection to use those endpoints.
// When this client is connected to a single server, nothing happens.
// When this client is connected to a cluster of servers, the connection will be updated to reflect
// the layout of the cluster.
// Compared to SynchronizeEndpoints, this function expects a database name as additional parameter.
// This database name is used to call `_db/<dbname>/_api/cluster/endpoints`. SynchronizeEndpoints uses
// the default database, i.e. `_system`. In the case the user does not have access to `_system`,
// SynchronizeEndpoints does not work with earlier versions of arangodb.
func (c *client) SynchronizeEndpoints2(ctx context.Context, dbname string) error {
// Cluster mode, fetch endpoints
cep, err := c.clusterEndpoints(ctx)
cep, err := c.clusterEndpoints(ctx, dbname)
if err != nil {
return WithStack(err)
// ignore Forbidden: automatic failover is not enabled errors
if !IsArangoErrorWithErrorNum(err, 403, 0, 11) { // 3.2 returns no error code, thus check for 0
return WithStack(err)
}
return nil
}
var endpoints []string
for _, ep := range cep.Endpoints {
@ -114,8 +124,14 @@ type clusterEndpoint struct {
}
// clusterEndpoints returns the endpoints of a cluster.
func (c *client) clusterEndpoints(ctx context.Context) (clusterEndpointsResponse, error) {
req, err := c.conn.NewRequest("GET", "_api/cluster/endpoints")
func (c *client) clusterEndpoints(ctx context.Context, dbname string) (clusterEndpointsResponse, error) {
var url string
if dbname == "" {
url = "_api/cluster/endpoints"
} else {
url = path.Join("_db", pathEscape(dbname), "_api/cluster/endpoints")
}
req, err := c.conn.NewRequest("GET", url)
if err != nil {
return clusterEndpointsResponse{}, WithStack(err)
}

View file

@ -75,6 +75,8 @@ type ServerHealth struct {
Status ServerStatus `json:"Status"`
CanBeDeleted bool `json:"CanBeDeleted"`
HostID string `json:"Host,omitempty"`
Version Version `json:"Version,omitempty"`
Engine EngineType `json:"Engine,omitempty"`
}
// ServerStatus describes the health status of a server
@ -93,6 +95,8 @@ const (
type DatabaseInventory struct {
// Details of all collections
Collections []InventoryCollection `json:"collections,omitempty"`
// Details of all views
Views []InventoryView `json:"views,omitempty"`
}
// IsReady returns true if the IsReady flag of all collections is set.
@ -124,6 +128,17 @@ func (i DatabaseInventory) CollectionByName(name string) (InventoryCollection, b
return InventoryCollection{}, false
}
// ViewByName returns the InventoryView with given name.
// Return false if not found.
func (i DatabaseInventory) ViewByName(name string) (InventoryView, bool) {
for _, v := range i.Views {
if v.Name == name {
return v, true
}
}
return InventoryView{}, false
}
// InventoryCollection is a single element of a DatabaseInventory, containing all information
// of a specific collection.
type InventoryCollection struct {
@ -196,6 +211,19 @@ func (i InventoryIndex) FieldsEqual(fields []string) bool {
return stringSliceEqualsIgnoreOrder(i.Fields, fields)
}
// InventoryView is a single element of a DatabaseInventory, containing all information
// of a specific view.
type InventoryView struct {
Name string `json:"name,omitempty"`
Deleted bool `json:"deleted,omitempty"`
ID string `json:"id,omitempty"`
IsSystem bool `json:"isSystem,omitempty"`
PlanID string `json:"planId,omitempty"`
Type ViewType `json:"type,omitempty"`
// Include all properties from an arangosearch view.
ArangoSearchViewProperties
}
// stringSliceEqualsIgnoreOrder returns true when the given lists contain the same elements.
// The order of elements is irrelevant.
func stringSliceEqualsIgnoreOrder(a, b []string) bool {

View file

@ -59,6 +59,9 @@ func (c *collection) ReadDocument(ctx context.Context, key string, result interf
if err != nil {
return DocumentMeta{}, WithStack(err)
}
// This line introduces a lot of side effects. In particular If-Match headers are now set (which is a bugfix)
// and invalid query parameters like waitForSync (which is potentially breaking change)
cs := applyContextSettings(ctx, req)
resp, err := c.conn.Do(ctx, req)
if err != nil {
return DocumentMeta{}, WithStack(err)
@ -71,6 +74,8 @@ func (c *collection) ReadDocument(ctx context.Context, key string, result interf
if err := resp.ParseBody("", &meta); err != nil {
return DocumentMeta{}, WithStack(err)
}
// load context response values
loadContextResponseValues(cs, resp)
// Parse result
if result != nil {
if err := resp.ParseBody("", result); err != nil {
@ -120,6 +125,8 @@ func (c *collection) ReadDocuments(ctx context.Context, keys []string, results i
if err := resp.CheckStatus(200); err != nil {
return nil, nil, WithStack(err)
}
// load context response values
loadContextResponseValues(cs, resp)
// Parse response array
metas, errs, err := parseResponseArray(resp, resultCount, cs, results)
if err != nil {

View file

@ -38,8 +38,13 @@ type indexData struct {
MinLength int `json:"minLength,omitempty"`
}
type genericIndexData struct {
ID string `json:"id,omitempty"`
Type string `json:"type"`
}
type indexListResponse struct {
Indexes []indexData `json:"indexes,omitempty"`
Indexes []genericIndexData `json:"indexes,omitempty"`
}
// Index opens a connection to an existing index within the collection.
@ -60,7 +65,7 @@ func (c *collection) Index(ctx context.Context, name string) (Index, error) {
if err := resp.ParseBody("", &data); err != nil {
return nil, WithStack(err)
}
idx, err := newIndex(data.ID, c)
idx, err := newIndex(data.ID, data.Type, c)
if err != nil {
return nil, WithStack(err)
}
@ -106,7 +111,7 @@ func (c *collection) Indexes(ctx context.Context) ([]Index, error) {
}
result := make([]Index, 0, len(data.Indexes))
for _, x := range data.Indexes {
idx, err := newIndex(x.ID, c)
idx, err := newIndex(x.ID, x.Type, c)
if err != nil {
return nil, WithStack(err)
}
@ -121,7 +126,7 @@ func (c *collection) Indexes(ctx context.Context) ([]Index, error) {
// The index is returned, together with a boolean indicating if the index was newly created (true) or pre-existing (false).
func (c *collection) EnsureFullTextIndex(ctx context.Context, fields []string, options *EnsureFullTextIndexOptions) (Index, bool, error) {
input := indexData{
Type: "fulltext",
Type: string(FullTextIndex),
Fields: fields,
}
if options != nil {
@ -146,7 +151,7 @@ func (c *collection) EnsureFullTextIndex(ctx context.Context, fields []string, o
// The index is returned, together with a boolean indicating if the index was newly created (true) or pre-existing (false).
func (c *collection) EnsureGeoIndex(ctx context.Context, fields []string, options *EnsureGeoIndexOptions) (Index, bool, error) {
input := indexData{
Type: "geo",
Type: string(GeoIndex),
Fields: fields,
}
if options != nil {
@ -164,7 +169,7 @@ func (c *collection) EnsureGeoIndex(ctx context.Context, fields []string, option
// The index is returned, together with a boolean indicating if the index was newly created (true) or pre-existing (false).
func (c *collection) EnsureHashIndex(ctx context.Context, fields []string, options *EnsureHashIndexOptions) (Index, bool, error) {
input := indexData{
Type: "hash",
Type: string(HashIndex),
Fields: fields,
}
off := false
@ -187,7 +192,7 @@ func (c *collection) EnsureHashIndex(ctx context.Context, fields []string, optio
// The index is returned, together with a boolean indicating if the index was newly created (true) or pre-existing (false).
func (c *collection) EnsurePersistentIndex(ctx context.Context, fields []string, options *EnsurePersistentIndexOptions) (Index, bool, error) {
input := indexData{
Type: "persistent",
Type: string(PersistentIndex),
Fields: fields,
}
if options != nil {
@ -206,7 +211,7 @@ func (c *collection) EnsurePersistentIndex(ctx context.Context, fields []string,
// The index is returned, together with a boolean indicating if the index was newly created (true) or pre-existing (false).
func (c *collection) EnsureSkipListIndex(ctx context.Context, fields []string, options *EnsureSkipListIndexOptions) (Index, bool, error) {
input := indexData{
Type: "skiplist",
Type: string(SkipListIndex),
Fields: fields,
}
off := false
@ -248,7 +253,7 @@ func (c *collection) ensureIndex(ctx context.Context, options indexData) (Index,
if err := resp.ParseBody("", &data); err != nil {
return nil, false, WithStack(err)
}
idx, err := newIndex(data.ID, c)
idx, err := newIndex(data.ID, data.Type, c)
if err != nil {
return nil, false, WithStack(err)
}

View file

@ -79,6 +79,10 @@ type Request interface {
Written() bool
// Clone creates a new request containing the same data as this request
Clone() Request
// Path returns the Request path
Path() string
// Method returns the Request method
Method() string
}
// Response represents the response from the server on a given request.

View file

@ -57,6 +57,7 @@ const (
keyDBServerID ContextKey = "arangodb-dbserverID"
keyBatchID ContextKey = "arangodb-batchID"
keyJobIDResponse ContextKey = "arangodb-jobIDResponse"
keyAllowDirtyReads ContextKey = "arangodb-allowDirtyReads"
)
// WithRevision is used to configure a context to make document
@ -137,6 +138,14 @@ func WithWaitForSync(parent context.Context, value ...bool) context.Context {
return context.WithValue(contextOrBackground(parent), keyWaitForSync, v)
}
// WithAllowDirtyReads is used in an active failover deployment to allow reads from the follower.
// You can pass a reference to a boolean that will set according to wether a potentially dirty read
// happened or not. nil is allowed.
// This is valid for document reads, aql queries, gharial vertex and edge reads.
func WithAllowDirtyReads(parent context.Context, wasDirtyRead *bool) context.Context {
return context.WithValue(contextOrBackground(parent), keyAllowDirtyReads, wasDirtyRead)
}
// WithRawResponse is used to configure a context that will make all functions store the raw response into a
// buffer.
func WithRawResponse(parent context.Context, value *[]byte) context.Context {
@ -231,6 +240,8 @@ type contextSettings struct {
ImportDetails *[]string
IsRestore bool
IsSystem bool
AllowDirtyReads bool
DirtyReadFlag *bool
IgnoreRevs *bool
EnforceReplicationFactor *bool
Configured *bool
@ -240,6 +251,29 @@ type contextSettings struct {
JobIDResponse *string
}
// loadContextResponseValue loads generic values from the response and puts it into variables specified
// via context values.
func loadContextResponseValues(cs contextSettings, resp Response) {
// Parse potential dirty read
if cs.DirtyReadFlag != nil {
if dirtyRead := resp.Header("X-Arango-Potential-Dirty-Read"); dirtyRead != "" {
*cs.DirtyReadFlag = true // The documentation does not say anything about the actual value (dirtyRead == "true")
} else {
*cs.DirtyReadFlag = false
}
}
}
// setDirtyReadFlagIfRequired is a helper function that sets the bool reference for allowDirtyReads to the
// specified value, if required and reference is not nil.
func setDirtyReadFlagIfRequired(ctx context.Context, wasDirty bool) {
if v := ctx.Value(keyAllowDirtyReads); v != nil {
if ref, ok := v.(*bool); ok && ref != nil {
*ref = wasDirty
}
}
}
// applyContextSettings returns the settings configured in the context in the given request.
// It then returns information about the applied settings that may be needed later in API implementation functions.
func applyContextSettings(ctx context.Context, req Request) contextSettings {
@ -279,6 +313,14 @@ func applyContextSettings(ctx context.Context, req Request) contextSettings {
result.WaitForSync = waitForSync
}
}
// AllowDirtyReads
if v := ctx.Value(keyAllowDirtyReads); v != nil {
req.SetHeader("x-arango-allow-dirty-read", "true")
result.AllowDirtyReads = true
if dirtyReadFlag, ok := v.(*bool); ok {
result.DirtyReadFlag = dirtyReadFlag
}
}
// ReturnOld
if v := ctx.Value(keyReturnOld); v != nil {
req.SetQuery("returnOld", "true")

View file

@ -33,26 +33,29 @@ import (
)
// newCursor creates a new Cursor implementation.
func newCursor(data cursorData, endpoint string, db *database) (Cursor, error) {
func newCursor(data cursorData, endpoint string, db *database, allowDirtyReads bool) (Cursor, error) {
if db == nil {
return nil, WithStack(InvalidArgumentError{Message: "db is nil"})
}
return &cursor{
cursorData: data,
endpoint: endpoint,
db: db,
conn: db.conn,
cursorData: data,
endpoint: endpoint,
db: db,
conn: db.conn,
allowDirtyReads: allowDirtyReads,
}, nil
}
type cursor struct {
cursorData
endpoint string
resultIndex int
db *database
conn Connection
closed int32
closeMutex sync.Mutex
endpoint string
resultIndex int
db *database
conn Connection
closed int32
closeMutex sync.Mutex
allowDirtyReads bool
lastReadWasDirty bool
}
type cursorStats struct {
@ -140,24 +143,40 @@ func (c *cursor) ReadDocument(ctx context.Context, result interface{}) (Document
ctx = WithEndpoint(ctx, c.endpoint)
if c.resultIndex >= len(c.Result) && c.cursorData.HasMore {
// This is required since we are interested if this was a dirty read
// but we do not want to trash the users bool reference.
var wasDirtyRead bool
fetchctx := ctx
if c.allowDirtyReads {
fetchctx = WithAllowDirtyReads(ctx, &wasDirtyRead)
}
// Fetch next batch
req, err := c.conn.NewRequest("PUT", path.Join(c.relPath(), c.cursorData.ID))
if err != nil {
return DocumentMeta{}, WithStack(err)
}
resp, err := c.conn.Do(ctx, req)
cs := applyContextSettings(fetchctx, req)
resp, err := c.conn.Do(fetchctx, req)
if err != nil {
return DocumentMeta{}, WithStack(err)
}
if err := resp.CheckStatus(200); err != nil {
return DocumentMeta{}, WithStack(err)
}
loadContextResponseValues(cs, resp)
var data cursorData
if err := resp.ParseBody("", &data); err != nil {
return DocumentMeta{}, WithStack(err)
}
c.cursorData = data
c.resultIndex = 0
c.lastReadWasDirty = wasDirtyRead
}
// ReadDocument should act as if it would actually do a read
// hence update the bool reference
if c.allowDirtyReads {
setDirtyReadFlagIfRequired(ctx, c.lastReadWasDirty)
}
index := c.resultIndex

View file

@ -46,6 +46,9 @@ type Database interface {
// Collection functions
DatabaseCollections
// View functions
DatabaseViews
// Graph functions
DatabaseGraphs

View file

@ -139,6 +139,7 @@ func (d *database) Query(ctx context.Context, query string, bindVars map[string]
if _, err := req.SetBody(input); err != nil {
return nil, WithStack(err)
}
cs := applyContextSettings(ctx, req)
resp, err := d.conn.Do(ctx, req)
if err != nil {
return nil, WithStack(err)
@ -150,7 +151,7 @@ func (d *database) Query(ctx context.Context, query string, bindVars map[string]
if err := resp.ParseBody("", &data); err != nil {
return nil, WithStack(err)
}
col, err := newCursor(data, resp.Endpoint(), d)
col, err := newCursor(data, resp.Endpoint(), d, cs.AllowDirtyReads)
if err != nil {
return nil, WithStack(err)
}

View file

@ -0,0 +1,52 @@
//
// DISCLAIMER
//
// Copyright 2018 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
// Author Ewout Prangsma
//
package driver
import "context"
// DatabaseViews provides access to all views in a single database.
// Views are only available in ArangoDB 3.4 and higher.
type DatabaseViews interface {
// View opens a connection to an existing view within the database.
// If no collection with given name exists, an NotFoundError is returned.
View(ctx context.Context, name string) (View, error)
// ViewExists returns true if a view with given name exists within the database.
ViewExists(ctx context.Context, name string) (bool, error)
// Views returns a list of all views in the database.
Views(ctx context.Context) ([]View, error)
// CreateArangoSearchView creates a new view of type ArangoSearch,
// with given name and options, and opens a connection to it.
// If a view with given name already exists within the database, a ConflictError is returned.
CreateArangoSearchView(ctx context.Context, name string, options *ArangoSearchViewProperties) (ArangoSearchView, error)
}
// ViewType is the type of a view.
type ViewType string
const (
// ViewTypeArangoSearch specifies an ArangoSearch view type.
ViewTypeArangoSearch = ViewType("arangosearch")
)

View file

@ -0,0 +1,157 @@
//
// DISCLAIMER
//
// Copyright 2018 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
// Author Ewout Prangsma
//
package driver
import (
"context"
"path"
)
type viewInfo struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Type ViewType `json:"type,omitempty"`
}
type getViewResponse struct {
Result []viewInfo `json:"result,omitempty"`
}
// View opens a connection to an existing view within the database.
// If no collection with given name exists, an NotFoundError is returned.
func (d *database) View(ctx context.Context, name string) (View, error) {
escapedName := pathEscape(name)
req, err := d.conn.NewRequest("GET", path.Join(d.relPath(), "_api/view", escapedName))
if err != nil {
return nil, WithStack(err)
}
applyContextSettings(ctx, req)
resp, err := d.conn.Do(ctx, req)
if err != nil {
return nil, WithStack(err)
}
if err := resp.CheckStatus(200); err != nil {
return nil, WithStack(err)
}
var data viewInfo
if err := resp.ParseBody("", &data); err != nil {
return nil, WithStack(err)
}
view, err := newView(name, data.Type, d)
if err != nil {
return nil, WithStack(err)
}
return view, nil
}
// ViewExists returns true if a view with given name exists within the database.
func (d *database) ViewExists(ctx context.Context, name string) (bool, error) {
escapedName := pathEscape(name)
req, err := d.conn.NewRequest("GET", path.Join(d.relPath(), "_api/view", escapedName))
if err != nil {
return false, WithStack(err)
}
applyContextSettings(ctx, req)
resp, err := d.conn.Do(ctx, req)
if err != nil {
return false, WithStack(err)
}
if err := resp.CheckStatus(200); err == nil {
return true, nil
} else if IsNotFound(err) {
return false, nil
} else {
return false, WithStack(err)
}
}
// Views returns a list of all views in the database.
func (d *database) Views(ctx context.Context) ([]View, error) {
req, err := d.conn.NewRequest("GET", path.Join(d.relPath(), "_api/view"))
if err != nil {
return nil, WithStack(err)
}
applyContextSettings(ctx, req)
resp, err := d.conn.Do(ctx, req)
if err != nil {
return nil, WithStack(err)
}
if err := resp.CheckStatus(200); err != nil {
return nil, WithStack(err)
}
var data getViewResponse
if err := resp.ParseBody("", &data); err != nil {
return nil, WithStack(err)
}
result := make([]View, 0, len(data.Result))
for _, info := range data.Result {
view, err := newView(info.Name, info.Type, d)
if err != nil {
return nil, WithStack(err)
}
result = append(result, view)
}
return result, nil
}
// CreateArangoSearchView creates a new view of type ArangoSearch,
// with given name and options, and opens a connection to it.
// If a view with given name already exists within the database, a ConflictError is returned.
func (d *database) CreateArangoSearchView(ctx context.Context, name string, options *ArangoSearchViewProperties) (ArangoSearchView, error) {
input := struct {
Name string `json:"name"`
Type ViewType `json:"type"`
ArangoSearchViewProperties // `json:"properties"`
}{
Name: name,
Type: ViewTypeArangoSearch,
}
if options != nil {
input.ArangoSearchViewProperties = *options
}
req, err := d.conn.NewRequest("POST", path.Join(d.relPath(), "_api/view"))
if err != nil {
return nil, WithStack(err)
}
if _, err := req.SetBody(input); err != nil {
return nil, WithStack(err)
}
applyContextSettings(ctx, req)
resp, err := d.conn.Do(ctx, req)
if err != nil {
return nil, WithStack(err)
}
if err := resp.CheckStatus(201); err != nil {
return nil, WithStack(err)
}
view, err := newView(name, input.Type, d)
if err != nil {
return nil, WithStack(err)
}
result, err := view.ArangoSearchView()
if err != nil {
return nil, WithStack(err)
}
return result, nil
}

View file

@ -67,6 +67,9 @@ func (c *edgeCollection) readDocument(ctx context.Context, key string, result in
if err := resp.CheckStatus(200); err != nil {
return DocumentMeta{}, contextSettings{}, WithStack(err)
}
// Concerns: ReadDocuments reads multiple documents via multiple calls to readDocument (this function).
// Currently with AllowDirtyReads the wasDirtyFlag is only set according to the last read request.
loadContextResponseValues(cs, resp)
// Parse metadata
var meta DocumentMeta
if err := resp.ParseBody("edge", &meta); err != nil {

View file

@ -128,6 +128,11 @@ func IsNoLeader(err error) bool {
return IsArangoErrorWithCode(err, 503) && IsArangoErrorWithErrorNum(err, 1496)
}
// IsNoLeaderOrOngoing return true if the given error is an ArangoError with code 503 and error number 1496 or 1495
func IsNoLeaderOrOngoing(err error) bool {
return IsArangoErrorWithCode(err, 503) && (IsArangoErrorWithErrorNum(err, 1495) || IsArangoErrorWithErrorNum(err, 1496))
}
// InvalidArgumentError is returned when a go function argument is invalid.
type InvalidArgumentError struct {
Message string

View file

@ -0,0 +1,120 @@
//
// DISCLAIMER
//
// Copyright 2017 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
// +build !auth
// This example demonstrates how to create a graph, how to add vertices and edges and how to delete it again.
package driver_test
import (
"log"
"fmt"
driver "github.com/arangodb/go-driver"
"github.com/arangodb/go-driver/http"
)
type MyObject struct {
Name string `json:"_key"`
Age int `json:"age"`
}
type MyEdgeObject struct {
From string `json:"_from"`
To string `json:"_to"`
}
func Example_createGraph() {
fmt.Println("Hello World")
// 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,
})
// Create database
db, err := c.CreateDatabase(nil, "my_graph_db", nil)
if err != nil {
log.Fatalf("Failed to create database: %v", err)
}
// define the edgeCollection to store the edges
var edgeDefinition driver.EdgeDefinition
edgeDefinition.Collection = "myEdgeCollection"
// define a set of collections where an edge is going out...
edgeDefinition.From = []string{"myCollection1", "myCollection2"}
// repeat this for the collections where an edge is going into
edgeDefinition.To = []string{"myCollection1", "myCollection3"}
// A graph can contain additional vertex collections, defined in the set of orphan collections
var options driver.CreateGraphOptions
options.OrphanVertexCollections = []string{"myCollection4", "myCollection5"}
options.EdgeDefinitions = []driver.EdgeDefinition{edgeDefinition}
// now it's possible to create a graph
graph, err := db.CreateGraph(nil, "myGraph", &options)
if err != nil {
log.Fatalf("Failed to create graph: %v", err)
}
// add vertex
vertexCollection1, err := graph.VertexCollection(nil, "myCollection1")
if err != nil {
log.Fatalf("Failed to get vertex collection: %v", err)
}
myObjects := []MyObject{
MyObject{
"Homer",
38,
},
MyObject{
"Marge",
36,
},
}
_, _, err = vertexCollection1.CreateDocuments(nil, myObjects)
if err != nil {
log.Fatalf("Failed to create vertex documents: %v", err)
}
// add edge
edgeCollection, _, err := graph.EdgeCollection(nil, "myEdgeCollection")
if err != nil {
log.Fatalf("Failed to select edge collection: %v", err)
}
edge := MyEdgeObject{From: "myCollection1/Homer", To: "myCollection1/Marge"}
_, err = edgeCollection.CreateDocument(nil, edge)
if err != nil {
log.Fatalf("Failed to create edge document: %v", err)
}
// delete graph
graph.Remove(nil)
}

View file

@ -0,0 +1,7 @@
module github.com/arangodb/go-driver
require (
github.com/arangodb/go-velocypack v0.0.0-20180928134037-d177e3455691
github.com/coreos/go-iptables v0.4.0
github.com/dgrijalva/jwt-go v3.2.0+incompatible
)

View file

@ -0,0 +1,12 @@
github.com/arangodb/go-velocypack v0.0.0-20180928134037-d177e3455691 h1:62SGGAvrKTXOrewra74du0H98Yg/eufQ/tO3RoIP8Bs=
github.com/arangodb/go-velocypack v0.0.0-20180928134037-d177e3455691/go.mod h1:7QCjpWXdB49P6fql1CxmsBWd8z/T4L4pqFLTnc10xNM=
github.com/coreos/go-iptables v0.4.0 h1:wh4UbVs8DhLUbpyq97GLJDKrQMjEDD63T1xE4CrsKzQ=
github.com/coreos/go-iptables v0.4.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=

View file

@ -47,6 +47,16 @@ type httpJSONRequest struct {
written bool
}
// Path returns the Request path
func (r *httpJSONRequest) Path() string {
return r.path
}
// Method returns the Request method
func (r *httpJSONRequest) Method() string {
return r.method
}
// Clone creates a new request containing the same data as this request
func (r *httpJSONRequest) Clone() driver.Request {
clone := *r

View file

@ -47,6 +47,16 @@ type httpVPackRequest struct {
written bool
}
// Path returns the Request path
func (r *httpVPackRequest) Path() string {
return r.path
}
// Method returns the Request method
func (r *httpVPackRequest) Method() string {
return r.method
}
// Clone creates a new request containing the same data as this request
func (r *httpVPackRequest) Clone() driver.Request {
clone := *r

View file

@ -24,11 +24,27 @@ package driver
import "context"
// IndexType represents a index type as string
type IndexType string
// Symbolic constants for index types
const (
PrimaryIndex = IndexType("primary")
FullTextIndex = IndexType("fulltext")
HashIndex = IndexType("hash")
SkipListIndex = IndexType("skiplist")
PersistentIndex = IndexType("persistent")
GeoIndex = IndexType("geo")
)
// Index provides access to a single index in a single collection.
type Index interface {
// Name returns the name of the index.
Name() string
// Type returns the type of the index
Type() IndexType
// Remove removes the entire index.
// If the index does not exist, a NotFoundError is returned.
Remove(ctx context.Context) error

View file

@ -28,8 +28,29 @@ import (
"strings"
)
// indexStringToType converts a string representation of an index to IndexType
func indexStringToType(indexTypeString string) (IndexType, error) {
switch indexTypeString {
case string(FullTextIndex):
return FullTextIndex, nil
case string(HashIndex):
return HashIndex, nil
case string(SkipListIndex):
return SkipListIndex, nil
case string(PrimaryIndex):
return PrimaryIndex, nil
case string(PersistentIndex):
return PersistentIndex, nil
case string(GeoIndex), "geo1", "geo2":
return GeoIndex, nil
default:
return "", WithStack(InvalidArgumentError{Message: "unknown index type"})
}
}
// newIndex creates a new Index implementation.
func newIndex(id string, col *collection) (Index, error) {
func newIndex(id string, indexTypeString string, col *collection) (Index, error) {
if id == "" {
return nil, WithStack(InvalidArgumentError{Message: "id is empty"})
}
@ -40,19 +61,25 @@ func newIndex(id string, col *collection) (Index, error) {
if col == nil {
return nil, WithStack(InvalidArgumentError{Message: "col is nil"})
}
indexType, err := indexStringToType(indexTypeString)
if err != nil {
return nil, WithStack(err)
}
return &index{
id: id,
col: col,
db: col.db,
conn: col.conn,
id: id,
indexType: indexType,
col: col,
db: col.db,
conn: col.conn,
}, nil
}
type index struct {
id string
db *database
col *collection
conn Connection
id string
indexType IndexType
db *database
col *collection
conn Connection
}
// relPath creates the relative path to this index (`_db/<db-name>/_api/index`)
@ -66,6 +93,11 @@ func (i *index) Name() string {
return parts[1]
}
// Type returns the type of the index
func (i *index) Type() IndexType {
return i.indexType
}
// Remove removes the entire index.
// If the index does not exist, a NotFoundError is returned.
func (i *index) Remove(ctx context.Context) error {

View file

@ -25,6 +25,7 @@ package test
import (
"context"
"crypto/tls"
"fmt"
"log"
httplib "net/http"
"os"
@ -178,14 +179,14 @@ func createClientFromEnv(t testEnv, waitUntilReady bool, connection ...*driver.C
t.Fatalf("Failed to create new client: %s", describe(err))
}
if waitUntilReady {
timeout := 3 * time.Minute
timeout := time.Minute
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
if up := waitUntilServerAvailable(ctx, c, t); !up {
t.Fatalf("Connection is not available in %s", timeout)
if up := waitUntilServerAvailable(ctx, c, t); up != nil {
t.Fatalf("Connection is not available in %s: %s", timeout, describe(up))
}
// Synchronize endpoints
if err := c.SynchronizeEndpoints(context.Background()); err != nil {
if err := waitUntilEndpointSynchronized(ctx, c, "", t); err != nil {
t.Errorf("Failed to synchronize endpoints: %s", describe(err))
} else {
logEndpointsOnce.Do(func() {
@ -197,27 +198,68 @@ func createClientFromEnv(t testEnv, waitUntilReady bool, connection ...*driver.C
}
// waitUntilServerAvailable keeps waiting until the server/cluster that the client is addressing is available.
func waitUntilServerAvailable(ctx context.Context, c driver.Client, t testEnv) bool {
instanceUp := make(chan bool)
func waitUntilServerAvailable(ctx context.Context, c driver.Client, t testEnv) error {
instanceUp := make(chan error)
go func() {
for {
verCtx, cancel := context.WithTimeout(ctx, time.Second*5)
if _, err := c.Version(verCtx); err == nil {
// check if leader challenge is ongoing
if _, err := c.ServerRole(verCtx); err != nil {
if !driver.IsNoLeaderOrOngoing(err) {
cancel()
instanceUp <- err
return
}
//t.Logf("Retry. Waiting for leader: %s", describe(err))
continue
}
//t.Logf("Found version %s", v.Version)
cancel()
instanceUp <- true
instanceUp <- nil
return
}
cancel()
//t.Logf("Version failed: %s %#v", describe(err), err)
time.Sleep(time.Second)
time.Sleep(time.Second * 2)
}
}()
select {
case up := <-instanceUp:
return up
case <-ctx.Done():
return false
return nil
}
}
// waitUntilEndpointSynchronized keeps waiting until the endpoints are synchronized. leadership might be ongoing.
func waitUntilEndpointSynchronized(ctx context.Context, c driver.Client, dbname string, t testEnv) error {
endpointsSynced := make(chan error)
go func() {
for {
callCtx, cancel := context.WithTimeout(ctx, time.Second*5)
if err := c.SynchronizeEndpoints2(callCtx, dbname); err != nil {
t.Logf("SynchonizedEnpoints failed: %s", describe(err))
} else {
cancel()
endpointsSynced <- nil
return
}
cancel()
select {
case <-ctx.Done():
return
case <-time.After(time.Second):
}
}
}()
select {
case up := <-endpointsSynced:
return up
case <-ctx.Done():
return fmt.Errorf("Timeout while synchronizing endpoints")
}
}
@ -261,8 +303,8 @@ func TestCreateClientHttpConnectionCustomTransport(t *testing.T) {
timeout := 3 * time.Minute
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
if up := waitUntilServerAvailable(ctx, c, t); !up {
t.Fatalf("Connection is not available in %s", timeout)
if up := waitUntilServerAvailable(ctx, c, t); up != nil {
t.Fatalf("Connection is not available in %s: %s", timeout, describe(up))
}
if info, err := c.Version(driver.WithDetails(ctx)); err != nil {
t.Errorf("Version failed: %s", describe(err))

View file

@ -50,5 +50,5 @@ if [ "$CMD" == "start" ]; then
${STARTER} \
--starter.port=7000 --starter.address=127.0.0.1 \
--docker.image=${ARANGODB} \
--starter.local --starter.mode=${STARTERMODE} --all.log.output=+ $STARTERARGS
--starter.local --starter.mode=${STARTERMODE} --all.log.output=+ $STARTERARGS
fi

View file

@ -51,6 +51,15 @@ func TestClusterHealth(t *testing.T) {
dbservers := 0
coordinators := 0
for _, sh := range h.Health {
if v, err := c.Version(nil); err == nil {
if v.Version.CompareTo(sh.Version) != 0 {
t.Logf("Server version differs from `_api/version`, got `%s` and `%s`", v.Version, sh.Version)
}
} else {
t.Errorf("Version failed: %s", describe(err))
}
switch sh.Role {
case driver.ServerRoleAgent:
agents++
@ -205,3 +214,110 @@ func TestClusterMoveShard(t *testing.T) {
}
}
}
// TestClusterMoveShardWithViews tests the Cluster.MoveShard method with collection
// that are being used in views.
func TestClusterMoveShardWithViews(t *testing.T) {
ctx := context.Background()
c := createClientFromEnv(t, true)
skipBelowVersion(c, "3.4", t)
cl, err := c.Cluster(ctx)
if driver.IsPreconditionFailed(err) {
t.Skip("Not a cluster")
} else {
db, err := c.Database(ctx, "_system")
if err != nil {
t.Fatalf("Failed to open _system database: %s", describe(err))
}
col, err := db.CreateCollection(ctx, "test_move_shard_with_view", &driver.CreateCollectionOptions{
NumberOfShards: 12,
})
if err != nil {
t.Fatalf("CreateCollection failed: %s", describe(err))
}
opts := &driver.ArangoSearchViewProperties{
Links: driver.ArangoSearchLinks{
"test_move_shard_with_view": driver.ArangoSearchElementProperties{},
},
}
viewName := "test_move_shard_view"
if _, err := db.CreateArangoSearchView(ctx, viewName, opts); err != nil {
t.Fatalf("Failed to create view '%s': %s", viewName, describe(err))
}
h, err := cl.Health(ctx)
if err != nil {
t.Fatalf("Health failed: %s", describe(err))
}
inv, err := cl.DatabaseInventory(ctx, db)
if err != nil {
t.Fatalf("DatabaseInventory failed: %s", describe(err))
}
if len(inv.Collections) == 0 {
t.Error("Expected multiple collections, got 0")
}
var targetServerID driver.ServerID
for id, s := range h.Health {
if s.Role == driver.ServerRoleDBServer {
targetServerID = id
break
}
}
if len(targetServerID) == 0 {
t.Fatalf("Failed to find any dbserver")
}
movedShards := 0
for _, colInv := range inv.Collections {
if colInv.Parameters.Name == col.Name() {
for shardID, dbServers := range colInv.Parameters.Shards {
if dbServers[0] != targetServerID {
movedShards++
var rawResponse []byte
if err := cl.MoveShard(driver.WithRawResponse(ctx, &rawResponse), col, shardID, dbServers[0], targetServerID); err != nil {
t.Errorf("MoveShard for shard %s in collection %s failed: %s (raw response '%s' %x)", shardID, col.Name(), describe(err), string(rawResponse), rawResponse)
}
}
}
}
}
if movedShards == 0 {
t.Fatal("Expected to have moved at least 1 shard, all seem to be on target server already")
}
// Wait until all shards are on the targetServerID
start := time.Now()
maxTestTime := time.Minute
lastShardsNotOnTargetServerID := movedShards
for {
shardsNotOnTargetServerID := 0
inv, err := cl.DatabaseInventory(ctx, db)
if err != nil {
t.Errorf("DatabaseInventory failed: %s", describe(err))
} else {
for _, colInv := range inv.Collections {
if colInv.Parameters.Name == col.Name() {
for shardID, dbServers := range colInv.Parameters.Shards {
if dbServers[0] != targetServerID {
shardsNotOnTargetServerID++
t.Logf("Shard %s in on %s, wanted %s", shardID, dbServers[0], targetServerID)
}
}
}
}
}
if shardsNotOnTargetServerID == 0 {
// We're done
break
}
if shardsNotOnTargetServerID != lastShardsNotOnTargetServerID {
// Something changed, we give a bit more time
maxTestTime = maxTestTime + time.Second*15
lastShardsNotOnTargetServerID = shardsNotOnTargetServerID
}
if time.Since(start) > maxTestTime {
t.Errorf("%d shards did not move within %s", shardsNotOnTargetServerID, maxTestTime)
break
}
t.Log("Waiting a bit")
time.Sleep(time.Second * 5)
}
}
}

View file

@ -0,0 +1,68 @@
//
// DISCLAIMER
//
// Copyright 2017 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
// Author Lars Maier
//
package test
import (
"context"
"testing"
driver "github.com/arangodb/go-driver"
)
// TestReadDocumentWithIfMatch creates a document and reads it with a non-matching revision.
func TestReadDocumentWithIfMatch(t *testing.T) {
c := createClientFromEnv(t, true)
db := ensureDatabase(nil, c, "document_read_test", nil, t)
col := ensureCollection(nil, db, "document_read_test", nil, t)
doc := UserDoc{
"Jan",
40,
}
meta, err := col.CreateDocument(nil, doc)
if err != nil {
t.Fatalf("Failed to create new document: %s", describe(err))
}
ctx := context.Background()
ctx = driver.WithRevision(ctx, meta.Rev)
meta2, err := col.ReadDocument(ctx, meta.Key, &doc)
if err != nil {
t.Fatalf("Failed to read document: %s", describe(err))
}
if meta2 != meta {
t.Error("Read wrong meta data.")
}
var resp driver.Response
ctx2 := context.Background()
ctx2 = driver.WithRevision(ctx2, "nonsense")
ctx2 = driver.WithResponse(ctx2, &resp)
_, err = col.ReadDocument(ctx2, meta.Key, &doc)
if err == nil {
t.Error("Reading with wrong revision did not fail")
}
if resp.StatusCode() != 412 {
t.Errorf("Expected status code 412, found %d", resp.StatusCode())
}
}

View file

@ -50,6 +50,9 @@ func TestEnsureFullTextIndex(t *testing.T) {
if !created {
t.Error("Expected created to be true, got false")
}
if idxType := idx.Type(); idxType != driver.FullTextIndex {
t.Errorf("Expected FullTextIndex, found `%s`", idxType)
}
// Index must exists now
if found, err := col.IndexExists(nil, idx.Name()); err != nil {
@ -102,6 +105,9 @@ func TestEnsureGeoIndex(t *testing.T) {
if !created {
t.Error("Expected created to be true, got false")
}
if idxType := idx.Type(); idxType != driver.GeoIndex {
t.Errorf("Expected GeoIndex, found `%s`", idxType)
}
// Index must exists now
if found, err := col.IndexExists(nil, idx.Name()); err != nil {
@ -156,6 +162,9 @@ func TestEnsureHashIndex(t *testing.T) {
if !created {
t.Error("Expected created to be true, got false")
}
if idxType := idx.Type(); idxType != driver.HashIndex {
t.Errorf("Expected HashIndex, found `%s`", idxType)
}
// Index must exists now
if found, err := col.IndexExists(nil, idx.Name()); err != nil {
@ -210,6 +219,9 @@ func TestEnsurePersistentIndex(t *testing.T) {
if !created {
t.Error("Expected created to be true, got false")
}
if idxType := idx.Type(); idxType != driver.PersistentIndex {
t.Errorf("Expected PersistentIndex, found `%s`", idxType)
}
// Index must exists now
if found, err := col.IndexExists(nil, idx.Name()); err != nil {
@ -264,6 +276,9 @@ func TestEnsureSkipListIndex(t *testing.T) {
if !created {
t.Error("Expected created to be true, got false")
}
if idxType := idx.Type(); idxType != driver.SkipListIndex {
t.Errorf("Expected SkipListIndex, found `%s`", idxType)
}
// Index must exists now
if found, err := col.IndexExists(nil, idx.Name()); err != nil {

View file

@ -29,6 +29,23 @@ import (
driver "github.com/arangodb/go-driver"
)
// TestDefaultIndexes creates a collection without any custom index.
func TestDefaultIndexes(t *testing.T) {
c := createClientFromEnv(t, true)
db := ensureDatabase(nil, c, "index_test", nil, t)
col := ensureCollection(nil, db, "def_indexes_test", nil, t)
// Get list of indexes
if idxs, err := col.Indexes(context.Background()); err != nil {
t.Fatalf("Failed to get indexes: %s", describe(err))
} else {
if len(idxs) != 1 {
// 1 is always added by the system
t.Errorf("Expected 1 index, got %d", len(idxs))
}
}
}
// TestCreateFullTextIndex creates a collection with a full text index.
func TestIndexes(t *testing.T) {
c := createClientFromEnv(t, true)
@ -36,10 +53,19 @@ func TestIndexes(t *testing.T) {
col := ensureCollection(nil, db, "indexes_test", nil, t)
// Create some indexes
if _, _, err := col.EnsureFullTextIndex(nil, []string{"name"}, nil); err != nil {
if idx, _, err := col.EnsureFullTextIndex(nil, []string{"name"}, nil); err == nil {
if idxType := idx.Type(); idxType != driver.FullTextIndex {
t.Errorf("Expected FullTextIndex, found `%s`", idxType)
}
} else {
t.Fatalf("Failed to create new index: %s", describe(err))
}
if _, _, err := col.EnsureHashIndex(nil, []string{"age", "gender"}, nil); err != nil {
if idx, _, err := col.EnsureHashIndex(nil, []string{"age", "gender"}, nil); err == nil {
if idxType := idx.Type(); idxType != driver.HashIndex {
t.Errorf("Expected HashIndex, found `%s`", idxType)
}
} else {
t.Fatalf("Failed to create new index: %s", describe(err))
}

View file

@ -51,6 +51,7 @@ func TestUpdateUserPasswordMyself(t *testing.T) {
if err != nil {
t.Fatalf("Expected success, got %s", describe(err))
}
ensureSynchronizedEndpoints(authClient, "", t)
if isVST1_0 && !isv32p {
t.Skip("Cannot update my own password using VST in 3.1")
@ -89,6 +90,7 @@ func TestUpdateUserPasswordOtherUser(t *testing.T) {
if err != nil {
t.Fatalf("Expected success, got %s", describe(err))
}
ensureSynchronizedEndpoints(authClient, "", t)
if isVST1_0 && !isv32p {
t.Skip("Cannot update other password using VST in 3.1")
@ -146,12 +148,10 @@ func TestGrantUserDatabase(t *testing.T) {
if err != nil {
t.Fatalf("Expected success, got %s", describe(err))
}
ensureSynchronizedEndpoints(authClient, "grant_user_test", t)
authDb := waitForDatabaseAccess(authClient, "grant_user_test", t)
// Try to create a collection in the db
authDb, err := authClient.Database(nil, "grant_user_test")
if err != nil {
t.Fatalf("Expected success, got %s", describe(err))
}
if _, err := authDb.CreateCollection(nil, "some_collection", nil); err != nil {
t.Errorf("Expected success, got %s", describe(err))
}
@ -230,12 +230,10 @@ func TestGrantUserDefaultDatabase(t *testing.T) {
if err != nil {
t.Fatalf("Expected success, got %s", describe(err))
}
ensureSynchronizedEndpoints(authClient, "grant_user_def_test", t)
// Try to create a collection in the db, should succeed
authDb, err := authClient.Database(nil, db.Name())
if err != nil {
t.Fatalf("Expected success, got %s", describe(err))
}
authDb := waitForDatabaseAccess(authClient, "grant_user_def_test", t)
authCol, err := authDb.CreateCollection(nil, "books_def_db", nil)
if err != nil {
@ -348,11 +346,26 @@ func TestGrantUserCollection(t *testing.T) {
if err := u.SetCollectionAccess(nil, col, driver.GrantReadWrite); err != nil {
t.Fatalf("SetCollectionAccess failed: %s", describe(err))
}
// Read back collection access
if grant, err := u.GetCollectionAccess(nil, col); err != nil {
t.Fatalf("GetCollectionAccess failed: %s", describe(err))
} else if grant != driver.GrantReadWrite {
t.Errorf("Collection access invalid, expected 'rw', got '%s'", grant)
// wait for change to propagate
{
deadline := time.Now().Add(time.Minute)
for {
// Read back collection access
if grant, err := u.GetCollectionAccess(nil, col); err == nil {
if grant == driver.GrantReadWrite {
break
}
if time.Now().Before(deadline) {
t.Logf("Expected failure, got %s, trying again...", describe(err))
time.Sleep(time.Second * 2)
continue
}
t.Errorf("Collection access invalid, expected 'rw', got '%s'", grant)
} else {
t.Fatalf("GetCollectionAccess failed: %s", describe(err))
}
}
}
authClient, err := driver.NewClient(driver.ClientConfig{
@ -362,12 +375,10 @@ func TestGrantUserCollection(t *testing.T) {
if err != nil {
t.Fatalf("Expected success, got %s", describe(err))
}
ensureSynchronizedEndpoints(authClient, "grant_user_col_test", t)
authDb := waitForDatabaseAccess(authClient, "grant_user_col_test", t)
// Try to create a document in the col
authDb, err := authClient.Database(nil, db.Name())
if err != nil {
t.Fatalf("Expected success, got %s", describe(err))
}
authCol, err := authDb.Collection(nil, col.Name())
if err != nil {
t.Fatalf("Expected success, got %s", describe(err))
@ -602,3 +613,28 @@ func TestUserAccessibleDatabases(t *testing.T) {
t.Logf("Last part of test fails on version < 3.2 (got version %s)", version.Version)
}
}
func waitForDatabaseAccess(authClient driver.Client, dbname string, t *testing.T) driver.Database {
deadline := time.Now().Add(time.Minute)
for {
// Try to select the database
authDb, err := authClient.Database(nil, dbname)
if err == nil {
return authDb
}
if time.Now().Before(deadline) {
t.Logf("Expected success, got %s, trying again...", describe(err))
time.Sleep(time.Second * 2)
continue
}
t.Fatalf("Failed to select database, got %s", describe(err))
return nil
}
}
func ensureSynchronizedEndpoints(authClient driver.Client, dbname string, t *testing.T) {
ctx, _ := context.WithTimeout(context.Background(), time.Second*10)
if err := waitUntilEndpointSynchronized(ctx, authClient, dbname, t); err != nil {
t.Fatalf("Failed to synchronize endpoint: %s", describe(err))
}
}

View file

@ -0,0 +1,502 @@
//
// DISCLAIMER
//
// Copyright 2018 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
// Author Ewout Prangsma
//
package test
import (
"context"
"fmt"
"testing"
driver "github.com/arangodb/go-driver"
)
// ensureArangoSearchView is a helper to check if an arangosearch view exists and create it if needed.
// It will fail the test when an error occurs.
func ensureArangoSearchView(ctx context.Context, db driver.Database, name string, options *driver.ArangoSearchViewProperties, t testEnv) driver.ArangoSearchView {
v, err := db.View(ctx, name)
if driver.IsNotFound(err) {
v, err = db.CreateArangoSearchView(ctx, name, options)
if err != nil {
t.Fatalf("Failed to create arangosearch view '%s': %s", name, describe(err))
}
} else if err != nil {
t.Fatalf("Failed to open view '%s': %s", name, describe(err))
}
result, err := v.ArangoSearchView()
if err != nil {
t.Fatalf("Failed to open view '%s' as arangosearch view: %s", name, describe(err))
}
return result
}
// checkLinkExists tests if a given collection is linked to the given arangosearch view
func checkLinkExists(ctx context.Context, view driver.ArangoSearchView, colName string, t testEnv) bool {
props, err := view.Properties(ctx)
if err != nil {
t.Fatalf("Failed to get view properties: %s", describe(err))
}
links := props.Links
if _, exists := links[colName]; !exists {
return false
}
return true
}
// tryAddArangoSearchLink is a helper that adds a link to a view and collection.
// It will fail the test when an error occurs and returns wether the link is actually there or not.
func tryAddArangoSearchLink(ctx context.Context, db driver.Database, view driver.ArangoSearchView, colName string, t testEnv) bool {
addprop := driver.ArangoSearchViewProperties{
Links: driver.ArangoSearchLinks{
colName: driver.ArangoSearchElementProperties{},
},
}
if err := view.SetProperties(ctx, addprop); err != nil {
t.Fatalf("Could not create link, view: %s, collection: %s, error: %s", view.Name(), colName, describe(err))
}
return checkLinkExists(ctx, view, colName, t)
}
// assertArangoSearchView is a helper to check if an arangosearch view exists and fail if it does not.
func assertArangoSearchView(ctx context.Context, db driver.Database, name string, t *testing.T) driver.ArangoSearchView {
v, err := db.View(ctx, name)
if driver.IsNotFound(err) {
t.Fatalf("View '%s': does not exist", name)
} else if err != nil {
t.Fatalf("Failed to open view '%s': %s", name, describe(err))
}
result, err := v.ArangoSearchView()
if err != nil {
t.Fatalf("Failed to open view '%s' as arangosearch view: %s", name, describe(err))
}
return result
}
// TestCreateArangoSearchView creates an arangosearch view and then checks that it exists.
func TestCreateArangoSearchView(t *testing.T) {
ctx := context.Background()
c := createClientFromEnv(t, true)
skipBelowVersion(c, "3.4", t)
db := ensureDatabase(ctx, c, "view_test", nil, t)
ensureCollection(ctx, db, "someCol", nil, t)
name := "test_create_asview"
opts := &driver.ArangoSearchViewProperties{
Links: driver.ArangoSearchLinks{
"someCol": driver.ArangoSearchElementProperties{},
},
}
v, err := db.CreateArangoSearchView(ctx, name, opts)
if err != nil {
t.Fatalf("Failed to create view '%s': %s", name, describe(err))
}
// View must exist now
if found, err := db.ViewExists(ctx, name); err != nil {
t.Errorf("ViewExists('%s') failed: %s", name, describe(err))
} else if !found {
t.Errorf("ViewExists('%s') return false, expected true", name)
}
// Check v.Name
if actualName := v.Name(); actualName != name {
t.Errorf("Name() failed. Got '%s', expected '%s'", actualName, name)
}
// Check v properties
p, err := v.Properties(ctx)
if err != nil {
t.Fatalf("Properties failed: %s", describe(err))
}
if len(p.Links) != 1 {
t.Errorf("Expected 1 link, got %d", len(p.Links))
}
}
// TestCreateArangoSearchViewInvalidLinks attempts to create an arangosearch view with invalid links and then checks that it does not exists.
func TestCreateArangoSearchViewInvalidLinks(t *testing.T) {
ctx := context.Background()
c := createClientFromEnv(t, true)
skipBelowVersion(c, "3.4", t)
db := ensureDatabase(ctx, c, "view_test", nil, t)
name := "test_create_inv_view"
opts := &driver.ArangoSearchViewProperties{
Links: driver.ArangoSearchLinks{
"some_nonexistent_col": driver.ArangoSearchElementProperties{},
},
}
_, err := db.CreateArangoSearchView(ctx, name, opts)
if err == nil {
t.Fatalf("Creating view did not fail")
}
// View must not exist now
if found, err := db.ViewExists(ctx, name); err != nil {
t.Errorf("ViewExists('%s') failed: %s", name, describe(err))
} else if found {
t.Errorf("ViewExists('%s') return true, expected false", name)
}
// Try to open view, must fail as well
if v, err := db.View(ctx, name); !driver.IsNotFound(err) {
t.Errorf("Expected NotFound error from View('%s'), got %s instead (%#v)", name, describe(err), v)
}
}
// TestCreateEmptyArangoSearchView creates an arangosearch view without any links.
func TestCreateEmptyArangoSearchView(t *testing.T) {
ctx := context.Background()
c := createClientFromEnv(t, true)
skipBelowVersion(c, "3.4", t)
db := ensureDatabase(ctx, c, "view_test", nil, t)
name := "test_create_empty_asview"
v, err := db.CreateArangoSearchView(ctx, name, nil)
if err != nil {
t.Fatalf("Failed to create view '%s': %s", name, describe(err))
}
// View must exist now
if found, err := db.ViewExists(ctx, name); err != nil {
t.Errorf("ViewExists('%s') failed: %s", name, describe(err))
} else if !found {
t.Errorf("ViewExists('%s') return false, expected true", name)
}
// Check v properties
p, err := v.Properties(ctx)
if err != nil {
t.Fatalf("Properties failed: %s", describe(err))
}
if len(p.Links) != 0 {
t.Errorf("Expected 0 links, got %d", len(p.Links))
}
}
// TestCreateDuplicateArangoSearchView creates an arangosearch view twice and then checks that it exists.
func TestCreateDuplicateArangoSearchView(t *testing.T) {
ctx := context.Background()
c := createClientFromEnv(t, true)
skipBelowVersion(c, "3.4", t)
db := ensureDatabase(ctx, c, "view_test", nil, t)
name := "test_create_dup_asview"
if _, err := db.CreateArangoSearchView(ctx, name, nil); err != nil {
t.Fatalf("Failed to create view '%s': %s", name, describe(err))
}
// View must exist now
if found, err := db.ViewExists(ctx, name); err != nil {
t.Errorf("ViewExists('%s') failed: %s", name, describe(err))
} else if !found {
t.Errorf("ViewExists('%s') return false, expected true", name)
}
// Try to create again. Must fail
if _, err := db.CreateArangoSearchView(ctx, name, nil); !driver.IsConflict(err) {
t.Fatalf("Expect a Conflict error from CreateArangoSearchView, got %s", describe(err))
}
}
// TestCreateArangoSearchViewThenRemoveCollection creates an arangosearch view
// with a link to an existing collection and the removes that collection.
func TestCreateArangoSearchViewThenRemoveCollection(t *testing.T) {
ctx := context.Background()
c := createClientFromEnv(t, true)
skipBelowVersion(c, "3.4", t)
db := ensureDatabase(ctx, c, "view_test", nil, t)
col := ensureCollection(ctx, db, "someViewTmpCol", nil, t)
name := "test_create_view_then_rem_col"
opts := &driver.ArangoSearchViewProperties{
Links: driver.ArangoSearchLinks{
"someViewTmpCol": driver.ArangoSearchElementProperties{},
},
}
v, err := db.CreateArangoSearchView(ctx, name, opts)
if err != nil {
t.Fatalf("Failed to create view '%s': %s", name, describe(err))
}
// View must exist now
if found, err := db.ViewExists(ctx, name); err != nil {
t.Errorf("ViewExists('%s') failed: %s", name, describe(err))
} else if !found {
t.Errorf("ViewExists('%s') return false, expected true", name)
}
// Check v.Name
if actualName := v.Name(); actualName != name {
t.Errorf("Name() failed. Got '%s', expected '%s'", actualName, name)
}
// Check v properties
p, err := v.Properties(ctx)
if err != nil {
t.Fatalf("Properties failed: %s", describe(err))
}
if len(p.Links) != 1 {
t.Errorf("Expected 1 link, got %d", len(p.Links))
}
// Now delete the collection
if err := col.Remove(ctx); err != nil {
t.Fatalf("Failed to remove collection '%s': %s", col.Name(), describe(err))
}
// Re-check v properties
p, err = v.Properties(ctx)
if err != nil {
t.Fatalf("Properties failed: %s", describe(err))
}
if len(p.Links) != 0 {
// TODO is the really the correct expected behavior.
t.Errorf("Expected 0 links, got %d", len(p.Links))
}
}
// TestAddCollectionMultipleViews creates a collection and two view. adds the collection to both views
// and checks if the links exist. The links are set via modifying properties.
func TestAddCollectionMultipleViews(t *testing.T) {
ctx := context.Background()
c := createClientFromEnv(t, true)
skipBelowVersion(c, "3.4", t)
db := ensureDatabase(ctx, c, "col_in_multi_view_test", nil, t)
ensureCollection(ctx, db, "col_in_multi_view", nil, t)
v1 := ensureArangoSearchView(ctx, db, "col_in_multi_view_view1", nil, t)
if !tryAddArangoSearchLink(ctx, db, v1, "col_in_multi_view", t) {
t.Error("Link does not exists")
}
v2 := ensureArangoSearchView(ctx, db, "col_in_multi_view_view2", nil, t)
if !tryAddArangoSearchLink(ctx, db, v2, "col_in_multi_view", t) {
t.Error("Link does not exists")
}
}
// TestAddCollectionMultipleViews creates a collection and two view. adds the collection to both views
// and checks if the links exist. The links are set when creating the view.
func TestAddCollectionMultipleViewsViaCreate(t *testing.T) {
ctx := context.Background()
c := createClientFromEnv(t, true)
skipBelowVersion(c, "3.4", t)
db := ensureDatabase(ctx, c, "col_in_multi_view_create_test", nil, t)
ensureCollection(ctx, db, "col_in_multi_view_create", nil, t)
opts := &driver.ArangoSearchViewProperties{
Links: driver.ArangoSearchLinks{
"col_in_multi_view_create": driver.ArangoSearchElementProperties{},
},
}
v1 := ensureArangoSearchView(ctx, db, "col_in_multi_view_view1", opts, t)
if !checkLinkExists(ctx, v1, "col_in_multi_view_create", t) {
t.Error("Link does not exists")
}
v2 := ensureArangoSearchView(ctx, db, "col_in_multi_view_view2", opts, t)
if !checkLinkExists(ctx, v2, "col_in_multi_view_create", t) {
t.Error("Link does not exists")
}
}
// TestGetArangoSearchView creates an arangosearch view and then gets it again.
func TestGetArangoSearchView(t *testing.T) {
ctx := context.Background()
c := createClientFromEnv(t, true)
skipBelowVersion(c, "3.4", t)
db := ensureDatabase(ctx, c, "view_test", nil, t)
col := ensureCollection(ctx, db, "someCol", nil, t)
name := "test_get_asview"
opts := &driver.ArangoSearchViewProperties{
Links: driver.ArangoSearchLinks{
"someCol": driver.ArangoSearchElementProperties{},
},
}
if _, err := db.CreateArangoSearchView(ctx, name, opts); err != nil {
t.Fatalf("Failed to create view '%s': %s", name, describe(err))
}
// Get view
v, err := db.View(ctx, name)
if err != nil {
t.Fatalf("View('%s') failed: %s", name, describe(err))
}
asv, err := v.ArangoSearchView()
if err != nil {
t.Fatalf("ArangoSearchView() failed: %s", describe(err))
}
// Check v.Name
if actualName := v.Name(); actualName != name {
t.Errorf("Name() failed. Got '%s', expected '%s'", actualName, name)
}
// Check asv properties
p, err := asv.Properties(ctx)
if err != nil {
t.Fatalf("Properties failed: %s", describe(err))
}
if len(p.Links) != 1 {
t.Errorf("Expected 1 link, got %d", len(p.Links))
}
// Check indexes on collection
indexes, err := col.Indexes(ctx)
if err != nil {
t.Fatalf("Indexes() failed: %s", describe(err))
}
if len(indexes) != 1 {
// 1 is always added by the system
t.Errorf("Expected 1 index, got %d", len(indexes))
}
}
// TestGetArangoSearchViews creates several arangosearch views and then gets all of them.
func TestGetArangoSearchViews(t *testing.T) {
ctx := context.Background()
c := createClientFromEnv(t, true)
skipBelowVersion(c, "3.4", t)
db := ensureDatabase(ctx, c, "view_test", nil, t)
// Get views before adding some
before, err := db.Views(ctx)
if err != nil {
t.Fatalf("Views failed: %s", describe(err))
}
// Create views
names := make([]string, 5)
for i := 0; i < len(names); i++ {
names[i] = fmt.Sprintf("test_get_views_%d", i)
if _, err := db.CreateArangoSearchView(ctx, names[i], nil); err != nil {
t.Fatalf("Failed to create view '%s': %s", names[i], describe(err))
}
}
// Get views
after, err := db.Views(ctx)
if err != nil {
t.Fatalf("Views failed: %s", describe(err))
}
// Check count
if len(before)+len(names) != len(after) {
t.Errorf("Expected %d views, got %d", len(before)+len(names), len(after))
}
// Check view names
for _, n := range names {
found := false
for _, v := range after {
if v.Name() == n {
found = true
break
}
}
if !found {
t.Errorf("Expected view '%s' is not found", n)
}
}
}
// TestRemoveArangoSearchView creates an arangosearch view and then removes it.
func TestRemoveArangoSearchView(t *testing.T) {
ctx := context.Background()
c := createClientFromEnv(t, true)
skipBelowVersion(c, "3.4", t)
db := ensureDatabase(ctx, c, "view_test", nil, t)
name := "test_remove_asview"
v, err := db.CreateArangoSearchView(ctx, name, nil)
if err != nil {
t.Fatalf("Failed to create collection '%s': %s", name, describe(err))
}
// View must exist now
if found, err := db.ViewExists(ctx, name); err != nil {
t.Errorf("ViewExists('%s') failed: %s", name, describe(err))
} else if !found {
t.Errorf("ViewExists('%s') return false, expected true", name)
}
// Now remove it
if err := v.Remove(ctx); err != nil {
t.Fatalf("Failed to remove view '%s': %s", name, describe(err))
}
// View must not exist now
if found, err := db.ViewExists(ctx, name); err != nil {
t.Errorf("ViewExists('%s') failed: %s", name, describe(err))
} else if found {
t.Errorf("ViewExists('%s') return true, expected false", name)
}
}
// TestUseArangoSearchView tries to create a view and actually use it in
// an AQL query.
func TestUseArangoSearchView(t *testing.T) {
ctx := context.Background()
c := createClientFromEnv(t, true)
skipBelowVersion(c, "3.4", t)
db := ensureDatabase(nil, c, "view_test", nil, t)
col := ensureCollection(ctx, db, "some_collection", nil, t)
ensureArangoSearchView(ctx, db, "some_view", &driver.ArangoSearchViewProperties{
Links: driver.ArangoSearchLinks{
"some_collection": driver.ArangoSearchElementProperties{
Fields: driver.ArangoSearchFields{
"name": driver.ArangoSearchElementProperties{},
},
},
},
}, t)
docs := []UserDoc{
UserDoc{
"John",
23,
},
UserDoc{
"Alice",
43,
},
UserDoc{
"Helmut",
56,
},
}
_, errs, err := col.CreateDocuments(ctx, docs)
if err != nil {
t.Fatalf("Failed to create new documents: %s", describe(err))
} else if err := errs.FirstNonNil(); err != nil {
t.Fatalf("Expected no errors, got first: %s", describe(err))
}
// now access it via AQL with waitForSync
{
cur, err := db.Query(driver.WithQueryCount(ctx), `FOR doc IN some_view SEARCH doc.name == "John" OPTIONS {waitForSync:true} RETURN doc`, nil)
if err != nil {
t.Fatalf("Failed to query data using arangodsearch: %s", describe(err))
} else if cur.Count() != 1 || !cur.HasMore() {
t.Fatalf("Wrong number of return values: expected 1, found %d", cur.Count())
}
var doc UserDoc
_, err = cur.ReadDocument(ctx, &doc)
if err != nil {
t.Fatalf("Failed to read document: %s", describe(err))
}
if doc.Name != "John" {
t.Fatalf("Expected result `John`, found `%s`", doc.Name)
}
}
// now access it via AQL without waitForSync
{
cur, err := db.Query(driver.WithQueryCount(ctx), `FOR doc IN some_view SEARCH doc.name == "John" RETURN doc`, nil)
if err != nil {
t.Fatalf("Failed to query data using arangodsearch: %s", describe(err))
} else if cur.Count() != 1 || !cur.HasMore() {
t.Fatalf("Wrong number of return values: expected 1, found %d", cur.Count())
}
var doc UserDoc
_, err = cur.ReadDocument(ctx, &doc)
if err != nil {
t.Fatalf("Failed to read document: %s", describe(err))
}
if doc.Name != "John" {
t.Fatalf("Expected result `John`, found `%s`", doc.Name)
}
}
}

View file

@ -66,6 +66,9 @@ func (c *vertexCollection) readDocument(ctx context.Context, key string, result
if err := resp.CheckStatus(200); err != nil {
return DocumentMeta{}, contextSettings{}, WithStack(err)
}
// Concerns: ReadDocuments reads multiple documents via multiple calls to readDocument (this function).
// Currently with AllowDirtyReads the wasDirtyFlag is only set according to the last read request.
loadContextResponseValues(cs, resp)
// Parse metadata
var meta DocumentMeta
if err := resp.ParseBody("vertex", &meta); err != nil {

View file

@ -0,0 +1,48 @@
//
// DISCLAIMER
//
// Copyright 2018 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
// Author Ewout Prangsma
//
package driver
import (
"context"
)
// View provides access to the information of a view.
// Views are only available in ArangoDB 3.4 and higher.
type View interface {
// Name returns the name of the view.
Name() string
// Type returns the type of this view.
Type() ViewType
// ArangoSearchView returns this view as an ArangoSearch view.
// When the type of the view is not ArangoSearch, an error is returned.
ArangoSearchView() (ArangoSearchView, error)
// Database returns the database containing the view.
Database() Database
// Remove removes the entire view.
// If the view does not exist, a NotFoundError is returned.
Remove(ctx context.Context) error
}

View file

@ -0,0 +1,130 @@
//
// DISCLAIMER
//
// Copyright 2018 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
// Author Ewout Prangsma
//
package driver
import (
"context"
)
// ArangoSearchView provides access to the information of a view.
// Views are only available in ArangoDB 3.4 and higher.
type ArangoSearchView interface {
// Include generic View functions
View
// Properties fetches extended information about the view.
Properties(ctx context.Context) (ArangoSearchViewProperties, error)
// SetProperties changes properties of the view.
SetProperties(ctx context.Context, options ArangoSearchViewProperties) error
}
// ArangoSearchViewProperties contains properties an an ArangoSearch view.
type ArangoSearchViewProperties struct {
// CleanupIntervalStep specifies the minimum number of commits to wait between
// removing unused files in the data directory.
// Defaults to 10.
// Use 0 to disable waiting.
// For the case where the consolidation policies merge segments often
// (i.e. a lot of commit+consolidate), a lower value will cause a lot of
// disk space to be wasted.
// For the case where the consolidation policies rarely merge segments
// (i.e. few inserts/deletes), a higher value will impact performance
// without any added benefits.
CleanupIntervalStep int64 `json:"cleanupIntervalStep,omitempty"`
// ConsolidationInterval specifies the minimum number of milliseconds that must be waited
// between committing index data changes and making them visible to queries.
// Defaults to 60000.
// Use 0 to disable.
// For the case where there are a lot of inserts/updates, a lower value,
// until commit, will cause the index not to account for them and memory usage
// would continue to grow.
// For the case where there are a few inserts/updates, a higher value will
// impact performance and waste disk space for each commit call without
// any added benefits.
ConsolidationInterval int64 `json:"consolidationIntervalMsec,omitempty"`
// ConsolidationPolicy specifies thresholds for consolidation.
ConsolidationPolicy *ArangoSearchConsolidationPolicy `json:"consolidationPolicy,omitempty"`
/*
// Locale specifies the default locale used for queries on analyzed string values.
// Defaults to "C". TODO What is that?
Locale Locale `json:"locale,omitempty"`
*/
// Links contains the properties for how individual collections
// are indexed in thie view.
// The key of the map are collection names.
Links ArangoSearchLinks `json:"links,omitempty"`
}
// Locale is a strongly typed specifier of a locale.
// TODO specify semantics.
type Locale string
// ArangoSearchConsolidationPolicy holds threshold values specifying when to
// consolidate view data.
// Semantics of the values depend on where they are used.
type ArangoSearchConsolidationPolicy struct {
// Threshold is a percentage (0..1)
Threshold float64 `json:"threshold,omitempty"`
// SegmentThreshold is an absolute value.
SegmentThreshold int64 `json:"segmentThreshold,omitempty"`
}
// ArangoSearchLinks is a strongly typed map containing links between a
// collection and a view.
// The keys in the map are collection names.
type ArangoSearchLinks map[string]ArangoSearchElementProperties
// ArangoSearchFields is a strongly typed map containing properties per field.
// The keys in the map are field names.
type ArangoSearchFields map[string]ArangoSearchElementProperties
// ArangoSearchElementProperties contains properties that specify how an element
// is indexed in an ArangoSearch view.
// Note that this structure is recursive. Settings not specified (nil)
// at a given level will inherit their setting from a lower level.
type ArangoSearchElementProperties struct {
// The list of analyzers to be used for indexing of string values. Defaults to ["identify"].
Analyzers []string `json:"analyzers,omitempty"`
// If set to true, all fields of this element will be indexed. Defaults to false.
IncludeAllFields *bool `json:"includeAllFields,omitempty"`
// If set to true, values in a listed are treated as separate values. Defaults to false.
TrackListPositions *bool `json:"trackListPositions,omitempty"`
// This values specifies how the view should track values.
StoreValues ArangoSearchStoreValues `json:"storeValues,omitempty"`
// Fields contains the properties for individual fields of the element.
// The key of the map are field names.
Fields ArangoSearchFields `json:"fields,omitempty"`
}
// ArangoSearchStoreValues is the type of the StoreValues option of an ArangoSearch element.
type ArangoSearchStoreValues string
const (
// ArangoSearchStoreValuesNone specifies that a view should not store values.
ArangoSearchStoreValuesNone ArangoSearchStoreValues = "none"
// ArangoSearchStoreValuesID specifies that a view should only store
// information about value presence, to allow use of the EXISTS() function.
ArangoSearchStoreValuesID ArangoSearchStoreValues = "id"
)

View file

@ -0,0 +1,74 @@
//
// DISCLAIMER
//
// Copyright 2018 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
// Author Ewout Prangsma
//
package driver
import (
"context"
"path"
)
// viewArangoSearch implements ArangoSearchView
type viewArangoSearch struct {
view
}
// Properties fetches extended information about the view.
func (v *viewArangoSearch) Properties(ctx context.Context) (ArangoSearchViewProperties, error) {
req, err := v.conn.NewRequest("GET", path.Join(v.relPath(), "properties"))
if err != nil {
return ArangoSearchViewProperties{}, WithStack(err)
}
applyContextSettings(ctx, req)
resp, err := v.conn.Do(ctx, req)
if err != nil {
return ArangoSearchViewProperties{}, WithStack(err)
}
if err := resp.CheckStatus(200); err != nil {
return ArangoSearchViewProperties{}, WithStack(err)
}
var data ArangoSearchViewProperties
if err := resp.ParseBody("", &data); err != nil {
return ArangoSearchViewProperties{}, WithStack(err)
}
return data, nil
}
// SetProperties changes properties of the view.
func (v *viewArangoSearch) SetProperties(ctx context.Context, options ArangoSearchViewProperties) error {
req, err := v.conn.NewRequest("PUT", path.Join(v.relPath(), "properties"))
if err != nil {
return WithStack(err)
}
if _, err := req.SetBody(options); err != nil {
return WithStack(err)
}
applyContextSettings(ctx, req)
resp, err := v.conn.Do(ctx, req)
if err != nil {
return WithStack(err)
}
if err := resp.CheckStatus(200); err != nil {
return WithStack(err)
}
return nil
}

View file

@ -0,0 +1,104 @@
//
// DISCLAIMER
//
// Copyright 2018 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
// Author Ewout Prangsma
//
package driver
import (
"context"
"fmt"
"net/http"
"path"
)
// newView creates a new View implementation.
func newView(name string, viewType ViewType, db *database) (View, error) {
if name == "" {
return nil, WithStack(InvalidArgumentError{Message: "name is empty"})
}
if viewType == "" {
return nil, WithStack(InvalidArgumentError{Message: "viewType is empty"})
}
if db == nil {
return nil, WithStack(InvalidArgumentError{Message: "db is nil"})
}
return &view{
name: name,
viewType: viewType,
db: db,
conn: db.conn,
}, nil
}
type view struct {
name string
viewType ViewType
db *database
conn Connection
}
// relPath creates the relative path to this view (`_db/<db-name>/_api/view/<view-name>`)
func (v *view) relPath() string {
escapedName := pathEscape(v.name)
return path.Join(v.db.relPath(), "_api", "view", escapedName)
}
// Name returns the name of the view.
func (v *view) Name() string {
return v.name
}
// Type returns the type of this view.
func (v *view) Type() ViewType {
return v.viewType
}
// ArangoSearchView returns this view as an ArangoSearch view.
// When the type of the view is not ArangoSearch, an error is returned.
func (v *view) ArangoSearchView() (ArangoSearchView, error) {
if v.viewType != ViewTypeArangoSearch {
return nil, WithStack(newArangoError(http.StatusConflict, 0, fmt.Sprintf("Type must be '%s', got '%s'", ViewTypeArangoSearch, v.viewType)))
}
return &viewArangoSearch{view: *v}, nil
}
// Database returns the database containing the view.
func (v *view) Database() Database {
return v.db
}
// Remove removes the entire view.
// If the view does not exist, a NotFoundError is returned.
func (v *view) Remove(ctx context.Context) error {
req, err := v.conn.NewRequest("DELETE", v.relPath())
if err != nil {
return WithStack(err)
}
applyContextSettings(ctx, req)
resp, err := v.conn.Do(ctx, req)
if err != nil {
return WithStack(err)
}
if err := resp.CheckStatus(200); err != nil {
return WithStack(err)
}
return nil
}

View file

@ -43,6 +43,16 @@ type vstRequest struct {
written bool
}
// Path returns the Request path
func (r *vstRequest) Path() string {
return r.path
}
// Method returns the Request method
func (r *vstRequest) Method() string {
return r.method
}
// Clone creates a new request containing the same data as this request
func (r *vstRequest) Clone() driver.Request {
clone := *r