Skip to content

Commit

Permalink
Update v2.
Browse files Browse the repository at this point in the history
  • Loading branch information
s-yata committed May 12, 2017
1 parent 09787f8 commit 09c35bc
Show file tree
Hide file tree
Showing 8 changed files with 325 additions and 7 deletions.
12 changes: 8 additions & 4 deletions v2/argument.go
Expand Up @@ -5,7 +5,11 @@ import (
"fmt"
)

// Argument represents a named argument of a request.
// Argument stores a command argument.
//
// If Key != "", it is a named argument.
// Otherwise, it is an unnamed argument.
// Note that the order of unnamed arguments is important.
type Argument struct {
Key string
Value string
Expand Down Expand Up @@ -36,7 +40,7 @@ func checkArgumentKey(s string) error {
continue
}
switch s[i] {
case '#', '@', '-', '_':
case '#', '@', '-', '_', '.', '[', ']':
default:
return fmt.Errorf("invalid format: s = %s", s)
}
Expand All @@ -45,8 +49,8 @@ func checkArgumentKey(s string) error {
}

// Check checks if arg is valid.
func (arg *Argument) Check() error {
if err := checkArgumentKey(arg.Key); err != nil {
func (a *Argument) Check() error {
if err := checkArgumentKey(a.Key); err != nil {
return fmt.Errorf("checkArgumentKey failed: %v", err)
}
return nil
Expand Down
42 changes: 42 additions & 0 deletions v2/error.go
@@ -0,0 +1,42 @@
package grnci

import "encoding/json"

// Code is an error code.
type Code int

// List of error codes.
const (
CodeSuccess = Code(0)
)

// String returns a string which briefly describes an error.
func (c Code) String() string {
switch c {
case CodeSuccess:
return "Success"
default:
return "Unknown error"
}
}

// Error stores details of an error.
type Error struct {
Code Code
Message string
Data interface{}
}

// NewError creates an error object.
func NewError(code Code, data interface{}) error {
return &Error{
Code: code,
Data: data,
}
}

// Error returns a string which describes an error.
func (e Error) Error() string {
b, _ := json.Marshal(e)
return string(b)
}
4 changes: 4 additions & 0 deletions v2/gqtp.go
Expand Up @@ -34,6 +34,10 @@ func (c *gqtpClient) Close() error {

// Query sends a request and receives a response.
func (c *gqtpClient) Query(req *Request) (*Response, error) {
if err := req.Check(); err != nil {
return nil, err
}

// TODO
return nil, nil
}
38 changes: 36 additions & 2 deletions v2/http.go
Expand Up @@ -2,8 +2,10 @@ package grnci

import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
"path"
)

// httpClient is an HTTP client.
Expand Down Expand Up @@ -34,6 +36,38 @@ func (c *httpClient) Close() error {

// Query sends a request and receives a response.
func (c *httpClient) Query(req *Request) (*Response, error) {
// TODO
return nil, nil
if err := req.Check(); err != nil {
return nil, err
}

u := *c.url
u.Path = path.Join(u.Path, req.Cmd)
if len(req.Args) != 0 {
q := u.Query()
for _, arg := range req.Args {
q.Set(arg.Key, arg.Value)
}
u.RawQuery = q.Encode()
}
addr := u.String()

var resp *http.Response
var err error
if req.Body == nil {
if resp, err = c.client.Get(addr); err != nil {
return nil, fmt.Errorf("c.client.Get failed: %v", err)
}
} else {
if resp, err = c.client.Post(addr, "application/json", req.Body); err != nil {
return nil, fmt.Errorf("c.client.Post failed: %v", err)
}
}
defer resp.Body.Close()

// TODO: parse the response.
respBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("ioutil.ReadAll failed: %v", err)
}
return &Response{Bytes: respBytes}, nil
}
37 changes: 37 additions & 0 deletions v2/libgrn/client.go
@@ -0,0 +1,37 @@
package libgrn

// #cgo pkg-config: groonga
// #include <groonga.h>
// #include <stdlib.h>
import "C"
import "github.com/groonga/grnci/v2"

// Client is associated with a C.grn_ctx.
type Client struct {
ctx *grnCtx
}

// Open opens a local Groonga DB.
func Open(path string) (*grnci.Client, error) {
return nil, nil
}

// Create creates a local Groonga DB.
func Create(path string) (*grnci.Client, error) {
return nil, nil
}

// Connect establishes a connection with a GQTP server.
func Connect(addr string) (*grnci.Client, error) {
return nil, nil
}

// Close closes a client.
func (c *Client) Close() error {
return nil
}

// Query sends a request and receives a response.
func (c *Client) Query(req *grnci.Request) (*grnci.Response, error) {
return nil, nil
}
196 changes: 196 additions & 0 deletions v2/libgrn/libgrn.go
@@ -0,0 +1,196 @@
// Package libgrn provides Client using libgroonga.
package libgrn

// #cgo pkg-config: groonga
// #include <groonga.h>
// #include <stdlib.h>
import "C"
import (
"errors"
"fmt"
"sync"
"unsafe"
)

var (
// libCount is incremented by Init and decremented by Fin.
libCount int
// libMutex is used for exclusion control in Init and Fin.
libMutex sync.Mutex
)

// Init initializes libgroonga.
//
// If an internal counter is zero, Init increments it and initializes libgroonga.
// Otherwise, Init only increments the internal counter.
//
// There is no need to call Init explicitly.
// libgrn calls Init when it creates a Client.
func Init() error {
libMutex.Lock()
defer libMutex.Unlock()
if libCount == 0 {
if rc := C.grn_init(); rc != C.GRN_SUCCESS {
return fmt.Errorf("C.grn_init failed: rc = %d", rc)
}
}
libCount++
return nil
}

// Fin finalizes libgroonga.
//
// If an internal counter is one, Fin decrements it and finalizes libgroonga.
// Otherwise, Fin only decrements the internal counter.
//
// There is no need to call Fin explicitly.
// libgrn calls Fin when it closes a Client.
func Fin() error {
libMutex.Lock()
defer libMutex.Unlock()
if libCount == 0 {
return fmt.Errorf("libCount = 0")
}
libCount--
if libCount == 0 {
if rc := C.grn_fin(); rc != C.GRN_SUCCESS {
return fmt.Errorf("C.grn_fin failed: rc = %d", rc)
}
}
return nil
}

// ctx is a Groonga context.
type grnCtx struct {
ctx *C.grn_ctx
}

// newGrnCtx returns a new grnCtx.
func newGrnCtx() (*grnCtx, error) {
if err := Init(); err != nil {
return nil, fmt.Errorf("Init failed: %v", err)
}
ctx := C.grn_ctx_open(C.int(0))
if ctx == nil {
Fin()
return nil, errors.New("C.grn_ctx_open failed")
}
return &grnCtx{ctx: ctx}, nil
}

// Close closes a grnCtx.
func (c *grnCtx) Close() error {
if rc := C.grn_ctx_close(c.ctx); rc != C.GRN_SUCCESS {
return fmt.Errorf("C.grn_ctx_close failed: %s", rc)
}
if err := Fin(); err != nil {
return fmt.Errorf("Fin failed: %v", err)
}
return nil
}

func (c *grnCtx) Err() error {
if c.ctx.rc == C.GRN_SUCCESS {
return nil
}
return fmt.Errorf("rc = %s: %s", c.ctx.rc, C.GoString(&c.ctx.errbuf[0]))
}

// grnDB is a DB handle.
type grnDB struct {
obj *C.grn_obj
path string
count int
mutex sync.Mutex
}

// createDB creates a new DB.
func createDB(ctx *grnCtx, path string) (*grnDB, error) {
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
obj := C.grn_db_create(ctx.ctx, cPath, nil)
if obj == nil {
return nil, fmt.Errorf("C.grn_db_create failed: %v", ctx.Err())
}
if cAbsPath := C.grn_obj_path(ctx.ctx, obj); cAbsPath != nil {
path = C.GoString(cAbsPath)
}
return &grnDB{
obj: obj,
path: path,
count: 1,
}, nil
}

// openDB opens an existing DB.
func openDB(ctx *grnCtx, path string) (*grnDB, error) {
if ctx == nil {
return nil, errors.New("invalid argument: ctx = nil")
}
if path == "" {
return nil, errors.New("invalid argument: path = ")
}
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
obj := C.grn_db_open(ctx.ctx, cPath)
if obj == nil {
return nil, fmt.Errorf("C.grn_db_create failed: %v", ctx.Err())
}
if cAbsPath := C.grn_obj_path(ctx.ctx, obj); cAbsPath != nil {
path = C.GoString(cAbsPath)
}
return &grnDB{
obj: obj,
path: path,
count: 1,
}, nil
}

// Close closes a DB.
func (db *grnDB) Close(ctx *grnCtx) error {
if db == nil {
return errors.New("invalid self: db = nil")
}
if db.obj == nil {
return errors.New("invalid self: obj = nil")
}
if ctx == nil {
return errors.New("invalid argument: ctx = nil")
}
db.mutex.Lock()
defer db.mutex.Unlock()
if db.count <= 0 {
return fmt.Errorf("underflow: cnt = %d", db.count)
}
db.count--
if db.count == 0 {
if rc := C.grn_obj_close(ctx.ctx, db.obj); rc != C.GRN_SUCCESS {
return fmt.Errorf("C.grn_obj_close failed: rc = %s", rc)
}
db.obj = nil
}
return nil
}

// Dup duplicates a DB handle.
func (db *grnDB) Dup() (*grnCtx, error) {
if db == nil {
return nil, errors.New("invalid self: db = nil")
}
if db.obj == nil {
return nil, errors.New("invalid self: obj = nil")
}
ctx, err := newGrnCtx()
if err != nil {
return nil, fmt.Errorf("newGrnCtx failed: %v", err)
}
C.grn_ctx_use(ctx.ctx, db.obj)
if err := ctx.Err(); err != nil {
ctx.Close()
return nil, fmt.Errorf("C.grn_ctx_use failed: %v", err)
}
db.mutex.Lock()
db.count++
db.mutex.Unlock()
return ctx, nil
}
2 changes: 1 addition & 1 deletion v2/request.go
Expand Up @@ -32,7 +32,7 @@ func checkCmd(s string) error {
// Check checks if req is valid.
func (req *Request) Check() error {
if err := checkCmd(req.Cmd); err != nil {
return fmt.Errorf("CheckCmd failed: %v", err)
return fmt.Errorf("checkCmd failed: %v", err)
}
for _, arg := range req.Args {
if err := arg.Check(); err != nil {
Expand Down
1 change: 1 addition & 0 deletions v2/respponse.go → v2/response.go
Expand Up @@ -4,6 +4,7 @@ import (
"time"
)

// Response stores a response of Groonga.
type Response struct {
Bytes []byte
Error error
Expand Down

0 comments on commit 09c35bc

Please sign in to comment.