[Groonga-commit] groonga/grnci at 1caa4a7 [master] Refactor the implementation of DB

Back to archive index

Susumu Yata null+****@clear*****
Fri Feb 19 00:51:53 JST 2016


Susumu Yata	2016-02-19 00:51:53 +0900 (Fri, 19 Feb 2016)

  New Revision: 1caa4a7a26ef908bb83f3ed83b0aff42643985ae
  https://github.com/groonga/grnci/commit/1caa4a7a26ef908bb83f3ed83b0aff42643985ae

  Message:
    Refactor the implementation of DB
    
    DB.IsHandle() and DB.IsConnection() are removed.
    DBMode and DB.Mode() are added.

  Modified files:
    db.go
    grn.go
    grnci_test.go

  Modified: db.go (+516 -237)
===================================================================
--- db.go    2016-02-18 18:00:45 +0900 (41e59b3)
+++ db.go    2016-02-19 00:51:53 +0900 (a826250)
@@ -14,295 +14,216 @@ import (
 	"unsafe"
 )
 
-//
-// DB handle
-//
-
-// refCount is a reference counter for DB.obj.
-type refCount struct {
-	cnt   int        // Count.
-	mutex sync.Mutex // Mutex for the reference count.
+// FIXME: utility function.
+// joinErrors joins errors.
+func joinErrors(errs []error) error {
+	if len(errs) == 1 {
+		return errs[0]
+	} else if len(errs) > 1 {
+		return fmt.Errorf("%v", errs)
+	}
+	return nil
 }
 
-// newRefCount creates a reference counter.
-func newRefCount() *refCount {
-	return &refCount{}
-}
+// DBMode is a mode of DB instance.
+type DBMode int
 
-// DB is a handle to a database or a connection to a server.
-type DB struct {
-	ctx  *C.grn_ctx // Context.
-	obj  *C.grn_obj // Database object (handle).
-	path string     // Database path (handle).
-	ref  *refCount  // Reference counter for obj.
-	host string     // Server host name (connection).
-	port int        // Server port number (connection).
+const (
+	InvalidDB = DBMode(iota) // Invalid instance
+	LocalDB                  // Handle to a local Groonga DB
+	GQTPClient               // Connection to a GQTP Groonga server
+)
+
+// localDB is a handle to a local Groonga DB.
+type localDB struct {
+	ctx   *C.grn_ctx  // Context
+	obj   *C.grn_obj  // Database object
+	path  string      // Database path
+	cnt   *int        // Reference count
+	mutex *sync.Mutex // Mutex for reference count
 }
 
-// newDB creates an instance of DB.
-// The instance must be finalized by DB.fin.
-func newDB() (*DB, error) {
-	if err := grnInit(); err != nil {
-		return nil, err
-	}
-	var db DB
-	db.ctx = C.grn_ctx_open(C.int(0))
-	if db.ctx == nil {
-		grnFin()
-		return nil, fmt.Errorf("grn_ctx_open failed")
-	}
-	return &db, nil
+func newLocalDB() *localDB {
+	return &localDB{}
 }
 
-// fin finalizes an instance of DB.
-func (db *DB) fin() error {
+func (db *localDB) fin() error {
 	if db == nil {
-		return fmt.Errorf("db is nil")
+		return fmt.Errorf("db = nil")
 	}
 	if db.ctx == nil {
 		return nil
 	}
+	var errs []error
 	if db.obj != nil {
-		if db.ref == nil {
-			return fmt.Errorf("ref is nil")
+		db.mutex.Lock()
+		*db.cnt--
+		if *db.cnt == 0 {
+			if rc := C.grn_obj_close(db.ctx, db.obj); rc != C.GRN_SUCCESS {
+				errs = append(errs, fmt.Errorf("C.grn_obj_close failed: rc = %s", rc))
+			}
 		}
-		db.ref.mutex.Lock()
-		db.ref.cnt--
-		if db.ref.cnt == 0 {
-			C.grn_obj_close(db.ctx, db.obj)
-		}
-		db.ref.mutex.Unlock()
-		db.obj = nil
-		db.ref = nil
-	} else {
-		db.host = ""
-		db.port = 0
+		db.mutex.Unlock()
 	}
-	rc := C.grn_ctx_close(db.ctx)
-	db.ctx = nil
-	grnFin()
-	if rc != C.GRN_SUCCESS {
-		return fmt.Errorf("grn_ctx_close failed: rc = %s", rc)
+	if rc := C.grn_ctx_close(db.ctx); rc != C.GRN_SUCCESS {
+		errs = append(errs, fmt.Errorf("C.grn_ctx_close failed: rc = %s", rc))
 	}
-	return nil
+	return joinErrors(errs)
 }
 
-// errorf creates an error.
-func (db *DB) errorf(format string, args ...interface{}) error {
+func (db *localDB) errorf(format string, args ...interface{}) error {
 	msg := fmt.Sprintf(format, args...)
 	if (db == nil) || (db.ctx == nil) || (db.ctx.rc == C.GRN_SUCCESS) {
-		return fmt.Errorf(format, args...)
+		return fmt.Errorf("%s: path = \"%s\"", msg, db.path)
 	}
 	ctxMsg := C.GoString(&db.ctx.errbuf[0])
-	return fmt.Errorf("%s: ctx.rc = %s, ctx.errbuf = %s", msg, db.ctx.rc, ctxMsg)
-}
-
-// IsHandle returns whether db is a handle.
-func (db *DB) IsHandle() bool {
-	return (db != nil) && (db.obj != nil)
-}
-
-// IsConnection returns whether db is a connection.
-func (db *DB) IsConnection() bool {
-	return (db != nil) && (len(db.host) != 0)
-}
-
-// Path returns the database path if db is a handle.
-// Otherwise, it returns "".
-func (db *DB) Path() string {
-	if db == nil {
-		return ""
-	}
-	return db.path
-}
-
-// Host returns the server host name if db is a connection.
-// Otherwise, it returns "".
-func (db *DB) Host() string {
-	if db == nil {
-		return ""
-	}
-	return db.host
-}
-
-// Port returns the server port number if db is a connection.
-// Otherwise, it returns 0.
-func (db *DB) Port() int {
-	if db == nil {
-		return 0
-	}
-	return db.port
-}
-
-// check returns an error if db is invalid.
-func (db *DB) check() error {
-	if db == nil {
-		return fmt.Errorf("db is nil")
-	}
-	if db.ctx == nil {
-		return fmt.Errorf("ctx is nil")
-	}
-	if (db.obj == nil) && (len(db.host) == 0) {
-		return fmt.Errorf("neither a handle nor a connection")
-	}
-	return nil
+	return fmt.Errorf("%s: path = \"%s\", ctx = %s \"%s\"",
+		msg, db.path, db.ctx.rc, ctxMsg)
 }
 
-// Create creates a database and returns a handle to it.
-// The handle must be closed by DB.Close.
-func Create(path string) (*DB, error) {
-	if len(path) == 0 {
-		return nil, fmt.Errorf("path is empty")
-	}
-	db, err := newDB()
-	if err != nil {
-		return nil, err
-	}
-	cPath := C.CString(path)
-	defer C.free(unsafe.Pointer(cPath))
-	db.obj = C.grn_db_create(db.ctx, cPath, nil)
-	if db.obj == nil {
-		db.fin()
-		return nil, fmt.Errorf("grn_db_create failed")
-	}
-	db.ref = newRefCount()
-	db.ref.cnt++
-	cAbsPath := C.grn_obj_path(db.ctx, db.obj)
-	if cAbsPath == nil {
-		db.fin()
-		return nil, fmt.Errorf("grn_obj_path failed")
-	}
-	db.path = C.GoString(cAbsPath)
-	return db, nil
-}
-
-// Open opens a database and returns a handle to it.
-// The handle must be closed by DB.Close.
-func Open(path string) (*DB, error) {
-	if len(path) == 0 {
-		return nil, fmt.Errorf("path is empty")
-	}
-	db, err := newDB()
-	if err != nil {
-		return nil, err
-	}
-	cPath := C.CString(path)
-	defer C.free(unsafe.Pointer(cPath))
-	db.obj = C.grn_db_open(db.ctx, cPath)
-	if db.obj == nil {
-		db.fin()
-		return nil, fmt.Errorf("grn_db_open failed")
+func createLocalDB(path string) (*localDB, error) {
+	var errs []error
+	db := newLocalDB()
+	if db.ctx = C.grn_ctx_open(C.int(0)); db.ctx == nil {
+		errs = append(errs, fmt.Errorf("C.grn_ctx_open failed"))
+	} else {
+		cPath := C.CString(path)
+		defer C.free(unsafe.Pointer(cPath))
+		if db.obj = C.grn_db_create(db.ctx, cPath, nil); db.obj == nil {
+			err := fmt.Errorf("C.grn_db_create failed: path = \"%s\"", path)
+			errs = append(errs, err)
+		} else {
+			db.cnt = new(int)
+			db.mutex = new(sync.Mutex)
+			*db.cnt++
+			if cAbsPath := C.grn_obj_path(db.ctx, db.obj); cAbsPath == nil {
+				errs = append(errs, fmt.Errorf("C.grn_obj_path failed"))
+			} else {
+				db.path = C.GoString(cAbsPath)
+			}
+		}
 	}
-	db.ref = newRefCount()
-	db.ref.cnt++
-	cAbsPath := C.grn_obj_path(db.ctx, db.obj)
-	if cAbsPath == nil {
-		db.fin()
-		return nil, fmt.Errorf("grn_obj_path failed")
+	if errs != nil {
+		if err := db.fin(); err != nil {
+			errs = append(errs, err)
+		}
+		return nil, joinErrors(errs)
 	}
-	db.path = C.GoString(cAbsPath)
 	return db, nil
 }
 
-// Connect establishes a connection to a server.
-// The connection must be closed by DB.Close.
-func Connect(host string, port int) (*DB, error) {
-	if len(host) == 0 {
-		return nil, fmt.Errorf("host is empty")
-	}
-	db, err := newDB()
-	if err != nil {
-		return nil, err
+func openLocalDB(path string) (*localDB, error) {
+	var errs []error
+	db := newLocalDB()
+	if db.ctx = C.grn_ctx_open(C.int(0)); db.ctx == nil {
+		errs = append(errs, fmt.Errorf("C.grn_ctx_open failed"))
+	} else {
+		cPath := C.CString(path)
+		defer C.free(unsafe.Pointer(cPath))
+		if db.obj = C.grn_db_open(db.ctx, cPath); db.obj == nil {
+			err := fmt.Errorf("C.grn_db_open failed: path = \"%s\"", path)
+			errs = append(errs, err)
+		} else {
+			db.cnt = new(int)
+			db.mutex = new(sync.Mutex)
+			*db.cnt++
+			if cAbsPath := C.grn_obj_path(db.ctx, db.obj); cAbsPath == nil {
+				errs = append(errs, fmt.Errorf("C.grn_obj_path failed"))
+			} else {
+				db.path = C.GoString(cAbsPath)
+			}
+		}
 	}
-	cHost := C.CString(host)
-	defer C.free(unsafe.Pointer(cHost))
-	rc := C.grn_ctx_connect(db.ctx, cHost, C.int(port), C.int(0))
-	if rc != C.GRN_SUCCESS {
-		db.fin()
-		return nil, fmt.Errorf("grn_ctx_connect failed: rc = %s", rc)
+	if errs != nil {
+		if err := db.fin(); err != nil {
+			errs = append(errs, err)
+		}
+		return nil, joinErrors(errs)
 	}
-	db.host = host
-	db.port = port
 	return db, nil
 }
 
-// Dup duplicates a handle or a connection.
-// The handle or connection must be closed by DB.Close.
-func (db *DB) Dup() (*DB, error) {
-	if err := db.check(); err != nil {
-		return nil, err
-	}
-	if db.IsConnection() {
-		return Connect(db.host, db.port)
-	}
-	dupDB, err := newDB()
-	if err != nil {
-		return nil, err
+func (db *localDB) dup() (*localDB, error) {
+	var errs []error
+	dupDB := newLocalDB()
+	if dupDB.ctx = C.grn_ctx_open(C.int(0)); dupDB.ctx == nil {
+		errs = append(errs, fmt.Errorf("C.grn_ctx_open failed"))
+	} else {
+		if rc := C.grn_ctx_use(dupDB.ctx, db.obj); rc != C.GRN_SUCCESS {
+			errs = append(errs, db.errorf("C.grn_ctx_use failed: rc = %s", rc))
+		}
 	}
-	if rc := C.grn_ctx_use(dupDB.ctx, db.obj); rc != C.GRN_SUCCESS {
-		dupDB.fin()
-		return nil, db.errorf("grn_ctx_use failed: rc = %s", rc)
+	if errs != nil {
+		if err := dupDB.fin(); err != nil {
+			errs = append(errs, err)
+		}
+		return nil, joinErrors(errs)
 	}
 	dupDB.obj = db.obj
-	dupDB.ref = db.ref
-	dupDB.ref.mutex.Lock()
-	dupDB.ref.cnt++
-	dupDB.ref.mutex.Unlock()
 	dupDB.path = db.path
+	dupDB.cnt = db.cnt
+	dupDB.mutex = db.mutex
+	*dupDB.cnt++
 	return dupDB, nil
 }
 
-// Close closes a handle or a connection.
-func (db *DB) Close() error {
-	if err := db.check(); err != nil {
-		return err
+func (db *localDB) check() error {
+	if db == nil {
+		return fmt.Errorf("db = nil")
 	}
-	return db.fin()
+	if db.ctx == nil {
+		return fmt.Errorf("ctx = nil")
+	}
+	if db.obj == nil {
+		return fmt.Errorf("obj = nil")
+	}
+	return nil
 }
 
-//
-// Low-level command interface
-//
-
-// checkCmdName checks whether a string is valid as a command name.
+// checkCmdName checks whether s is valid as a command name.
 func checkCmdName(s string) error {
-	if len(s) == 0 {
-		return fmt.Errorf("command name must not be empty")
+	if s == "" {
+		return fmt.Errorf("invalid command name: s = \"\"")
 	}
 	if s[0] == '_' {
-		return fmt.Errorf("command name must not start with '_'")
+		return fmt.Errorf("invalid command name: s = \"%s\"", s)
 	}
 	for i := 0; i < len(s); i++ {
 		if !((s[i] >= 'a') && (s[i] <= 'z')) && (s[i] != '_') {
-			return fmt.Errorf("command name must not contain \\x%X", s[i])
+			return fmt.Errorf("invalid command name: s = \"%s\"", s)
 		}
 	}
 	return nil
 }
 
-// checkCmdArgKey checks whether a string is valid as an argument key.
-func checkArgKey(s string) error {
-	if len(s) == 0 {
-		return fmt.Errorf("command name must not be empty")
+// checkCmdArgKey checks whether s is valid as a command argument key.
+func checkCmdArgKey(s string) error {
+	if s == "" {
+		return fmt.Errorf("invalid command argument key: s = \"\"")
 	}
 	if s[0] == '_' {
-		return fmt.Errorf("command name must not start with '_'")
+		return fmt.Errorf("invalid command argument key: s = \"%s\"", s)
 	}
 	for i := 0; i < len(s); i++ {
 		if !((s[i] >= 'a') && (s[i] <= 'z')) && (s[i] != '_') {
-			return fmt.Errorf("command name must not contain \\x%X", s[i])
+			return fmt.Errorf("invalid command argument key: s = \"%s\"", s)
 		}
 	}
 	return nil
 }
 
 type cmdArg struct {
-	Key   string
-	Value string
+	key string
+	val string
+}
+
+// check checks whether a command argument is valid.
+func (arg *cmdArg) check() error {
+	return checkCmdArgKey(arg.key)
 }
 
 // composeCmd composes a command from its name and arguments.
-func (db *DB) composeCmd(name string, args []cmdArg) (string, error) {
+func composeCmd(name string, args []cmdArg) (string, error) {
 	if err := checkCmdName(name); err != nil {
 		return "", err
 	}
@@ -311,18 +232,141 @@ func (db *DB) composeCmd(name string, args []cmdArg) (string, error) {
 		return "", err
 	}
 	for _, arg := range args {
-		if err := checkArgKey(arg.Key); err != nil {
+		if err := arg.check(); err != nil {
 			return "", err
 		}
-		val := strings.Replace(arg.Value, "\\", "\\\\", -1)
+		val := strings.Replace(arg.val, "\\", "\\\\", -1)
 		val = strings.Replace(val, "'", "\\'", -1)
-		fmt.Fprintf(buf, " --%s '%s'", arg.Key, val)
+		fmt.Fprintf(buf, " --%s '%s'", arg.key, val)
 	}
 	return buf.String(), nil
 }
 
 // send sends data.
-func (db *DB) send(data []byte) error {
+func (db *localDB) send(data []byte) error {
+	var p *C.char
+	if len(data) != 0 {
+		p = (*C.char)(unsafe.Pointer(&data[0]))
+	}
+	rc := C.grn_rc(C.grn_ctx_send(db.ctx, p, C.uint(len(data)), C.int(0)))
+	if (rc != C.GRN_SUCCESS) || (db.ctx.rc != C.GRN_SUCCESS) {
+		return db.errorf("C.grn_ctx_send failed: rc = %s", rc)
+	}
+	return nil
+}
+
+// recv receives the response to sent data.
+func (db *localDB) recv() ([]byte, error) {
+	var resp *C.char
+	var respLen C.uint
+	var respFlags C.int
+	rc := C.grn_rc(C.grn_ctx_recv(db.ctx, &resp, &respLen, &respFlags))
+	if (rc != C.GRN_SUCCESS) || (db.ctx.rc != C.GRN_SUCCESS) {
+		return nil, db.errorf("C.grn_ctx_recv failed: rc = %s", rc)
+	}
+	return C.GoBytes(unsafe.Pointer(resp), C.int(respLen)), nil
+}
+
+// query sends a command and receives the response.
+func (db *localDB) query(name string, args []cmdArg, data []byte) ([]byte, error) {
+	cmd, err := composeCmd(name, args)
+	if err != nil {
+		return nil, err
+	}
+	// Send a command.
+	if err := db.send([]byte(cmd)); err != nil {
+		resp, _ := db.recv()
+		return resp, err
+	}
+	resp, err := db.recv()
+	if (data == nil) || (err != nil) {
+		return resp, err
+	}
+	// Send data if available.
+	if len(resp) != 0 {
+		return resp, db.errorf("unexpected response")
+	}
+	if err := db.send(data); err != nil {
+		resp, _ := db.recv()
+		return resp, err
+	}
+	return db.recv()
+}
+
+// gqtpClient is a connection to a GQTP Groonga server.
+type gqtpClient struct {
+	ctx  *C.grn_ctx // Context
+	host string     // Server's host name or IP address
+	port int        // Server's port number
+}
+
+func newGQTPClient() *gqtpClient {
+	return &gqtpClient{}
+}
+
+func (db *gqtpClient) fin() error {
+	if db == nil {
+		return fmt.Errorf("db is nil")
+	}
+	if db.ctx == nil {
+		return nil
+	}
+	var errs []error
+	if rc := C.grn_ctx_close(db.ctx); rc != C.GRN_SUCCESS {
+		errs = append(errs, fmt.Errorf("C.grn_ctx_close failed: rc = %s", rc))
+	}
+	return joinErrors(errs)
+}
+
+func (db *gqtpClient) errorf(format string, args ...interface{}) error {
+	msg := fmt.Sprintf(format, args...)
+	if (db == nil) || (db.ctx == nil) || (db.ctx.rc == C.GRN_SUCCESS) {
+		return fmt.Errorf("%s: host = \"%s\", port = %d", msg, db.host, db.port)
+	}
+	ctxMsg := C.GoString(&db.ctx.errbuf[0])
+	return fmt.Errorf("%s: host = \"%s\", port = %d, ctx = %s \"%s\"",
+		msg, db.host, db.port, db.ctx.rc, ctxMsg)
+}
+
+func (db *gqtpClient) check() error {
+	if db == nil {
+		return fmt.Errorf("db = nil")
+	}
+	if db.ctx == nil {
+		return fmt.Errorf("ctx = nil")
+	}
+	return nil
+}
+
+func openGQTPClient(host string, port int) (*gqtpClient, error) {
+	var errs []error
+	db := newGQTPClient()
+	if db.ctx = C.grn_ctx_open(C.int(0)); db.ctx == nil {
+		errs = append(errs, fmt.Errorf("C.grn_ctx_open failed"))
+	} else {
+		cHost := C.CString(host)
+		defer C.free(unsafe.Pointer(cHost))
+		rc := C.grn_ctx_connect(db.ctx, cHost, C.int(port), C.int(0))
+		if rc != C.GRN_SUCCESS {
+			const format = "C.grn_ctx_connect failed: host = \"%s\", port = %d, rc = %s"
+			err := fmt.Errorf(format, host, port, rc)
+			errs = append(errs, err)
+		} else {
+			db.host = host
+			db.port = port
+		}
+	}
+	if errs != nil {
+		if err := db.fin(); err != nil {
+			errs = append(errs, err)
+		}
+		return nil, joinErrors(errs)
+	}
+	return db, nil
+}
+
+// send sends data.
+func (db *gqtpClient) send(data []byte) error {
 	var p *C.char
 	if len(data) != 0 {
 		p = (*C.char)(unsafe.Pointer(&data[0]))
@@ -335,28 +379,28 @@ func (db *DB) send(data []byte) error {
 }
 
 // recv receives the response to sent data.
-func (db *DB) recv() ([]byte, error) {
-	var res *C.char
-	var resLen C.uint
-	var resFlags C.int
-	rc := C.grn_rc(C.grn_ctx_recv(db.ctx, &res, &resLen, &resFlags))
+func (db *gqtpClient) recv() ([]byte, error) {
+	var resp *C.char
+	var respLen C.uint
+	var respFlags C.int
+	rc := C.grn_rc(C.grn_ctx_recv(db.ctx, &resp, &respLen, &respFlags))
 	if (rc != C.GRN_SUCCESS) || (db.ctx.rc != C.GRN_SUCCESS) {
 		return nil, db.errorf("grn_ctx_recv failed: rc = %s", rc)
 	}
-	if (resFlags & C.GRN_CTX_MORE) == 0 {
-		return C.GoBytes(unsafe.Pointer(res), C.int(resLen)), nil
+	if (respFlags & C.GRN_CTX_MORE) == 0 {
+		return C.GoBytes(unsafe.Pointer(resp), C.int(respLen)), nil
 	}
-	buf := bytes.NewBuffer(C.GoBytes(unsafe.Pointer(res), C.int(resLen)))
+	buf := bytes.NewBuffer(C.GoBytes(unsafe.Pointer(resp), C.int(respLen)))
 	var bufErr error
 	for {
-		rc := C.grn_rc(C.grn_ctx_recv(db.ctx, &res, &resLen, &resFlags))
+		rc := C.grn_rc(C.grn_ctx_recv(db.ctx, &resp, &respLen, &respFlags))
 		if (rc != C.GRN_SUCCESS) || (db.ctx.rc != C.GRN_SUCCESS) {
 			return nil, db.errorf("grn_ctx_recv failed: rc = %s", rc)
 		}
 		if bufErr == nil {
-			_, bufErr = buf.Write(C.GoBytes(unsafe.Pointer(res), C.int(resLen)))
+			_, bufErr = buf.Write(C.GoBytes(unsafe.Pointer(resp), C.int(respLen)))
 		}
-		if (resFlags & C.GRN_CTX_MORE) == 0 {
+		if (respFlags & C.GRN_CTX_MORE) == 0 {
 			break
 		}
 	}
@@ -367,8 +411,8 @@ func (db *DB) recv() ([]byte, error) {
 }
 
 // query sends a command and receives the response.
-func (db *DB) query(name string, args []cmdArg, data []byte) ([]byte, error) {
-	cmd, err := db.composeCmd(name, args)
+func (db *gqtpClient) query(name string, args []cmdArg, data []byte) ([]byte, error) {
+	cmd, err := composeCmd(name, args)
 	if err != nil {
 		return nil, err
 	}
@@ -391,3 +435,238 @@ func (db *DB) query(name string, args []cmdArg, data []byte) ([]byte, error) {
 	}
 	return db.recv()
 }
+
+// DB is a handle to a Groonga DB or a connection to a Groonga server.
+//
+// Note that DB is not thread-safe.
+// DB.Dup is useful to create a DB instance for each thread.
+type DB struct {
+	mode       DBMode      // Mode
+	localDB    *localDB    // Handle to a local Groonga DB
+	gqtpClient *gqtpClient // Connection to a GQTP Groonga server
+}
+
+// Mode returns the DB mode.
+func (db *DB) Mode() DBMode {
+	if db == nil {
+		return InvalidDB
+	}
+	return db.mode
+}
+
+// Path returns the DB file path if db is a handle.
+// Otherwise, it returns "".
+func (db *DB) Path() string {
+	if db == nil {
+		return ""
+	}
+	switch db.mode {
+	case LocalDB:
+		return db.localDB.path
+	default:
+		return ""
+	}
+}
+
+// Host returns server's host name or IP address if db is a connection.
+// Otherwise, it returns "".
+func (db *DB) Host() string {
+	if db == nil {
+		return ""
+	}
+	switch db.mode {
+	case GQTPClient:
+		return db.gqtpClient.host
+	default:
+		return ""
+	}
+}
+
+// Port returns server's port number if db is a connection.
+// Otherwise, it returns 0.
+func (db *DB) Port() int {
+	if db == nil {
+		return 0
+	}
+	switch db.mode {
+	case GQTPClient:
+		return db.gqtpClient.port
+	default:
+		return 0
+	}
+}
+
+// check returns an error if db is invalid.
+func (db *DB) check() error {
+	if db == nil {
+		return fmt.Errorf("db = nil")
+	}
+	switch db.mode {
+	case InvalidDB:
+		return fmt.Errorf("mode = InvalidDB")
+	case LocalDB:
+		return db.localDB.check()
+	case GQTPClient:
+		return db.gqtpClient.check()
+	default:
+		return fmt.Errorf("undefined mode: mode = %d", db.mode)
+	}
+}
+
+// newDB creates a DB instance.
+// The instance must be finalized by DB.fin.
+func newDB() (*DB, error) {
+	if err := grnInit(); err != nil {
+		return nil, err
+	}
+	return &DB{}, nil
+}
+
+// fin finalizes a DB instance.
+func (db *DB) fin() error {
+	if db == nil {
+		return fmt.Errorf("db = nil")
+	}
+	var errs []error
+	switch db.mode {
+	case LocalDB:
+		if err := db.localDB.fin(); err != nil {
+			errs = append(errs, err)
+		}
+	case GQTPClient:
+		if err := db.gqtpClient.fin(); err != nil {
+			errs = append(errs, err)
+		}
+	}
+	if err := grnFin(); err != nil {
+		errs = append(errs, err)
+	}
+	return joinErrors(errs)
+}
+
+// Create creates a local DB and returns a handle to it.
+// The handle must be closed by DB.Close.
+func Create(path string) (*DB, error) {
+	if path == "" {
+		return nil, fmt.Errorf("path = \"\"")
+	}
+	db, err := newDB()
+	if err != nil {
+		return nil, err
+	}
+	var errs []error
+	if db.localDB, err = createLocalDB(path); err != nil {
+		errs = append(errs, err)
+		if err := db.fin(); err != nil {
+			errs = append(errs, err)
+		}
+		return nil, joinErrors(errs)
+	}
+	db.mode = LocalDB
+	return db, nil
+}
+
+// Open opens a local DB and returns a handle to it.
+// The handle must be closed by DB.Close.
+func Open(path string) (*DB, error) {
+	if path == "" {
+		return nil, fmt.Errorf("path = \"\"")
+	}
+	db, err := newDB()
+	if err != nil {
+		return nil, err
+	}
+	var errs []error
+	if db.localDB, err = openLocalDB(path); err != nil {
+		errs = append(errs, err)
+		if err := db.fin(); err != nil {
+			errs = append(errs, err)
+		}
+		return nil, joinErrors(errs)
+	}
+	db.mode = LocalDB
+	return db, nil
+}
+
+// Connect establishes a connection to a server.
+// The connection must be closed by DB.Close.
+func Connect(host string, port int) (*DB, error) {
+	if host == "" {
+		return nil, fmt.Errorf("host = \"\"")
+	}
+	db, err := newDB()
+	if err != nil {
+		return nil, err
+	}
+	var errs []error
+	if db.gqtpClient, err = openGQTPClient(host, port); err != nil {
+		errs = append(errs, err)
+		if err := db.fin(); err != nil {
+			errs = append(errs, err)
+		}
+		return nil, joinErrors(errs)
+	}
+	db.mode = GQTPClient
+	return db, nil
+}
+
+// Dup duplicates a DB instance.
+// The instance must be closed by DB.Close.
+func (db *DB) Dup() (*DB, error) {
+	if err := db.check(); err != nil {
+		return nil, err
+	}
+	switch db.mode {
+	case LocalDB:
+		dupDB, err := newDB()
+		if err != nil {
+			return nil, err
+		}
+		var errs []error
+		if dupDB.localDB, err = db.localDB.dup(); err != nil {
+			errs = append(errs, err)
+		}
+		if len(errs) != 0 {
+			if err := dupDB.fin(); err != nil {
+				errs = append(errs, err)
+			}
+			return nil, joinErrors(errs)
+		}
+		dupDB.mode = db.mode
+		return dupDB, nil
+	default:
+		return Connect(db.Host(), db.Port())
+	}
+}
+
+// Close closes a handle or a connection.
+func (db *DB) Close() error {
+	if err := db.check(); err != nil {
+		return err
+	}
+	return db.fin()
+}
+
+// errorf creates an error.
+func (db *DB) errorf(format string, args ...interface{}) error {
+	switch db.mode {
+	case LocalDB:
+		return db.localDB.errorf(format, args...)
+	case GQTPClient:
+		return db.gqtpClient.errorf(format, args...)
+	default:
+		return fmt.Errorf(format, args...)
+	}
+}
+
+// query sends a command and receives the response.
+func (db *DB) query(name string, args []cmdArg, data []byte) ([]byte, error) {
+	switch db.mode {
+	case LocalDB:
+		return db.localDB.query(name, args, data)
+	case GQTPClient:
+		return db.gqtpClient.query(name, args, data)
+	default:
+		return nil, fmt.Errorf("invalid mode: %d", db.mode)
+	}
+}

  Modified: grn.go (+2 -2)
===================================================================
--- grn.go    2016-02-18 18:00:45 +0900 (e01e5b1)
+++ grn.go    2016-02-19 00:51:53 +0900 (e117a61)
@@ -19,7 +19,7 @@ import (
 // Note that C.grn_init must not be called if Groonga is already initialized.
 //
 // Grnci, by default, initializes Groonga when it creates the first DB
-// instance.
+// instance and finalizes Groonga when it closes the last DB instance.
 // To achieve this, Grnci uses a reference count.
 
 // grnCnt is a reference count for Groonga.
@@ -99,7 +99,7 @@ func GrnInit() error {
 // GrnFin explicitly finalizes Groonga.
 // GrnFin should be used if Groonga is initialized by GrnInit.
 //
-// Note that Groonga is implicitly finalized when Grnci deletes the last DB
+// Note that Groonga is implicitly finalized when Grnci closes the last DB
 // instance if GrnInit is not used.
 func GrnFin() error {
 	grnCntMutex.Lock()

  Modified: grnci_test.go (+15 -15)
===================================================================
--- grnci_test.go    2016-02-18 18:00:45 +0900 (3b827ee)
+++ grnci_test.go    2016-02-19 00:51:53 +0900 (d858e7d)
@@ -45,12 +45,21 @@ func removeTempDB(tb testing.TB, dirPath string, db *DB) {
 
 // TestGrnInit tests GrnInit and GrnFin
 func TestGrnInit(t *testing.T) {
+	if err := GrnFin(); err == nil {
+		t.Fatalf("GrnFin succeeded")
+	}
 	if err := GrnInit(); err != nil {
 		t.Fatalf("GrnInit failed: %v", err)
 	}
+	if err := GrnInit(); err == nil {
+		t.Fatalf("GrnInit succeeded")
+	}
 	if err := GrnFin(); err != nil {
 		t.Fatalf("GrnFin failed: %v", err)
 	}
+	if err := GrnFin(); err == nil {
+		t.Fatalf("GrnFin succeeded")
+	}
 }
 
 // TestCreate tests Create.
@@ -58,11 +67,8 @@ func TestCreate(t *testing.T) {
 	dirPath, _, db := createTempDB(t)
 	defer removeTempDB(t, dirPath, db)
 
-	if !db.IsHandle() {
-		t.Fatalf("DB.IsHandle failed")
-	}
-	if db.IsConnection() {
-		t.Fatalf("DB.IsConnection failed")
+	if db.Mode() != LocalDB {
+		t.Fatalf("DB.Mode failed")
 	}
 	if len(db.Path()) == 0 {
 		t.Fatalf("DB.Path failed")
@@ -86,11 +92,8 @@ func TestOpen(t *testing.T) {
 	}
 	defer db2.Close()
 
-	if !db2.IsHandle() {
-		t.Fatalf("DB.IsHandle failed")
-	}
-	if db2.IsConnection() {
-		t.Fatalf("DB.IsConnection failed")
+	if db2.Mode() != LocalDB {
+		t.Fatalf("DB.Mode failed")
 	}
 	if len(db2.Path()) == 0 {
 		t.Fatalf("DB.Path failed")
@@ -114,11 +117,8 @@ func TestDup(t *testing.T) {
 	}
 	defer db2.Close()
 
-	if !db2.IsHandle() {
-		t.Fatalf("DB.IsHandle failed")
-	}
-	if db2.IsConnection() {
-		t.Fatalf("DB.IsConnection failed")
+	if db2.Mode() != LocalDB {
+		t.Fatalf("DB.Mode failed")
 	}
 	if len(db2.Path()) == 0 {
 		t.Fatalf("DB.Path failed")
-------------- next part --------------
HTML����������������������������...
Download 



More information about the Groonga-commit mailing list
Back to archive index