susumu.yata
null+****@clear*****
Tue Apr 28 19:27:43 JST 2015
susumu.yata 2015-04-28 19:27:43 +0900 (Tue, 28 Apr 2015) New Revision: d0d4cbd11a172bb9f7169bc7b45514856c7d189a https://github.com/groonga/grnxx/commit/d0d4cbd11a172bb9f7169bc7b45514856c7d189a Message: Gnx: add a new version. Added files: go3/gnx/gnx.go go3/gnx/gnx_cgo.c go3/gnx/gnx_cgo.h go3/gnx/gnx_test.go go3/gnx/grn.go go3/gnx/grn_cgo.c go3/gnx/grn_cgo.h go3/gnx/grn_test.go go3/gnx/grnxx.go go3/gnx/grnxx_cgo.cpp go3/gnx/grnxx_cgo.h go3/gnx/options.go Added: go3/gnx/gnx.go (+275 -0) 100644 =================================================================== --- /dev/null +++ go3/gnx/gnx.go 2015-04-28 19:27:43 +0900 (7198302) @@ -0,0 +1,275 @@ +package gnx + +/* +#cgo pkg-config: groonga +#include "gnx_cgo.h" +*/ +import "C" + +import ( + "fmt" + "math" +) + +// -- Data types -- + +type Bool uint8 +type Int int64 +type Float float64 +type GeoPoint struct{ Latitude, Longitude int32 } +type Text []byte + +type BoolVector []Bool +type IntVector []Int +type FloatVector []Float +type GeoPointVector []GeoPoint +type TextVector []Text + +const ( + True = Bool(3) + False = Bool(0) +) + +func NullBool() Bool { return Bool(1) } +func NullInt() Int { return Int(math.MinInt64) } +func NullFloat() Float { return Float(math.NaN()) } +func NullGeoPoint() GeoPoint { return GeoPoint{math.MinInt32, math.MinInt32} } +func NullText() Text { return nil } + +func NullBoolVector() BoolVector { return nil } +func NullIntVector() IntVector { return nil } +func NullFloatVector() FloatVector { return nil } +func NullGeoPointVector() GeoPointVector { return nil } +func NullTextVector() TextVector { return nil } + +type TypeID int + +const ( + VoidID = TypeID(iota) + BoolID + IntID + FloatID + GeoPointID + TextID + BoolVectorID + IntVectorID + FloatVectorID + GeoPointVectorID + TextVectorID +) + +func (id TypeID) String() string { + switch id { + case VoidID: + return "Void" + case BoolID: + return "Bool" + case IntID: + return "Int" + case FloatID: + return "Float" + case GeoPointID: + return "GeoPoint" + case TextID: + return "Text" + case BoolVectorID: + return "BoolVector" + case IntVectorID: + return "IntVector" + case FloatVectorID: + return "FloatVector" + case GeoPointVectorID: + return "GeoPointVector" + case TextVectorID: + return "TextVector" + default: + return fmt.Sprintf("TypeID(%d)", id) + } +} + +// -- DB -- + +type DB struct { + *GrnDB + *GrnxxDB + tables map[string]*Table +} + +func newDB(grnDB *GrnDB, grnxxDB *GrnxxDB) *DB { + return &DB{grnDB, grnxxDB, make(map[string]*Table)} +} + +func CreateDB(path string) (*DB, error) { + grnDB, err := CreateGrnDB(path) + if err != nil { + return nil, err + } + return newDB(grnDB, nil), nil +} + +func OpenDB(path string) (*DB, error) { + grnDB, err := OpenGrnDB(path) + if err != nil { + return nil, err + } + return newDB(grnDB, nil), nil +} + +func (db *DB) Close() error { + var grnErr error + var grnxxErr error + if db.GrnDB != nil { + grnErr = db.GrnDB.Close() + } + if db.GrnxxDB != nil { + grnxxErr = db.GrnxxDB.Close() + } + if grnErr != nil { + return grnErr + } + return grnxxErr +} + +func (db *DB) CreateTable(name string, options *TableOptions) (*Table, error) { + // TODO + return nil, fmt.Errorf("not supported yet") +} + +func (db *DB) RemoveTable(name string) error { + // TODO + return fmt.Errorf("not supported yet") +} + +func (db *DB) RenameTable(name, newName string) error { + // TODO + return fmt.Errorf("not supported yet") +} + +func (db *DB) FindTable(name string) (*Table, error) { + if table, ok := db.tables[name]; ok { + return table, nil + } + // TODO + return nil, fmt.Errorf("not supported yet") +} + +func (db *DB) FindRow(tableName string, key interface{}) (Int, error) { + table, err := db.FindTable(tableName) + if err != nil { + return NullInt(), err + } + return table.FindRow(key) +} + +func (db *DB) InsertRow(tableName string, key interface{}) (bool, Int, error) { + table, err := db.FindTable(tableName) + if err != nil { + return false, NullInt(), err + } + return table.InsertRow(key) +} + +func (db *DB) CreateColumn(tableName, columnName, valueType string, + options *ColumnOptions) (*Column, error) { + table, err := db.FindTable(tableName) + if err != nil { + return nil, err + } + return table.CreateColumn(columnName, valueType, options) +} + +func (db *DB) RemoveColumn(tableName, columnName string) error { + table, err := db.FindTable(tableName) + if err != nil { + return err + } + return table.RemoveColumn(columnName) +} + +func (db *DB) RenameColumn(tableName, columnName, newColumnName string) error { + table, err := db.FindTable(tableName) + if err != nil { + return err + } + return table.RenameColumn(columnName, newColumnName) +} + +func (db *DB) FindColumn(tableName, columnName string) (*Column, error) { + table, err := db.FindTable(tableName) + if err != nil { + return nil, err + } + return table.FindColumn(columnName) +} + +func (db *DB) SetValue( + tableName, columnName string, id Int, value interface{}) error { + table, err := db.FindTable(tableName) + if err != nil { + return err + } + return table.SetValue(columnName, id, value) +} + +// -- Table -- + +type Table struct { + *GrnTable + *GrnxxTable + columns map[string]*Column +} + +func (table *Table) FindRow(key interface{}) (Int, error) { + // TODO + return NullInt(), fmt.Errorf("not supported yet") +} + +func (table *Table) InsertRow(key interface{}) (bool, Int, error) { + // TODO + return false, NullInt(), fmt.Errorf("not supported yet") +} + +func (table *Table) CreateColumn( + name, valueType string, options *ColumnOptions) (*Column, error) { + // TODO + return nil, fmt.Errorf("not supported yet") +} + +func (table *Table) RemoveColumn(name string) error { + // TODO + return fmt.Errorf("not supported yet") +} + +func (table *Table) RenameColumn(name, newName string) error { + // TODO + return fmt.Errorf("not supported yet") +} + +func (table *Table) FindColumn(name string) (*Column, error) { + if column, ok := table.columns[name]; ok { + return column, nil + } + // TODO + return nil, fmt.Errorf("not supported yet") +} + +func (table *Table) SetValue( + columnName string, id Int, value interface{}) error { + column, err := table.FindColumn(columnName) + if err != nil { + return err + } + return column.SetValue(id, value) +} + +// -- Column -- + +type Column struct { + *GrnColumn + *GrnxxColumn +} + +func (column *Column) SetValue(id Int, value interface{}) error { + // TODO + return fmt.Errorf("not suported yet") +} Added: go3/gnx/gnx_cgo.c (+1 -0) 100644 =================================================================== --- /dev/null +++ go3/gnx/gnx_cgo.c 2015-04-28 19:27:43 +0900 (3957c21) @@ -0,0 +1 @@ +#include "gnx_cgo.h" Added: go3/gnx/gnx_cgo.h (+8 -0) 100644 =================================================================== --- /dev/null +++ go3/gnx/gnx_cgo.h 2015-04-28 19:27:43 +0900 (970bd6b) @@ -0,0 +1,8 @@ +#ifndef GNX_CGO_H +#define GNX_CGO_H + +#include <stdlib.h> + +#include <groonga.h> + +#endif // GNX_CGO_H Added: go3/gnx/gnx_test.go (+1 -0) 100644 =================================================================== --- /dev/null +++ go3/gnx/gnx_test.go 2015-04-28 19:27:43 +0900 (fa907c6) @@ -0,0 +1 @@ +package gnx Added: go3/gnx/grn.go (+534 -0) 100644 =================================================================== --- /dev/null +++ go3/gnx/grn.go 2015-04-28 19:27:43 +0900 (13c492e) @@ -0,0 +1,534 @@ +package gnx + +/* +#cgo pkg-config: groonga +#include "grn_cgo.h" +*/ +import "C" + +import ( + "fmt" + "reflect" + "strings" + "unsafe" +) + +// -- Groonga -- + +// grnInitCount is a counter for automatically initializing and finalizing +// Groonga. +var grnInitCount = 0 + +// DisableGrnInitCount() disables grnInitCount. +// This is useful if you want to manyally initialize and finalize Groonga. +func DisableGrnInitCount() { + grnInitCount = -1 +} + +// GrnInit() initializes Groonga if needed. +// grnInitCount is incremented and when it changes from 0 to 1, Groonga is +// initialized. +func GrnInit() error { + switch grnInitCount { + case -1: // Disabled. + return nil + case 0: + if rc := C.grn_init(); rc != C.GRN_SUCCESS { + return fmt.Errorf("grn_init() failed: rc = %d", rc) + } + } + grnInitCount++ + return nil +} + +// GrnFin() finalizes Groonga if needed. +// grnInitCount is decremented and when it changes from 1 to 0, Groonga is +// finalized. +func GrnFin() error { + switch grnInitCount { + case -1: // Disabled. + return nil + case 0: + return fmt.Errorf("Groonga is not initialized yet") + case 1: + if rc := C.grn_fin(); rc != C.GRN_SUCCESS { + return fmt.Errorf("grn_fin() failed: rc = %d", rc) + } + } + grnInitCount-- + return nil +} + +// openGrnCtx() allocates memory for grn_ctx and initializes it. +func openGrnCtx() (*C.grn_ctx, error) { + if err := GrnInit(); err != nil { + return nil, err + } + ctx := C.grn_ctx_open(0) + if ctx == nil { + GrnFin() + return nil, fmt.Errorf("grn_ctx_open() failed") + } + return ctx, nil +} + +// closeGrnCtx() finalizes grn_ctx and frees allocated memory. +func closeGrnCtx(ctx *C.grn_ctx) error { + rc := C.grn_ctx_close(ctx) + GrnFin() + if rc != C.GRN_SUCCESS { + return fmt.Errorf("grn_ctx_close() failed: rc = %d", rc) + } + return nil +} + +// -- GrnDB -- + +type GrnDB struct { + ctx *C.grn_ctx + obj *C.grn_obj + tables map[string]*GrnTable +} + +// newGrnDB() creates a new GrnDB object. +func newGrnDB(ctx *C.grn_ctx, obj *C.grn_obj) *GrnDB { + return &GrnDB{ctx, obj, make(map[string]*GrnTable)} +} + +// CreateGrnDB() creates a Groonga database and returns a handle to it. +// A temporary database is created if path is empty. +func CreateGrnDB(path string) (*GrnDB, error) { + ctx, err := openGrnCtx() + if err != nil { + return nil, err + } + var cPath *C.char + if path != "" { + cPath = C.CString(path) + defer C.free(unsafe.Pointer(cPath)) + } + obj := C.grn_db_create(ctx, cPath, nil) + if obj == nil { + closeGrnCtx(ctx) + errMsg := C.GoString(&ctx.errbuf[0]) + return nil, fmt.Errorf("grn_db_create() failed: err = %s", errMsg) + } + return newGrnDB(ctx, obj), nil +} + +// OpenGrnDB() opens an existing Groonga database and returns a handle. +func OpenGrnDB(path string) (*GrnDB, error) { + ctx, err := openGrnCtx() + if err != nil { + return nil, err + } + cPath := C.CString(path) + defer C.free(unsafe.Pointer(cPath)) + obj := C.grn_db_open(ctx, cPath) + if obj == nil { + closeGrnCtx(ctx) + errMsg := C.GoString(&ctx.errbuf[0]) + return nil, fmt.Errorf("grn_db_open() failed: err = %s", errMsg) + } + return newGrnDB(ctx, obj), nil +} + +// Close() closes a handle. +func (db *GrnDB) Close() error { + return closeGrnCtx(db.ctx) +} + +// Send() sends a raw command. +// The given command must be well-formed. +func (db *GrnDB) Send(command string) error { + commandBytes := []byte(command) + var cCommand *C.char + if len(commandBytes) != 0 { + cCommand = (*C.char)(unsafe.Pointer(&commandBytes[0])) + } + rc := C.grn_ctx_send(db.ctx, cCommand, C.uint(len(commandBytes)), 0) + switch { + case rc != C.GRN_SUCCESS: + errMsg := C.GoString(&db.ctx.errbuf[0]) + return fmt.Errorf("grn_ctx_send() failed: rc = %d, err = %s", rc, errMsg) + case db.ctx.rc != C.GRN_SUCCESS: + errMsg := C.GoString(&db.ctx.errbuf[0]) + return fmt.Errorf("grn_ctx_send() failed: ctx.rc = %d, err = %s", + db.ctx.rc, errMsg) + } + return nil +} + +// SendEx() sends a command with separated options. +func (db *GrnDB) SendEx(name string, options map[string]string) error { + if name == "" { + return fmt.Errorf("invalid command: name = <%s>", name) + } + for _, r := range name { + if (r != '_') && (r < 'a') && (r > 'z') { + return fmt.Errorf("invalid command: name = <%s>", name) + } + } + commandParts := []string{name} + for key, value := range options { + if key == "" { + return fmt.Errorf("invalid option: key = <%s>", key) + } + for _, r := range key { + if (r != '_') && (r < 'a') && (r > 'z') { + return fmt.Errorf("invalid option: key = <%s>", key) + } + } + value = strings.Replace(value, "\\", "\\\\", -1) + value = strings.Replace(value, "'", "\\'", -1) + commandParts = append(commandParts, fmt.Sprintf("--%s '%s'", key, value)) + } + return db.Send(strings.Join(commandParts, " ")) +} + +// Recv() receives the result of commands sent by Send(). +func (db *GrnDB) Recv() ([]byte, error) { + var resultBuffer *C.char + var resultLength C.uint + var flags C.int + rc := C.grn_ctx_recv(db.ctx, &resultBuffer, &resultLength, &flags) + switch { + case rc != C.GRN_SUCCESS: + errMsg := C.GoString(&db.ctx.errbuf[0]) + return nil, fmt.Errorf( + "grn_ctx_recv() failed: rc = %d, err = %s", rc, errMsg) + case db.ctx.rc != C.GRN_SUCCESS: + errMsg := C.GoString(&db.ctx.errbuf[0]) + return nil, fmt.Errorf( + "grn_ctx_recv() failed: ctx.rc = %d, err = %s", db.ctx.rc, errMsg) + } + result := C.GoBytes(unsafe.Pointer(resultBuffer), C.int(resultLength)) + return result, nil +} + +// Query() sends a raw command and receive the result. +func (db *GrnDB) Query(command string) ([]byte, error) { + if err := db.Send(command); err != nil { + result, _ := db.Recv() + return result, err + } + return db.Recv() +} + +// QueryEx() sends a command with separated options and receives the result. +func (db *GrnDB) QueryEx(name string, options map[string]string) ( + []byte, error) { + if err := db.SendEx(name, options); err != nil { + result, _ := db.Recv() + return result, err + } + return db.Recv() +} + +// CreateTable() creates a table. +func (db *GrnDB) CreateTable(name string, options *TableOptions) (*GrnTable, error) { + if options == nil { + options = NewTableOptions() + } + optionsMap := make(map[string]string) + optionsMap["name"] = name + switch options.TableType { + case ArrayTable: + optionsMap["flags"] = "TABLE_NO_KEY" + case HashTable: + optionsMap["flags"] = "TABLE_HASH_KEY" + case PatTable: + optionsMap["flags"] = "TABLE_PAT_KEY" + case DatTable: + optionsMap["flags"] = "TABLE_DAT_KEY" + default: + return nil, fmt.Errorf("undefined table type: options = %+v", options) + } + if options.WithSIS { + optionsMap["flags"] += "|KEY_WITH_SIS" + } + if options.KeyType != "" { + switch (options.KeyType) { + case "Bool": + optionsMap["key_type"] = "Bool" + case "Int": + optionsMap["key_type"] = "Int64" + case "Float": + optionsMap["key_type"] = "Float" + case "GeoPoint": + optionsMap["key_type"] = "WGS84GeoPoint" + case "Text": + optionsMap["key_type"] = "ShortText" + default: + if _, err := db.FindTable(options.KeyType); err != nil { + return nil, fmt.Errorf("unsupported key type: options = %+v", options) + } + optionsMap["key_type"] = options.KeyType + } + } + if options.ValueType != "" { + switch (options.ValueType) { + case "Bool": + optionsMap["value_type"] = "Bool" + case "Int": + optionsMap["value_type"] = "Int64" + case "Float": + optionsMap["value_type"] = "Float" + case "GeoPoint": + optionsMap["value_type"] = "WGS84GeoPoint" + default: + if _, err := db.FindTable(options.ValueType); err != nil { + return nil, fmt.Errorf("unsupported value type: options = %+v", + options) + } + optionsMap["value_type"] = options.ValueType + } + } + if options.DefaultTokenizer != "" { + optionsMap["default_tokenizer"] = options.DefaultTokenizer + } + if options.Normalizer != "" { + optionsMap["normalizer"] = options.Normalizer + } + if len(options.TokenFilters) != 0 { + optionsMap["token_filters"] = strings.Join(options.TokenFilters, ",") + } + bytes, err := db.QueryEx("table_create", optionsMap) + if err != nil { + return nil, err + } + if string(bytes) != "true" { + return nil, fmt.Errorf("table_create failed: name = <%s>", name) + } + return db.FindTable(name) +} + +// FindTable() finds a table. +func (db *GrnDB) FindTable(name string) (*GrnTable, error) { + if table, ok := db.tables[name]; ok { + return table, nil + } + nameBytes := []byte(name) + var cName *C.char + if len(nameBytes) != 0 { + cName = (*C.char)(unsafe.Pointer(&nameBytes[0])) + } + obj := C.grn_cgo_find_table(db.ctx, cName, C.int(len(nameBytes))) + if obj == nil { + return nil, fmt.Errorf("table not found: name = <%s>", name) + } + var keyInfo C.grn_cgo_type_info + if ok := C.grn_cgo_table_get_key_info(db.ctx, obj, &keyInfo); ok != C.GRN_TRUE { + return nil, fmt.Errorf("grn_cgo_table_get_key_info() failed: name = <%s>", + name) + } + // Check the key type. + var keyType TypeID + switch keyInfo.data_type { + case C.GRN_DB_VOID: + keyType = VoidID + case C.GRN_DB_BOOL: + keyType = BoolID + case C.GRN_DB_INT64: + keyType = IntID + case C.GRN_DB_FLOAT: + keyType = FloatID + case C.GRN_DB_WGS84_GEO_POINT: + keyType = GeoPointID + case C.GRN_DB_SHORT_TEXT: + keyType = TextID + default: + return nil, fmt.Errorf("unsupported key type: data_type = %d", + keyInfo.data_type) + } + // Find the destination table if the key is table reference. + var keyTable *GrnTable + if keyInfo.ref_table != nil { + if keyType == VoidID { + return nil, fmt.Errorf("reference to void: name = <%s>", name) + } + cKeyTableName := C.grn_cgo_table_get_name(db.ctx, keyInfo.ref_table) + if cKeyTableName == nil { + return nil, fmt.Errorf("grn_cgo_table_get_name() failed") + } + defer C.free(unsafe.Pointer(cKeyTableName)) + var err error + keyTable, err = db.FindTable(C.GoString(cKeyTableName)) + if err != nil { + return nil, err + } + } + table := newGrnTable(db, obj, name, keyType, keyTable) + db.tables[name] = table + return table, nil +} + +// CreateColumn() creates a column. +func (db *GrnDB) CreateColumn(tableName, columnName string, valueType string, options *ColumnOptions) (*GrnColumn, error) { + table, err := db.FindTable(tableName) + if err != nil { + return nil, err + } + return table.CreateColumn(columnName, valueType, options) +} + +// FindColumn() finds a column. +func (db *GrnDB) FindColumn(tableName, columnName string) (*GrnColumn, error) { + table, err := db.FindTable(tableName) + if err != nil { + return nil, err + } + return table.FindColumn(columnName) +} + +// -- GrnTable -- + +type GrnTable struct { + db *GrnDB + obj *C.grn_obj + name string + keyType TypeID + keyTable *GrnTable + columns map[string]*GrnColumn +} + +// newGrnTable() creates a new GrnTable object. +func newGrnTable(db *GrnDB, obj *C.grn_obj, name string, keyType TypeID, + keyTable *GrnTable) *GrnTable { + var table GrnTable + table.db = db + table.obj = obj + table.name = name + table.keyType = keyType + table.keyTable = keyTable + table.columns = make(map[string]*GrnColumn) + return &table +} + +// CreateColumn() creates a column. +func (table *GrnTable) CreateColumn(name string, valueType string, options *ColumnOptions) (*GrnColumn, error) { + // TODO + return nil, fmt.Errorf("not supported yet") +} + +// FindColumn() finds a column. +func (table *GrnTable) FindColumn(name string) (*GrnColumn, error) { + if column, ok := table.columns[name]; ok { + return column, nil + } + // TODO + return nil, fmt.Errorf("not supported yet") +} + +// InsertVoid() inserts an empty row. +func (table *GrnTable) InsertVoid() (bool, Int, error) { + if table.keyType != VoidID { + return false, NullInt(), fmt.Errorf("key type conflict") + } + rowInfo := C.grn_cgo_table_insert_void(table.db.ctx, table.obj) + if rowInfo.id == C.GRN_ID_NIL { + return false, NullInt(), fmt.Errorf("grn_cgo_table_insert_void() failed") + } + return rowInfo.inserted == C.GRN_TRUE, Int(rowInfo.id), nil +} + +// InsertBool() inserts an empty row. +func (table *GrnTable) InsertBool(key Bool) (bool, Int, error) { + if table.keyType != BoolID { + return false, NullInt(), fmt.Errorf("key type conflict") + } + grnKey := C.grn_bool(C.GRN_FALSE) + if key == True { + grnKey = C.grn_bool(C.GRN_TRUE) + } + rowInfo := C.grn_cgo_table_insert_bool(table.db.ctx, table.obj, grnKey) + if rowInfo.id == C.GRN_ID_NIL { + return false, NullInt(), fmt.Errorf("grn_cgo_table_insert_bool() failed") + } + return rowInfo.inserted == C.GRN_TRUE, Int(rowInfo.id), nil +} + +// InsertInt() inserts an empty row. +func (table *GrnTable) InsertInt(key Int) (bool, Int, error) { + if table.keyType != IntID { + return false, NullInt(), fmt.Errorf("key type conflict") + } + grnKey := C.int64_t(key) + rowInfo := C.grn_cgo_table_insert_int(table.db.ctx, table.obj, grnKey) + if rowInfo.id == C.GRN_ID_NIL { + return false, NullInt(), fmt.Errorf("grn_cgo_table_insert_int() failed") + } + return rowInfo.inserted == C.GRN_TRUE, Int(rowInfo.id), nil +} + +// InsertFloat() inserts an empty row. +func (table *GrnTable) InsertFloat(key Float) (bool, Int, error) { + if table.keyType != FloatID { + return false, NullInt(), fmt.Errorf("key type conflict") + } + grnKey := C.double(key) + rowInfo := C.grn_cgo_table_insert_float(table.db.ctx, table.obj, grnKey) + if rowInfo.id == C.GRN_ID_NIL { + return false, NullInt(), fmt.Errorf("grn_cgo_table_insert_float() failed") + } + return rowInfo.inserted == C.GRN_TRUE, Int(rowInfo.id), nil +} + +// InsertGeoPoint() inserts an empty row. +func (table *GrnTable) InsertGeoPoint(key GeoPoint) (bool, Int, error) { + if table.keyType != GeoPointID { + return false, NullInt(), fmt.Errorf("key type conflict") + } + grnKey := C.grn_geo_point{C.int(key.Latitude), C.int(key.Longitude)} + rowInfo := C.grn_cgo_table_insert_geo_point(table.db.ctx, table.obj, grnKey) + if rowInfo.id == C.GRN_ID_NIL { + return false, NullInt(), fmt.Errorf("grn_cgo_table_insert_geo_point() failed") + } + return rowInfo.inserted == C.GRN_TRUE, Int(rowInfo.id), nil +} + +// InsertText() inserts an empty row. +func (table *GrnTable) InsertText(key Text) (bool, Int, error) { + if table.keyType != TextID { + return false, NullInt(), fmt.Errorf("key type conflict") + } + var grnKey C.grn_cgo_text + if len(key) != 0 { + grnKey.ptr = (*C.char)(unsafe.Pointer(&key[0])) + grnKey.size = C.size_t(len(key)) + } + rowInfo := C.grn_cgo_table_insert_text(table.db.ctx, table.obj, &grnKey) + if rowInfo.id == C.GRN_ID_NIL { + return false, NullInt(), fmt.Errorf("grn_cgo_table_insert_text() failed") + } + return rowInfo.inserted == C.GRN_TRUE, Int(rowInfo.id), nil +} + +// InsertRow() inserts a record. +// The first return value specifies whether a row is inserted or not. +// The second return value is the ID of the inserted or found row. +func (table *GrnTable) InsertRow(key interface{}) (bool, Int, error) { + switch value := key.(type) { + case nil: + return table.InsertVoid() + case Bool: + return table.InsertBool(value) + case Int: + return table.InsertInt(value) + case Float: + return table.InsertFloat(value) + case GeoPoint: + return table.InsertGeoPoint(value) + case Text: + return table.InsertText(value) + default: + return false, NullInt(), fmt.Errorf( + "unsupported key type: typeName = <%s>", reflect.TypeOf(key).Name()) + } +} + +// -- GrnColumn -- + +type GrnColumn struct { + obj *C.grn_obj +} Added: go3/gnx/grn_cgo.c (+129 -0) 100644 =================================================================== --- /dev/null +++ go3/gnx/grn_cgo.c 2015-04-28 19:27:43 +0900 (e2b7523) @@ -0,0 +1,129 @@ +#include "grn_cgo.h" + +#include <string.h> + +#define GRN_CGO_MAX_DATA_TYPE_ID GRN_DB_WGS84_GEO_POINT + +grn_obj *grn_cgo_find_table(grn_ctx *ctx, const char *name, int name_len) { + grn_obj *obj = grn_ctx_get(ctx, name, name_len); + if (!obj) { + return NULL; + } + switch (obj->header.type) { + case GRN_TABLE_HASH_KEY: + case GRN_TABLE_PAT_KEY: + case GRN_TABLE_DAT_KEY: + case GRN_TABLE_NO_KEY: { + return obj; + } + default: { + // The object is not a table. + return NULL; + } + } +} + +// grn_cgo_init_type_info() initializes the members of type_info. +// The initialized type info specifies a valid Void type. +static void grn_cgo_init_type_info(grn_cgo_type_info *type_info) { + type_info->data_type = GRN_DB_VOID; + type_info->dimension = 0; + type_info->ref_table = NULL; +} + +grn_bool grn_cgo_table_get_key_info(grn_ctx *ctx, grn_obj *table, + grn_cgo_type_info *key_info) { + grn_cgo_init_type_info(key_info); + while (table) { + switch (table->header.type) { + case GRN_TABLE_HASH_KEY: + case GRN_TABLE_PAT_KEY: + case GRN_TABLE_DAT_KEY: { + if (table->header.domain <= GRN_CGO_MAX_DATA_TYPE_ID) { + key_info->data_type = table->header.domain; + return GRN_TRUE; + } + table = grn_ctx_at(ctx, table->header.domain); + if (!key_info->ref_table) { + key_info->ref_table = table; + } + break; + } + case GRN_TABLE_NO_KEY: { + return GRN_TRUE; + } + default: { + return GRN_FALSE; + } + } + } + return GRN_FALSE; +} + +char *grn_cgo_table_get_name(grn_ctx *ctx, grn_obj *table) { + if (!table) { + return NULL; + } + switch (table->header.type) { + case GRN_TABLE_HASH_KEY: + case GRN_TABLE_PAT_KEY: + case GRN_TABLE_DAT_KEY: + case GRN_TABLE_NO_KEY: { + break; + } + default: { + return NULL; + } + } + char buf[GRN_TABLE_MAX_KEY_SIZE]; + int len = grn_obj_name(ctx, table, buf, GRN_TABLE_MAX_KEY_SIZE); + if (len <= 0) { + return NULL; + } + char *table_name = (char *)malloc(len + 1); + if (!table_name) { + return NULL; + } + memcpy(table_name, buf, len); + table_name[len] = '\0'; + return table_name; +} + +// grn_cgo_table_insert_row() calls grn_table_add() and converts the result. +static grn_cgo_row_info grn_cgo_table_insert_row( + grn_ctx *ctx, grn_obj *table, const void *key_ptr, size_t key_size) { + grn_cgo_row_info row_info; + int inserted; + row_info.id = grn_table_add(ctx, table, key_ptr, key_size, &inserted); + row_info.inserted = inserted ? GRN_TRUE : GRN_FALSE; + return row_info; +} + +grn_cgo_row_info grn_cgo_table_insert_void(grn_ctx *ctx, grn_obj *table) { + return grn_cgo_table_insert_row(ctx, table, NULL, 0); +} + +grn_cgo_row_info grn_cgo_table_insert_bool(grn_ctx *ctx, grn_obj *table, + grn_bool key) { + return grn_cgo_table_insert_row(ctx, table, &key, sizeof(key)); +} + +grn_cgo_row_info grn_cgo_table_insert_int(grn_ctx *ctx, grn_obj *table, + int64_t key) { + return grn_cgo_table_insert_row(ctx, table, &key, sizeof(key)); +} + +grn_cgo_row_info grn_cgo_table_insert_float(grn_ctx *ctx, grn_obj *table, + double key) { + return grn_cgo_table_insert_row(ctx, table, &key, sizeof(key)); +} + +grn_cgo_row_info grn_cgo_table_insert_geo_point(grn_ctx *ctx, grn_obj *table, + grn_geo_point key) { + return grn_cgo_table_insert_row(ctx, table, &key, sizeof(key)); +} + +grn_cgo_row_info grn_cgo_table_insert_text(grn_ctx *ctx, grn_obj *table, + const grn_cgo_text *key) { + return grn_cgo_table_insert_row(ctx, table, key->ptr, key->size); +} Added: go3/gnx/grn_cgo.h (+59 -0) 100644 =================================================================== --- /dev/null +++ go3/gnx/grn_cgo.h 2015-04-28 19:27:43 +0900 (886f814) @@ -0,0 +1,59 @@ +#ifndef GRN_CGO_H +#define GRN_CGO_H + +#include <stdint.h> +#include <stdlib.h> + +#include <groonga.h> + +typedef struct { + const char *ptr; + size_t size; +} grn_cgo_text; + +// grn_cgo_find_table() finds a table with the given name. +// If found, an object associated with the table is returned. +// If not found, NULL is returned. +grn_obj *grn_cgo_find_table(grn_ctx *ctx, const char *name, int name_len); + +typedef struct { + grn_id data_type; // Data type (GRN_DB_VOID, GRN_DB_BOOL, etc.). + // If the type is table reference, the key type of the + // referenced table is stored. + int dimension; // Vector depth, 0 means the type is scalar. + grn_obj *ref_table; // The referenced table of table reference. +} grn_cgo_type_info; + +// grn_cgo_table_get_key_info() gets information of the table key. +grn_bool grn_cgo_table_get_key_info(grn_ctx *ctx, grn_obj *table, + grn_cgo_type_info *key_info); + +// grn_cgo_table_get_name() returns the name of table. +// On success, a non-NULL pointer is returned and it must be freed by free(). +// On failure, NULL is returned. +char *grn_cgo_table_get_name(grn_ctx *ctx, grn_obj *table); + +typedef struct { + grn_id id; // Row ID, GRN_ID_NIL means the info is invalid. + grn_bool inserted; // Inserted or not. +} grn_cgo_row_info; + +// grn_cgo_table_insert_void() inserts an empty row. +grn_cgo_row_info grn_cgo_table_insert_void(grn_ctx *ctx, grn_obj *table); +// grn_cgo_table_insert_bool() inserts a row with Bool key. +grn_cgo_row_info grn_cgo_table_insert_bool(grn_ctx *ctx, grn_obj *table, + grn_bool key); +// grn_cgo_table_insert_int() inserts a row with Int key. +grn_cgo_row_info grn_cgo_table_insert_int(grn_ctx *ctx, grn_obj *table, + int64_t key); +// grn_cgo_table_insert_float() inserts a row with Float key. +grn_cgo_row_info grn_cgo_table_insert_float(grn_ctx *ctx, grn_obj *table, + double key); +// grn_cgo_table_insert_geo_point() inserts a row with GeoPoint key. +grn_cgo_row_info grn_cgo_table_insert_geo_point(grn_ctx *ctx, grn_obj *table, + grn_geo_point key); +// grn_cgo_table_insert_text() inserts a row with Text key. +grn_cgo_row_info grn_cgo_table_insert_text(grn_ctx *ctx, grn_obj *table, + const grn_cgo_text *key); + +#endif // GRN_CGO_H Added: go3/gnx/grn_test.go (+158 -0) 100644 =================================================================== --- /dev/null +++ go3/gnx/grn_test.go 2015-04-28 19:27:43 +0900 (e9b99e4) @@ -0,0 +1,158 @@ +package gnx + +import ( + "io/ioutil" + "math/rand" + "os" + "strconv" + "testing" +) + +func createTempGrnDB(tb testing.TB) (string, string, *GrnDB) { + dirPath, err := ioutil.TempDir("", "grn_test") + if err != nil { + tb.Fatalf("ioutil.TempDir() failed: %v", err) + } + dbPath := dirPath + "/db" + db, err := CreateGrnDB(dbPath) + if err != nil { + os.RemoveAll(dirPath) + tb.Fatalf("CreateGrnDB() failed: %v", err) + } + return dirPath, dbPath, db +} + +func removeTempGrnDB(tb testing.TB, dirPath string, db *GrnDB) { + if err := db.Close(); err != nil { + os.RemoveAll(dirPath) + tb.Fatalf("GrnDB.Close() failed: %v", err) + } + if err := os.RemoveAll(dirPath); err != nil { + tb.Fatalf("os.RemoveAll() failed: %v", err) + } +} + +func createTempGrnTable(tb testing.TB, name string, options *TableOptions) ( + string, string, *GrnDB, *GrnTable) { + dirPath, dbPath, db := createTempGrnDB(tb) + table, err := db.CreateTable(name, options) + if err != nil { + removeTempGrnDB(tb, dirPath, db) + tb.Fatalf("GrnDB.CreateTable() failed: %v", err) + } + return dirPath, dbPath, db, table +} + +func TestCreateGrnDB(t *testing.T) { + dirPath, _, db := createTempGrnDB(t) + removeTempGrnDB(t, dirPath, db) +} + +func TestOpenGrnDB(t *testing.T) { + dirPath, dbPath, db := createTempGrnDB(t) + db2, err := OpenGrnDB(dbPath) + if err != nil { + t.Fatalf("OpenGrnDB() failed: %v", err) + } + db2.Close() + removeTempGrnDB(t, dirPath, db) +} + +func testGrnDBCreateTableWithKey(t *testing.T, keyType string) { + options := NewTableOptions() + options.TableType = PatTable + options.KeyType = keyType + dirPath, _, db, _ := createTempGrnTable(t, "Table", options) + removeTempGrnDB(t, dirPath, db) +} + +func testGrnDBCreateTableRef(t *testing.T, keyType string) { + options := NewTableOptions() + options.TableType = PatTable + options.KeyType = keyType + dirPath, _, db, _ := createTempGrnTable(t, "Table", options) + defer removeTempGrnDB(t, dirPath, db) + + options.KeyType = "Table" + _, err := db.CreateTable("Table2", options) + if err != nil { + t.Fatalf("GrnDB.CreateTable() failed: %v", err) + } +} + +func TestGrnDBCreateTable(t *testing.T) { + dirPath, _, db, _ := createTempGrnTable(t, "Table", nil) + removeTempGrnDB(t, dirPath, db) + + testGrnDBCreateTableWithKey(t, "Bool") + testGrnDBCreateTableWithKey(t, "Int") + testGrnDBCreateTableWithKey(t, "Float") + testGrnDBCreateTableWithKey(t, "GeoPoint") + testGrnDBCreateTableWithKey(t, "Text") + + testGrnDBCreateTableRef(t, "Bool") + testGrnDBCreateTableRef(t, "Int") + testGrnDBCreateTableRef(t, "Float") + testGrnDBCreateTableRef(t, "GeoPoint") + testGrnDBCreateTableRef(t, "Text") +} + +func generateRandomKey(keyType string) interface{} { + switch keyType { + case "Bool": + if (rand.Int() & 1) == 1 { + return True + } else { + return False + } + case "Int": + return Int(rand.Int63()) + case "Float": + return Float(rand.Float64()) + case "GeoPoint": + const ( + MinLatitude = 73531000 + MaxLatitude = 164006000 + MinLongitude = 439451000 + MaxLongitude = 554351000 + ) + latitude := MinLatitude + rand.Intn(MaxLatitude-MinLatitude+1) + longitude := MinLongitude + rand.Intn(MaxLongitude-MinLongitude+1) + return GeoPoint{ int32(latitude), int32(longitude) } + case "Text": + return Text(strconv.Itoa(rand.Int())) + default: + return nil + } +} + +func testGrnTableInsertRow(t *testing.T, keyType string) { + options := NewTableOptions() + if keyType != "" { + options.TableType = PatTable + } + options.KeyType = keyType + dirPath, _, db, table := createTempGrnTable(t, "Table", options) + defer removeTempGrnDB(t, dirPath, db) + + count := 0 + for i := 0; i < 100; i++ { + inserted, _, err := table.InsertRow(generateRandomKey(keyType)) + if err != nil { + t.Fatalf("GrnTable.InsertRow() failed: %v", err) + } + if inserted { + count++ + } + } + t.Logf("keyType = <%s>, count = %d", keyType, count) +} + +func TestGrnTableInsertRow(t *testing.T) { + testGrnTableInsertRow(t, "") + testGrnTableInsertRow(t, "Bool") + testGrnTableInsertRow(t, "Int") + testGrnTableInsertRow(t, "Float") + testGrnTableInsertRow(t, "GeoPoint") + testGrnTableInsertRow(t, "Text") +} Added: go3/gnx/grnxx.go (+28 -0) 100644 =================================================================== --- /dev/null +++ go3/gnx/grnxx.go 2015-04-28 19:27:43 +0900 (93222c5) @@ -0,0 +1,28 @@ +package gnx + +import ( + "fmt" +) + +// -- GrnxxDB -- + +type GrnxxDB struct { + // TODO +} + +func (db *GrnxxDB) Close() error { + // TODO + return fmt.Errorf("not supported yet") +} + +// -- GrnxxTable -- + +type GrnxxTable struct { + // TODO +} + +// -- GrnxxColumn -- + +type GrnxxColumn struct { + // TODO +} Added: go3/gnx/grnxx_cgo.cpp (+1 -0) 100644 =================================================================== --- /dev/null +++ go3/gnx/grnxx_cgo.cpp 2015-04-28 19:27:43 +0900 (0fd5c26) @@ -0,0 +1 @@ +#include "grnxx_cgo.h" Added: go3/gnx/grnxx_cgo.h (+6 -0) 100644 =================================================================== --- /dev/null +++ go3/gnx/grnxx_cgo.h 2015-04-28 19:27:43 +0900 (90f8462) @@ -0,0 +1,6 @@ +#ifndef GRNXX_CGO_H +#define GRNXX_CGO_H + +// TODO + +#endif // GRNXX_CGO_H Added: go3/gnx/options.go (+68 -0) 100644 =================================================================== --- /dev/null +++ go3/gnx/options.go 2015-04-28 19:27:43 +0900 (6be939d) @@ -0,0 +1,68 @@ +package gnx + +//import ( +// "" +//) + +// -- TableOptions -- + +// Constants for TableOptions. +type TableType int + +const ( + ArrayTable = TableType(iota) + HashTable + PatTable + DatTable +) + +// http://groonga.org/docs/reference/commands/table_create.html +type TableOptions struct { + TableType + WithSIS bool // KEY_WITH_SIS + KeyType string // http://groonga.org/docs/reference/types.html + ValueType string // http://groonga.org/docs/reference/types.html + DefaultTokenizer string // http://groonga.org/docs/reference/tokenizers.html + Normalizer string // http://groonga.org/docs/reference/normalizers.html + TokenFilters []string // http://groonga.org/docs/reference/token_filters.html +} + +func NewTableOptions() *TableOptions { + var options TableOptions + return &options +} + +// -- ColumnOptions -- + +// Constants for ColumnOptions. +type ColumnType int + +const ( + ScalarColumn = ColumnType(iota) + VectorColumn + IndexColumn +) + +// Constants for ColumnOptions. +type CompressionType int + +const ( + NoCompression = CompressionType(iota) + ZlibCompression + LzoCompression +) + +// http://groonga.org/ja/docs/reference/commands/column_create.html +type ColumnOptions struct { + ColumnType + CompressionType + WithSection bool // WITH_SECTION + WithWeight bool // WITH_WEIGHT + WithPosition bool // WITH_POSITION + Source string +} + +func NewColumnOptions() *ColumnOptions { + var options ColumnOptions + return &options +} -------------- next part -------------- HTML����������������������������... Descargar