thingsdb

package module
v1.0.6 Latest Latest
Warning

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

Go to latest
Published: Feb 15, 2024 License: MIT Imports: 10 Imported by: 1

README

CI Release Version

Go connector for ThingsDB

Go ThingsDB



Installation

Simple install the package to your $GOPATH with the go tool from shell:

At least Go version 1.12 is required.

$ go get github.com/thingsdb/go-thingsdb

Make sure Git is installed on your machine and in your system's PATH.

Quick usage

This is a fully working example of how the Go ThingsDB connector can be used. It might seem a lot of code but this is mostly because comments are added to explain what each line does.

package main

import (
	"fmt"

	"github.com/thingsdb/go-thingsdb"
)

func example(conn *thingsdb.Conn, ok chan bool) {
	defer func(ok chan bool) { ok <- true }(ok)

	// Create the connection, note that after calling connect you still need
	// too authenticate the connection.
	if err := conn.Connect(); err != nil {
		fmt.Println(err)
		return
	}

	// Make sure the connection will be closed at the end of this function
	defer conn.Close()

	// Here we use a username anf password to authenticate. It is also possible
	// to use a token with conn.AuthToken("my-secret-token").
	if err := conn.AuthPassword("admin", "pass"); err != nil {
		fmt.Println(err)
		return
	}

	// Arguments are optional, if no arguments are required, vars may be `nil`
	vars := map[string]interface{}{
		"a": 6,
		"b": 7,
	}

	// Just some example query using collection `stuff`
	if res, err := conn.Query("//stuff", "a * b;", vars); err == nil {
		fmt.Println(res) // Should print 42, as 6 * 7 = 42
	} else {
		fmt.Println(err)
	}
}

func main() {
	// Optionally, the `NewConn` function accepts TLS (SSL) configuration, for example:
	//
	//   config := &tls.Config{InsecureSkipVerify: false}
    //
	conn := thingsdb.NewConn("localhost", 9200, nil)

	// With conn.AddNode(..) it is possible to add more than one node.
	// This will be used when a re-connect is triggered to ensure that the
	// client quickly finds a new node to connect too. Adding another node is
	// not useful when using a single ThingsDB node or when using a thingsdb
	// service which handles node distribution.

	ok := make(chan bool)
	go example(conn, ok)
	<-ok // Just to make sure the example code is completed before exit
}

Conn

Type Conn is used as the ThingsDB Connection Client.

There are a few public properties which may be used:

Key Type Default Description
DefaultTimeout time.Duration 0 Default time-out used when querying ThingsDB. When 0, no time-out is used.
AutoReconnect bool true When true, the connection will try to re-connect when a connection is lost.
ReconnectionAttempts int 0 Maximum number of re-connect attempts. When 0, re-connect will try forever.
PingInterval time.Duration 30s Keep-alive ping interval. When 0, keep-alive will be disabled.
LogCh chan string nil Forward logging to this channel. When nil, log will be send to log.Println(..).
LogLevel LogLevelType LogWarning Log level. Available levels: LogDebug, LogInfo, LogWarning and LogError.
OnNodeStatus func(*NodeStatus) nil Called when a new node status is received. If implemented, the callback will be called before the client will handle the new status.
OnWarning func(*WarnEvent) nil Called when a warning is received from ThingsDB. If not implemented (nil), the client will log a warning.

Example:

conn := thingsdb.NewConn("localhost", 9200, nil)

// Change the log level
conn.LogLevel = thingsdb.LogInfo
NewConn

Call NewConn to create a new ThingsDB Connection.

Example:

thingsdb.NewConn("localhost", 9200, nil)

Or, with TLS (SSL) config enabled

config := &tls.Config{
    InsecureSkipVerify: false,
}
thingsdb.NewConn("localhost", 9200, config)
AddNode

Add another node to the connection object. The connection will switch between available nodes when a re-connect is triggered. This ensures that the connection will be available very quickly after a ThingsDB node is restarted.

TLS configuration will be shared between all nodes. Thus, it is not possible to enable TLS config (or a different) for a single node.

Note: It is useless to add another node when using only a single ThingsDB node, or when using a single thingsdb service, for example in Kubernetes.

Example:

conn := thingsdb.NewConn("node1.local", 9200, nil)
conn.AddNode("node2.local", 9200)
conn.AddNode("node3.local", 9200)
ToString

Prints the current node address used by the connection.

Example:

thingsdb.NewConn("localhost", 9200, nil).ToString()  // "localhost:9200"
Connect

Connect creates the TCP connection to the node.

AuthPassword

AuthPassword can be used to authenticate a connection using a username and password.

AuthToken

AuthToken can be used to authenticate a connection using a token.

IsConnected

IsConnected returns true when the connector is connected to ThingsDB, false if not.

Note: this function does not care is the connection is authenticated

Query

Query ThingsDB using code.

Example:

if res, err := conn.Query("/t", "'Hello Go Connector for ThingsDB!!';", nil); err == nil {
    fmt.Println(res)  // "Hello Go Connector for ThingsDB!!"
}

Arguments can be provided using a map[string]interface{}, for example:

vars := map[string]interface{}{
    "name": "Alice",
}

if res, err := conn.Query("/t", "`Hello {name}!!`;", vars); err == nil {
    fmt.Println(res) // "Hello Alice!!"
}
QueryRaw

The same as Query, except a raw []byte array is returned.

Run

Run a procedure in ThingsDB. Arguments are optional and may be either positional []interface{} or by map map[string]interface{}.

Example without arguments:

// Suppose collection `stuff` has the following procedure:
//
//   new_procedure('greet', || 'Hi');
//
if res, err := conn.Run("//stuff", "greet", nil); err == nil {
    fmt.Println(res)  // "Hi"
}

Example using positional arguments:

// Suppose collection `stuff` has the following procedure:
//
//   new_procedure('subtract', |a, b| a - b);
//
args := []interface{}{40, 10}

if res, err := conn.Run("//stuff", "subtract", args); err == nil {
    fmt.Println(res)  // 30
}

Example using mapped arguments:

// Suppose collection `stuff` has the following procedure:
//
//   new_procedure('subtract', |a, b| a - b);
//
vars := map[string]interface{}{
    "a": 15,
    "b": 5,
}
if res, err := conn.Run("//stuff", "subtract", vars); err == nil {
    fmt.Println(res)  // 10
}
RunRaw

The same as Run, except a raw []byte array is returned.

Emit-Conn

Emit an even to a room. If a Room is created for the given roomId, you probable want to use Emit on the Room type.

Example:

args := []interface{}{"This is a message"}

err := conn.Emit(
    "//stuff",      // scope of the Room
    123,            // Room Id
    "new-message",  // Event to emit
    args            // Arguments (may be nil)
);
Close

Close an open connection.

Warning: After calling Close(), the conn.AutoReconnect property will be set to false. Thus, if you later want to Connect() again, make sure to re-enable this property manually.

Room

Room type can be used to join a ThingsDB room.

Key Type Default Description
OnInit func(*Room) dummy func Called only once at the first join. This function will run before the OnJoin.
OnJoin func(*Room) dummy func Called at each join, thus also after a re-connect.
OnLeave func(*Room) dummy func Called only when a room is explicitly left (A call to room.Leave()).
OnDelete func(*Room) dummy func Called when the room is removed from ThingsDB.
OnEmit func(*Room, event string, args []interface{}) dummy func Called only when no event handler is configured for the event.
Data interface{} nil Free to use, for example to assign additional data to the room (Data stays untouched by the connector).

Example configuring the OnInit and OnJoin functions:

func onInit(room *thingsdb.Room) {
	fmt.Printf("Initializing Room Id %d\n", room.Id())
    // Do some initialization... (called only once)
}

func onJoin(room *thingsdb.Room) {
	fmt.Printf("Joined Room Id %d\n", room.Id())
    // Do some stuff, maybe query ThingsDB... (called after each join, thus again after a re-connect)
}

// Suppose we have a chat room in collection stuff...
room := thingsdb.NewRoom("//stuff", ".chatRoom.id();")
room.OnInit = onInit
room.OnJoin = onJoin
NewRoom

NewRoom creates a new room using code. The code should return the room Id for the room.

Example:

// Suppose Collection stuff has a room (.room)
room := thingsdb.NewRoom("//stuff", ".room.id();")
NewRoomFromId

NewRoomFromId creates a new room using a room Id.

If the room Id unknown, you may want use NewRoom(..) to get the Id for the room by code.

Example:

// Suppose Collection stuff has a room with Id 17
room := thingsdb.NewRoomFromId("//stuff", 17)
Id

Id returns the Id of the room.

Note: Id() returns 0 when the room was created using NewRoom(..) and the room has never been joined.

Scope

Scope returns the Room Scope

HandleEvent

HandleEvent adds an event handler to the room.

Example:

func onNewMessage(room *thingsdb.Room, args []interface{}) {
	if len(args) != 1 {
		fmt.Println("Invalid number of arguments")
		return
	}

	msg, ok := args[0].(string)
	if !ok {
		fmt.Println("Expecting argument 1 to be of type string")
		return
	}

	fmt.Println(msg)  // Just print the message
}

room = thingsdb.NewRoom("//stuff", ".chatRoom.id();")

// Add event handler for the "new-message" event.
room.HandleEvent("new-message", onNewMessage)

// Note: The (optional) `OnEmit` handler will no longer be called for `new-message` events.
Join

Join must be called to actually join the room.

The wait argument may be set to 0 to tell the room not to wait for the join to complete. If wait is set to any other positive value, then both the OnInit and OnJoin are called (in this order) before the call to Join returns unless the OnJoin is not completed before the wait duration (an error will be returned).

Example:

err := room.Join(conn, thingsdb.DefaultWait)
Leave

Leave will stop listening for events on a room.

Example:

err := room.Leave()
Emit

Emit an even to a room.

Example:

args := []interface{}{"Just some chat message"}

err := room.Emit(
    "new-message",  // Event to emit
    args            // Arguments (may be nil)
);

Documentation

Index

Constants

View Source
const DefaultWait = 60 * time.Second

DefaultWait can be used as default time to wait for a Join

View Source
const PkgInitCapacity = 8192

PkgInitCapacity will be used as default capacity when allocating for a package.

View Source
const Version = "1.0.6"

Version exposes the go-thingsdb version

Variables

This section is empty.

Functions

This section is empty.

Types

type Conn

type Conn struct {

	// Public
	DefaultTimeout       time.Duration
	AutoReconnect        bool
	ReconnectionAttempts int
	PingInterval         time.Duration
	LogCh                chan string
	LogLevel             LogLevelType
	OnNodeStatus         func(ns *NodeStatus)
	OnWarning            func(we *WarnEvent)
	// contains filtered or unexported fields
}

Conn is a ThingsDB connection to a single node.

func NewConn

func NewConn(host string, port uint16, config *tls.Config) *Conn

NewConn creates a new ThingsDB Connector.

Example:

thingsdb.NewConn("localhost", 9200, nil)

Or, with TLS (SSL) config enabled

config := &tls.Config{
    InsecureSkipVerify: false,
}
thingsdb.NewConn("localhost", 9200, config)

func (*Conn) AddNode added in v1.0.0

func (conn *Conn) AddNode(host string, port uint16)

The connection will switch between available nodes when a re-connect is triggered. This ensures that the connection will be available very quickly after a ThingsDB node is restarted.

TLS configuration will be shared between all nodes. Thus, it is not possible to enable TLS config (or a different) for a single node.

> Note: It is useless to add another node when using only a single ThingsDB node, or when using a single thingsdb service, for example in Kubernetes.

Example:

conn := thingsdb.NewConn("node1.local", 9200, nil)
conn.AddNode("node2.local", 9200)
conn.AddNode("node3.local", 9200)

func (*Conn) AuthPassword

func (conn *Conn) AuthPassword(username, password string) error

AuthPassword can be used to authenticate a connection using a username and password.

func (*Conn) AuthToken

func (conn *Conn) AuthToken(token string) error

AuthToken can be used to authenticate a connection using a token.

func (*Conn) Close

func (conn *Conn) Close()

Close an open connection.

> Warning: After calling Close(), the `conn.AutoReconnect` property will be set to `false`. Thus, if you later want to `Connect()` again, make sure to re-enable this property manually.

func (*Conn) Connect

func (conn *Conn) Connect() error

Connect creates the TCP connection to the node.

func (*Conn) Emit added in v1.0.0

func (conn *Conn) Emit(scope string, roomId uint64, event string, args []interface{}) error

Emit an even to a room.

If a `Room` is created for the given `roomId`, you probable want to use Emit(..) on the `Room` type.

Example:

args := []interface{}{"This is a message"}

err := conn.Emit(
    "//stuff",      // scope of the Room
    123,            // Room Id
    "new-message",  // Event to emit
    args            // Arguments (may be nil)
);

func (*Conn) IsConnected

func (conn *Conn) IsConnected() bool

IsConnected returns `true` when the connector is connected to ThingsDB, `false` if not.

> Note: this function does not care is the connection is authenticated

func (*Conn) Query

func (conn *Conn) Query(scope string, code string, vars map[string]interface{}) (interface{}, error)

Query ThingsDB using code.

Example:

if res, err := conn.Query("/t", "'Hello Go Connector for ThingsDB!!';", nil); err == nil {
    fmt.Println(res)  // "Hello Go Connector for ThingsDB!!"
}

Arguments can be provided using a `map[string]interface{}`, for example:

vars := map[string]interface{}{
    "name": "Alice",
}

if res, err := conn.Query("/t", "`Hello {name}!!`;", vars); err == nil {
    fmt.Println(res) // "Hello Alice!!"
}

func (*Conn) QueryRaw added in v1.0.6

func (conn *Conn) QueryRaw(scope string, code string, vars map[string]interface{}) ([]byte, error)

QueryRaw is like Query except a raw []byte array is returned

func (*Conn) Run

func (conn *Conn) Run(scope string, procedure string, args interface{}) (interface{}, error)

Run a procedure in ThingsDB. Arguments are optional and may be either positional `[]interface{}` or by map `map[string]interface{}`.

Example without arguments:

// Suppose collection `stuff` has the following procedure:
// new_procedure('greet', || 'Hi');

if res, err := conn.Run("//stuff", "greet", nil); err == nil {
    fmt.Println(res)  // "Hi"
}

Example using positional arguments:

// Suppose collection `stuff` has the following procedure:
// new_procedure('subtract', |a, b| a - b);

args := []interface{}{40, 10}

if res, err := conn.Run("//stuff", "subtract", args); err == nil {
    fmt.Println(res)  // 30
}

Example using mapped arguments:

// Suppose collection `stuff` has the following procedure:
// new_procedure('subtract', |a, b| a - b);

args := map[string]interface{}{
    "a": 15,
    "b": 5,
}
if res, err := conn.Run("//stuff", "subtract", args); err == nil {
    fmt.Println(res)  // 10
}

```

func (*Conn) RunRaw added in v1.0.6

func (conn *Conn) RunRaw(scope string, procedure string, args interface{}) ([]byte, error)

RunRaw is like Query except a raw []byte array is returned

func (*Conn) ToString

func (conn *Conn) ToString() string

ToString prints the current node address used by the connection.

Example:

thingsdb.NewConn("localhost", 9200, nil).ToString()  // "localhost:9200"

type ErrorCode

type ErrorCode int

ErrorCode known by ThingsDB

const (
	// UnpackError - invalid qpack data when create a new Error
	UnpackError ErrorCode = ErrorCode(-200)

	// CancelledError - operation is cancelled before completion
	CancelledError ErrorCode = ErrorCode(-64)
	// OperationError - operation is not valid in the current context
	OperationError ErrorCode = ErrorCode(-63)
	// NumArgumentsError - wrong number of arguments
	NumArgumentsError ErrorCode = ErrorCode(-62)
	// TypeError - object of inappropriate type
	TypeError ErrorCode = ErrorCode(-61)
	// ValueError - object has the right type but an inappropriate value
	ValueError ErrorCode = ErrorCode(-60)
	// OverflowError - interger overflow
	OverflowError ErrorCode = ErrorCode(-59)
	// ZeroDivError - division or module by zero
	ZeroDivError ErrorCode = ErrorCode(-58)
	// MaxQuotaError - max quota is reached
	MaxQuotaError ErrorCode = ErrorCode(-57)
	// AuthError - authentication error
	AuthError ErrorCode = ErrorCode(-56)
	// ForbiddenError - forbidden (access denied)
	ForbiddenError ErrorCode = ErrorCode(-55)
	// LookupError - requested resource not found
	LookupError ErrorCode = ErrorCode(-54)
	// BadRequestError - unable to handle request due to invalid data
	BadRequestError ErrorCode = ErrorCode(-53)
	// SyntaxError - syntax error in query
	SyntaxError ErrorCode = ErrorCode(-52)
	// NodeError - node is temporary unable to handle the request
	NodeError ErrorCode = ErrorCode(-51)
	// AssertionError - assertion statement has failed
	AssertionError ErrorCode = ErrorCode(-50)

	// ResultTooLargeError - result too large
	ResultTooLargeError = ErrorCode(-6)
	// RequestTimeoutError - request timed out
	RequestTimeoutError = ErrorCode(-5)
	// RequestCancelError - request is cancelled
	RequestCancelError = ErrorCode(-4)
	// WriteUVError - cannot write to socket
	WriteUVError ErrorCode = ErrorCode(-3)
	// MemoryError - memory allocation error
	MemoryError ErrorCode = ErrorCode(-2)
	// InternalError - internal error
	InternalError ErrorCode = ErrorCode(-1)
)

type LogLevelType added in v1.0.0

type LogLevelType int8
const (
	// Debug for debug logging
	LogDebug LogLevelType = 0
	// Info level
	LogInfo LogLevelType = 1
	// Warning level
	LogWarning LogLevelType = 2
	// Error level
	LogError LogLevelType = 3
)

type NodeStatus added in v1.0.0

type NodeStatus struct {
	Id     uint32 `msgpack:"id"`
	Status string `msgpack:"status"`
}

NodeStatus is used for the node status event

type Proto

type Proto int8

Proto is used as protocol type used by ThingsDB.

const (

	// ProtoOnNodeStatus the connected node has changed it's status
	ProtoOnNodeStatus Proto = 0

	// ProtoOnWarn warning message for the connected client
	ProtoOnWarn Proto = 5

	// ProtoOnRoomJoin initial join
	ProtoOnRoomJoin Proto = 6

	// ProtoOnRoomLeave leave join
	ProtoOnRoomLeave Proto = 7

	// ProtoOnRoomEvent emit event
	ProtoOnRoomEvent Proto = 8

	// ProtoOnRoomDelete room removed from ThingsDB
	ProtoOnRoomDelete Proto = 9

	// ProtoResPong responds with `nil`
	ProtoResPong Proto = 16
	// ProtoResOk responds with `nil`
	ProtoResOk Proto = 17
	// ProtoResData responds with `...`
	ProtoResData Proto = 18
	// ProtoResError responds with `{error_msg:..., error_code:,...}`
	ProtoResError Proto = 19

	// ProtoReqPing requires `nil`
	ProtoReqPing Proto = 32
	// ProtoReqAuth requires `[username, password]`
	ProtoReqAuth Proto = 33
	// ProtoReqQuery requires `[scope, query [, variable]]`
	ProtoReqQuery Proto = 34
	// ProtoReqRun requires `[scope, procedure[, arguments]]`
	ProtoReqRun Proto = 37
	// ProtoReqJoin requires `[scope, room ids...]`
	ProtoReqJoin Proto = 38
	// ProtoReqLeave requires `[scope, room ids...]`
	ProtoReqLeave Proto = 39
	// ProtoReqEmit requires `[scope, roomId, event, arguments...]`
	ProtoReqEmit Proto = 40
)

type Room added in v1.0.0

type Room struct {

	// Public
	// Note: OnEmit will *only* be called when no event handler for the given
	//       even is implemented.
	OnInit   func(room *Room)
	OnJoin   func(room *Room)
	OnLeave  func(room *Room)
	OnDelete func(room *Room)
	OnEmit   func(room *Room, event string, args []interface{})
	Data     interface{}
	// contains filtered or unexported fields
}

Room type can be used to join a ThingsDB room

func NewRoom added in v1.0.0

func NewRoom(scope string, code string) *Room

NewRoom creates a new room using code. The code should return the room Id for the room.

Example:

// Suppose Collection stuff has a room (.room)
room := thingsdb.NewRoom("//stuff", ".room.id();")

func NewRoomFromId added in v1.0.0

func NewRoomFromId(scope string, id uint64) *Room

NewRoomFromId creates a new room using a room Id.

If the room Id unknown, you may want use `NewRoom(..)` to get the Id for the room by code.

Example:

// Suppose Collection stuff has a room with Id 17
room := thingsdb.NewRoomFromId("//stuff", 17)

func (*Room) Emit added in v1.0.0

func (room *Room) Emit(event string, args []interface{}) error

Emit an even to a room.

Example:

args := []interface{}{"Just some chat message"}

err := room.Emit(
    "new-message",  // Event to emit
    args            // Arguments (may be nil)
);

func (*Room) HandleEvent added in v1.0.0

func (room *Room) HandleEvent(event string, handle func(room *Room, args []interface{}))

HandleEvent adds an event handler to the room.

Example:

func onNewMessage(room *thingsdb.Room, args []interface{}) {
    if len(args) != 1 {
       fmt.Println("Invalid number of arguments")
       return
    }

    msg, ok := args[0].(string)
    if !ok {
       fmt.Println("Expecting argument 1 to be of type string")
       return
    }

    fmt.Println(msg)  // Just print the message
}

room = thingsdb.NewRoom("//stuff", ".chatRoom.id();")

// Add event handler for the "new-message" event
room.HandleEvent("new-message", onNewMessage)

func (*Room) Id added in v1.0.0

func (room *Room) Id() uint64

Id returns the Id of the room.

> Note: Id() returns `0` when the room was created using `NewRoom(..)` and the room has never been joined.

func (*Room) Join added in v1.0.0

func (room *Room) Join(conn *Conn, wait time.Duration) error

Join must be called to actually join the room.

The `wait` argument may be set to `0` to tell the room not to wait for the join to complete. If `wait` is set to any other positive value, then both the `OnInit` and `OnJoin` are called (in this order) before the call to Join returns unless the `OnJoin` is not completed before the `wait` duration (an error will be returned).

Example:

err := room.Join(conn, thingsdb.DefaultWait)

func (*Room) Leave added in v1.0.0

func (room *Room) Leave() error

Leave will stop listening for events on a room.

Example:

err := room.Leave()

func (*Room) Scope added in v1.0.0

func (room *Room) Scope() string

Scope returns the Room Scope

type TiError added in v1.0.0

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

TiError can be returned by the ThingsDB package.

func NewTiError added in v1.0.0

func NewTiError(msg string, code ErrorCode) *TiError

NewError returns a pointer to a new Error.

func NewTiErrorFromByte added in v1.0.0

func NewTiErrorFromByte(b []byte) *TiError

NewTiErrorFromByte returns a pointer to a new Error from msgpack byte data.

func (*TiError) Code added in v1.0.0

func (e *TiError) Code() ErrorCode

Code returns the error type.

func (*TiError) Error added in v1.0.0

func (e *TiError) Error() string

Error returns the error msg.

type WarnEvent added in v1.0.0

type WarnEvent struct {
	Msg  string `msgpack:"warn_msg"`
	Code uint16 `msgpack:"warn_code"`
}

WarnEvent is receveid when a warning is raised by ThingsDB

Jump to

Keyboard shortcuts

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