arangolite

package module
v2.0.0+incompatible Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 12, 2017 License: MIT Imports: 14 Imported by: 0

README

Arangolite Build Status Coverage Status Code Climate

Arangolite is a lightweight ArangoDB driver for Go.

It focuses entirely on pure AQL querying. See AranGO for a more ORM-like experience.

Arangolite also features a LoopBack heavily inspired filtering system, in a separated package so you don't need to import it if you don't use it.

Installation

To install Arangolite:

go get github.com/solher/arangolite

Basic Usage

package main

import (
  "encoding/json"
  "fmt"

  "github.com/solher/arangolite"
)

type Node struct {
  arangolite.Document
}

func main() {
  db := arangolite.New().
    LoggerOptions(false, false, false).
    Connect("http://localhost:8000", "_system", "root", "rootPassword")

  _, _ = db.Run(&arangolite.CreateDatabase{
		Name: "testDB",
		Users: []map[string]interface{}{
			{"username": "root", "passwd": "rootPassword"},
			{"username": "user", "passwd": "password"},
		},
	})

  db.SwitchDatabase("testDB").SwitchUser("user", "password")

  _, _ = db.Run(&arangolite.CreateCollection{Name: "nodes"})

  key := "48765564346"

  q := arangolite.NewQuery(`
    FOR n
    IN nodes
    FILTER n._key == %s
    RETURN n
  `, key).Cache(true).BatchSize(500) // The caching feature is unavailable prior to ArangoDB 2.7

  // The Run method returns all the query results of every batches
  // available in the cursor as a slice of byte.
  r, _ := db.Run(q)

  nodes := []Node{}
  json.Unmarshal(r, &nodes)

  // The RunAsync method returns a Result struct allowing to handle batches as they
  // are retrieved from the database.
  async, _ := db.RunAsync(q)

  nodes = []Node{}
  decoder := json.NewDecoder(async.Buffer())

  for async.HasMore() {
    batch := []Node{}
    decoder.Decode(&batch)
    nodes = append(nodes, batch...)
  }

  fmt.Printf("%v", nodes)
}

// OUTPUT EXAMPLE:
// [
//   {
//     "_id": "nodes/48765564346",
//     "_rev": "48765564346",
//     "_key": "48765564346"
//   }
// ]

Document and Edge

// Document represents a basic ArangoDB document
// Fields are pointers to allow null values in ArangoDB
type Document struct {
  ID  *string `json:"_id,omitempty"`
  Rev *string `json:"_rev,omitempty"`
  Key *string `json:"_key,omitempty"`
}

// Edge represents a basic ArangoDB edge
// Fields are pointers to allow null values in ArangoDB
type Edge struct {
  Document
  From *string `json:"_from,omitempty"`
  To   *string `json:"_to,omitempty"`
}

Transactions

Overview

Arangolite provides an abstraction layer to the Javascript ArangoDB transactions.

The only limitation is that no Javascript processing can be manually added inside the transaction. The queries can only be connected in a raw way, using the Go templating conventions.

Usage
func main() {
  db := arangolite.New()
  db.Connect("http://localhost:8000", "testDB", "user", "password")

  t := arangolite.NewTransaction([]string{"nodes"}, nil).
  AddQuery("nodes", `
    FOR n
    IN nodes
    RETURN n
  `).AddQuery("ids", `
    FOR n
    IN {{.nodes}}
    RETURN n._id
  `).Return("ids")

  r, _ := db.Run(t)

  ids := []string{}
  json.Unmarshal(r, &ids)

  fmt.Printf("%v", ids)
}

Graphs

Overview

AQL may be used for querying graph data. But to manage graphs, aroangolite offers a few specific requests:

  • CreateGraph to create a graph
  • ListGraphs to list available graphs
  • GetGraph to get an existing graph
  • DropGraph to delete a graph
Usage
  db.Run(&arangolite.CreateCollection{Name: "CollectionName"})
  db.Run(&arangolite.CreateCollection{Name: "RelationshipCollectionName", Type: 3})

  // check graph existence
  _, err := db.Run(&arangolite.GetGraph{Name: "GraphName"})

  // if graph does not exist, create a new one
  if err != nil {
    from := make([]string, 1)
    from[0] = "FirstCollectionName"
    to := make([]string, 1)
    to[0] = "SecondCollectionName"

    edgeDefinition := arangolite.EdgeDefinition{Collection: "EdgeCollectionName", From: from, To: to}
    edgeDefinitions := make([]arangolite.EdgeDefinition, 1)
    edgeDefinitions[0] = edgeDefinition
    db.Run(&arangolite.CreateGraph{Name: "GraphName", EdgeDefinitions: edgeDefinitions})
  }

  // grab the graph
  graphBytes, _ := config.DB().Run(&arangolite.GetGraph{Name: "GraphName"})
  graph := &arangolite.GraphData{}
  json.Unmarshal(graphBytes, &graph)
  fmt.Printf("Graph: %+v", graph)

  // list existing graphs
  listBytes, _ :=  db.Run(&arangolite.ListGraphs{})
  list := &arangolite.GraphList{}
  json.Unmarshal(listBytes, &list)
  fmt.Printf("Graph list: %+v", list)

  // destroy the graph we just created, and the related collections
  db.Run(&arangolite.DropGraph{Name: "GraphName", DropCollections: true})

Filters

Overview

In a similar way than in LoopBack, the filtering system is API client oriented.

Its goal is to provide an easy way of converting JSON filters passed through query strings into an actual AQL query:

// Filter defines a way of filtering AQL queries.
type Filter struct {
  Offset  int                      `json:"offset"`
  Limit   int                      `json:"limit"`
  Sort    []string                 `json:"sort"`
  Where   []map[string]interface{} `json:"where"`
  Options []string                 `json:"options"`
}
Options Field

The Options field implementation is left to the developer. It is not translated into AQL during the filtering.

Its main goal is to allow a filtering similar to the Include one in traditional ORMs, as a relation can be a join or a edge in ArangoDB.

Of course, the Options field can also be used as a more generic option selector (e.g., Options: "Basic" to only return the basic info about a resource).

Translation example

JSON:

{
  "offset": 1,
  "limit": 2,
  "sort": ["age desc", "money"],
  "where": [
    {"firstName": "Pierre"},
    {
      "or": [
        {"birthPlace": ["Paris", "Los Angeles"]},
        {"age": {"gte": 18}}
      ]
    },
    {
      "like": {
        "text": "lastName",
        "search": "R%",
        "case_insensitive": true
      }
    }
  ]
  },
  "options": ["details"]
}

AQL:

LIMIT 1, 2
SORT var.age DESC, var.money ASC
FILTER var.firstName == 'Pierre' && (var.birthPlace IN ['Paris', 'Los Angeles'] || var.age >= 18) && LIKE(var.lastName, 'R%', true)
Operators
  • and: Logical AND operator.
  • or: Logical OR operator.
  • not: Logical NOT operator.
  • gt, gte: Numerical greater than (>); greater than or equal (>=).
  • lt, lte: Numerical less than (<); less than or equal (<=).
  • eq, neq: Equal (==); non equal (!=).
  • like: LIKE(text, search, case_insensitive) function support
Usage
func main() {
  db := arangolite.New(true)
  db.Connect("http://localhost:8000", "testDB", "user", "password")

  filter, err := filters.FromJSON(`{"limit": 2}`)
  if err != nil {
    panic(err)
  }

  aqlFilter, err := filters.ToAQL("n", filter)
  if err != nil {
    panic(err)
  }

  r, _ := db.Run(arangolite.NewQuery(`
    FOR n
    IN nodes
    %s
    RETURN n
  `, aqlFilter))

  nodes := []Node{}
  json.Unmarshal(r, &nodes)

  fmt.Printf("%v", nodes)
}

// OUTPUT EXAMPLE:
// [
//   {
//     "_id": "nodes/47473545749",
//     "_rev": "47473545749",
//     "_key": "47473545749"
//   },
//   {
//     "_id": "nodes/47472824853",
//     "_rev": "47472824853",
//     "_key": "47472824853"
//   }
// ]

Contributing

Currently, very few methods of the ArangoDB HTTP API are implemented in Arangolite. Fortunately, it is really easy to add your own. There are two ways:

  • Using the Send method, passing a struct representing the request you want to send.
func (db *DB) Send(description, method, path string, req interface{}) ([]byte, error) {}
  • Implementing the Runnable interface. You can then use the regular Run method.
// Runnable defines requests runnable by the Run and RunAsync methods.
// Queries, transactions and everything in the requests.go file are Runnable.
type Runnable interface {
	Description() string // Description shown in the logger
	Generate() []byte // The body of the request
	Path() string // The path where to send the request
	Method() string // The HTTP method to use
}

Please pull request when you implement some new features so everybody can use it.

License

MIT

Documentation

Overview

Package arangolite provides a lightweight ArangoDatabase driver.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func HasErrorNum

func HasErrorNum(err error, errorNum ...int) bool

HasErrorNum returns true when one of the given error num matches the one returned by the database.

func HasStatusCode

func HasStatusCode(err error, statusCode ...int) bool

HasStatusCode returns true when one of the given error status code matches the one returned by the database.

func IsErrForbidden

func IsErrForbidden(err error) bool

IsErrForbidden returns true when the database returns a 403.

func IsErrInvalidRequest

func IsErrInvalidRequest(err error) bool

IsErrInvalidRequest returns true when the database returns a 400.

func IsErrNotFound

func IsErrNotFound(err error) bool

IsErrNotFound returns true when the database returns a 404 or when the error num is: 1202 - ERROR_ARANGO_DOCUMENT_NOT_FOUND 1203 - ERROR_ARANGO_COLLECTION_NOT_FOUND

func IsErrUnauthorized

func IsErrUnauthorized(err error) bool

IsErrUnauthorized returns true when the database returns a 401.

func IsErrUnique

func IsErrUnique(err error) bool

IsErrUnique returns true when the error num is a 1210 - ERROR_ARANGO_UNIQUE_CONSTRAINT_VIOLATED.

Types

type Database

type Database struct {
	// contains filtered or unexported fields
}

Database represents an access to an ArangoDB database.

func NewDatabase

func NewDatabase(opts ...Option) *Database

NewDatabase returns a new Database object.

func (*Database) Options

func (db *Database) Options(opts ...Option)

Options apply options to the database.

func (*Database) Run

func (db *Database) Run(ctx context.Context, q Runnable, v interface{}) error

Run runs the Runnable, follows the query cursor if any and unmarshal the result in the given object.

func (*Database) Send

func (db *Database) Send(ctx context.Context, q Runnable) (Response, error)

Send runs the Runnable and returns a "raw" Response object.

type Document

type Document struct {
	// The document handle. Format: ':collection/:key'
	ID string `json:"_id,omitempty"`
	// The document's revision token. Changes at each update.
	Rev string `json:"_rev,omitempty"`
	// The document's unique key.
	Key string `json:"_key,omitempty"`
}

Document represents a basic ArangoDB document Fields are pointers to allow null values in ArangoDB

type Edge

type Edge struct {
	Document
	// Reference to another document. Format: ':collection/:key'
	From string `json:"_from,omitempty"`
	// Reference to another document. Format: ':collection/:key'
	To string `json:"_to,omitempty"`
}

Edge represents a basic ArangoDB edge Fields are pointers to allow null values in ArangoDB

type LogVerbosity

type LogVerbosity int

LogVerbosity is the logging verbosity.

const (
	// LogSummary prints a simple summary of the exchanges with the database.
	LogSummary LogVerbosity = iota
	// LogDebug prints all the sent and received http requests.
	LogDebug
)

type Option

type Option func(db *Database)

Option sets an option for the database connection.

func OptBasicAuth

func OptBasicAuth(username, password string) Option

OptBasicAuth sets the username and password used to access the database using basic authentication.

func OptDatabaseName

func OptDatabaseName(dbName string) Option

OptDatabaseName sets the name of the targeted database.

func OptEndpoint

func OptEndpoint(endpoint string) Option

OptEndpoint sets the endpoint used to access the database.

func OptHTTPClient

func OptHTTPClient(cli *http.Client) Option

OptHTTPClient sets the HTTP client used to interact with the database. It is also the current solution to set a custom TLS config.

func OptJWTAuth

func OptJWTAuth(username, password string) Option

OptJWTAuth sets the username and password used to access the database using JWT authentication.

func OptLogging

func OptLogging(logger *log.Logger, verbosity LogVerbosity) Option

OptLogging enables logging of the exchanges with the database.

type Response

type Response interface {
	// The raw response from the database.
	Raw() json.RawMessage
	// The raw response result, if present.
	RawResult() json.RawMessage
	// The response HTTP status code.
	StatusCode() int
	// HasMore indicates if a next result page is available.
	HasMore() bool
	// The cursor ID if more result pages are available.
	Cursor() string
	// Unmarshal decodes the response into the given object.
	Unmarshal(v interface{}) error
	// UnmarshalResult decodes the value of the Result field into the given object, if present.
	UnmarshalResult(v interface{}) error
}

Response defines the response returned by the execution of a Runnable.

type Runnable added in v1.2.0

type Runnable interface {
	// The body of the request.
	Generate() []byte
	// The path where to send the request.
	Path() string
	// The HTTP method to use.
	Method() string
}

Runnable defines requests runnable by the Run and Send methods. A Runnable library is located in the 'requests' package.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL