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