susumu.yata
null+****@clear*****
Fri Mar 20 21:42:11 JST 2015
susumu.yata 2015-03-20 21:42:11 +0900 (Fri, 20 Mar 2015) New Revision: f0cbcc86cb2879845e076cc12a583aeb2a6516c6 https://github.com/groonga/grnxx/commit/f0cbcc86cb2879845e076cc12a583aeb2a6516c6 Message: Gnx: first commit for Gnx. Added files: go/.gitignore go/benchmark.go go/gnx/gnx.go go/gnx/grnxx/grnxx.cpp go/gnx/grnxx/grnxx.go go/gnx/grnxx/grnxx.h go/gnx/grnxx/grnxx_test.go go/gnx/groonga/groonga.go go/gnx/groonga/groonga_test.go go/gnxConsole.go go/groongaConsole.go Added: go/.gitignore (+3 -0) 100644 =================================================================== --- /dev/null +++ go/.gitignore 2015-03-20 21:42:11 +0900 (1ffe89b) @@ -0,0 +1,3 @@ +benchmark +gnxConsole +groongaConsole Added: go/benchmark.go (+173 -0) 100644 =================================================================== --- /dev/null +++ go/benchmark.go 2015-03-20 21:42:11 +0900 (8568cc1) @@ -0,0 +1,173 @@ +package main + +import "./gnx" + +import "encoding/json" +import "flag" +import "fmt" +import "log" +import "math/rand" +import "os" +import "runtime" +import "runtime/pprof" +import "strconv" +import "strings" +import "unicode" + +var flagMode = flag.String("mode", "run", "mode (run or print)") +var flagLoad = flag.String("load", "load", "load type (load or gnx_load)") +var flagKey = flag.String("key", "", "key data type") +var flagColumns = flag.String("columns", "Bool,Int16,Float,ShortText", + "comma-separated column data types") +var flagRows = flag.Int("rows", 1000000, "number of rows") +var flagBlock = flag.Int("block", 10, "number of rows per command") +var flagPartition = flag.Int("partition", 1, "number of groonga DBs") +var flagCPU = flag.Int("cpu", 1, "number of CPU cores") +var flagProfile = flag.String("profile", "benchmark.prof", + "path of profile data") + +func generateValue(typeName string) interface{} { + switch typeName { + case "Bool": + return (rand.Int() % 2) == 1 + case "Int8": + return int8(rand.Int() % 128) + case "Int16": + return int16(rand.Int() % 32768) + case "Int32": + return rand.Int31() + case "Int64": + return rand.Int63() + case "UInt8": + return uint8(rand.Int() % 256) + case "UInt16": + return uint16(rand.Int() % 65536) + case "UInt32": + return rand.Uint32() + case "UInt64": + return uint64(rand.Int63()) + case "Float": + return rand.Float64() + case "ShortText", "Text", "LongText": + return strconv.Itoa(int(rand.Int31())) + default: + log.Fatalln("unsupported data type: name =", typeName) + return nil + } +} + +func generateCommands() []string { + var commands []string + if *flagKey == "" { + commands = append(commands, "table_create Table TABLE_NO_KEY") + } else { + commands = append(commands, + fmt.Sprintf("table_create Table TABLE_PAT_KEY %s", *flagKey)) + } + + var columnTypes []string + var columnNames []string + if *flagColumns != "" { + tokens := strings.Split(*flagColumns, ",") + for _, token := range tokens { + columnType := strings.TrimFunc(token, unicode.IsSpace) + if columnType != "" { + columnTypes = append(columnTypes, columnType) + columnName := fmt.Sprintf("Col%d", len(columnNames)) + columnNames = append(columnNames, columnName) + commands = append(commands, + fmt.Sprintf("column_create Table %s COLUMN_SCALAR %s", + columnName, columnType)) + } + } + } + + columnsOption := strings.Join(columnNames, ",") + numValues := len(columnNames) + if *flagKey != "" { + if columnsOption == "" { + columnsOption = "_key" + } else { + columnsOption = "_key," + columnsOption + } + numValues += 1 + } + loadPrefix := fmt.Sprintf("%s --table Table --columns '%s' --values ", + *flagLoad, columnsOption) + for i := 0; i < *flagRows; i += *flagBlock { + var blockValues [][]interface{} + for j := 0; (j < *flagBlock) && ((i + j) < *flagRows); j++ { + var rowValues []interface{} + if *flagKey != "" { + rowValues = append(rowValues, generateValue(*flagKey)) + } + for _, columnType := range columnTypes { + rowValues = append(rowValues, generateValue(columnType)) + } + blockValues = append(blockValues, rowValues) + } + bytes, err := json.Marshal(blockValues) + if err != nil { + log.Fatalln(err) + } + commands = append(commands, loadPrefix+"'"+string(bytes)+"'") + } + return commands +} + +func runCommands(commands []string) { + file, err := os.Create(*flagProfile) + if err != nil { + log.Fatalln(err) + } + defer file.Close() + if err := pprof.StartCPUProfile(file); err != nil { + log.Fatalln(err) + } + defer pprof.StopCPUProfile() + + db, dir, err := gnx.CreateTempDB("", "benchmark", *flagPartition) + if err != nil { + log.Fatalln(err) + } + defer os.RemoveAll(dir) + defer db.Close() + for _, command := range commands { + if _, err := db.Query(command); err != nil { + log.Fatalln(err) + } + } +} + +func printCommands(commands []string) { + db, dir, err := gnx.CreateTempDB("", "benchmark", *flagPartition) + if err != nil { + log.Fatalln(err) + } + defer os.RemoveAll(dir) + defer db.Close() + for _, command := range commands { + fmt.Println(command) +// if _, err := db.Query(command); err != nil { +// log.Fatalln(err) +// } + } +} + +func main() { + flag.Parse() + if *flagCPU == 0 { + runtime.GOMAXPROCS(runtime.NumCPU()) + } else { + runtime.GOMAXPROCS(*flagCPU) + } + commands := generateCommands() + switch *flagMode { + case "run": + runCommands(commands) + case "print": + printCommands(commands) + default: + log.Fatalln("undefined mode") + } +} Added: go/gnx/gnx.go (+994 -0) 100644 =================================================================== --- /dev/null +++ go/gnx/gnx.go 2015-03-20 21:42:11 +0900 (90bbe64) @@ -0,0 +1,994 @@ +package gnx + +import "./grnxx" +import "./groonga" +import "encoding/binary" +import "encoding/json" +import "fmt" +import "hash/fnv" +import "io/ioutil" +import "math" +import "math/rand" +import "os" +import "strconv" +import "strings" +import "time" +import "unicode" + +type DB struct { + groongaDBs []*groonga.DB + grnxxDB *grnxx.DB +} + +func CreateDB(path string, n int) (*DB, error) { + if n <= 0 { + return nil, fmt.Errorf("invalid parameter: n = %d", n) + } + groongaDBs := make([]*groonga.DB, n) + for i := 0; i < n; i++ { + dbPath := path + if i != 0 { + dbPath += strconv.Itoa(i) + } + groongaDB, err := groonga.CreateDB(dbPath) + if err != nil { + for j := 0; j < i; j++ { + groongaDBs[j].Close() + } + return nil, err + } + groongaDBs[i] = groongaDB + } + return &DB{groongaDBs, nil}, nil +} + +func CreateTempDB(dir, prefix string, n int) (*DB, string, error) { + if n <= 0 { + return nil, "", fmt.Errorf("invalid parameter: n = %d", n) + } + tempDir, err := ioutil.TempDir(dir, prefix) + if err != nil { + return nil, "", err + } + db, err := CreateDB(tempDir+"/db", n) + if err != nil { + os.RemoveAll(tempDir) + return nil, "", err + } + return db, tempDir, nil +} + +func OpenDB(path string) (*DB, error) { + var groongaDBs []*groonga.DB + for i := 0; ; i++ { + dbPath := path + if i != 0 { + dbPath += strconv.Itoa(i) + } + groongaDB, err := groonga.OpenDB(dbPath) + if err != nil { + if i == 0 { + return nil, err + } + break + } + groongaDBs = append(groongaDBs, groongaDB) + } + return &DB{groongaDBs, nil}, nil +} + +func (db *DB) Close() error { + for i := 0; i < len(db.groongaDBs); i++ { + db.groongaDBs[i].Close() + } + if db.grnxxDB != nil { + db.grnxxDB.Close() + } + return nil +} + +func (db *DB) gnxLoad(command string) ([]byte, error) { + _, options, err := db.ParseCommand(command) + if err != nil { + return nil, err + } + columnsOption, ok := options["columns"] + if !ok { + return nil, fmt.Errorf("columns option missing") + } + idPos := -1 + keyPos := -1 + var columns []string + columnNames := strings.Split(columnsOption, ",") + for i := 0; i < len(columnNames); i++ { + columnName := strings.TrimFunc(columnNames[i], unicode.IsSpace) + if len(columnName) != 0 { + switch columnName { + case "_id": + if idPos != -1 { + return nil, fmt.Errorf("_id appeared more than once") + } + idPos = len(columns) + case "_key": + if keyPos != -1 { + return nil, fmt.Errorf("_key appeared more than once") + } + keyPos = len(columns) + } + columns = append(columns, columnName) + } + } + if (idPos != -1) && (keyPos != -1) { + return nil, fmt.Errorf("both _id and _key appeared") + } + + valuesOption, ok := options["values"] + if !ok { + return nil, fmt.Errorf("values option missing") + } + var mergedValues [][]interface{} + if err = json.Unmarshal([]byte(valuesOption), &mergedValues); err != nil { + return nil, err + } + values := make([][][]interface{}, len(db.groongaDBs)) + switch { + case idPos != -1: + // NOTE: Groonga does not support array-style values with _id. + for i := 0; i < len(mergedValues); i++ { + id := int(mergedValues[i][idPos].(float64)) + if id <= 0 { + return nil, fmt.Errorf("invalid ID: id = %d", id) + } + mergedValues[i][idPos] = ((id - 1) / len(values)) + 1 + db := (id - 1) % len(values) + values[db] = append(values[db], mergedValues[i]) + } + case keyPos != -1: + for i := 0; i < len(mergedValues); i++ { + switch value := mergedValues[i][keyPos].(type) { + case float64: + hasher := fnv.New32a() + err := binary.Write(hasher, binary.LittleEndian, value) + if err != nil { + return nil, err + } + db := int(hasher.Sum32()) % len(values) + values[db] = append(values[db], mergedValues[i]) + case string: + hasher := fnv.New32a() + if _, err := hasher.Write([]byte(value)); err != nil { + return nil, err + } + db := int(hasher.Sum32()) % len(values) + values[db] = append(values[db], mergedValues[i]) + default: + return nil, fmt.Errorf("unsupported key type") + } + } + default: + for i := 0; i < len(mergedValues); i++ { + db := rand.Int() % len(values) + values[db] = append(values[db], mergedValues[i]) + } + } + +// var channels []chan int +// for i := 0; i < len(db.groongaDBs); i++ { +// if len(values[i]) != 0 { +// channel := make(chan int) +// go func(channel chan int, db *groonga.DB, options map[string]string, +// values [][]interface{}) { +// newOptions := make(map[string]string) +// for key, value := range options { +// newOptions[key] = value +// } +// bytes, err := json.Marshal(values) +// if err != nil { +// channel <- 0 +// return +// } +// newOptions["values"] = string(bytes) +//// fmt.Println("options:", newOptions) +// count, err := db.Load(newOptions) +// if err != nil { +// channel <- 0 +// return +// } +// channel <- count +// close(channel) +// return +// }(channel, db.groongaDBs[i], options, values[i]) +// channels = append(channels, channel) +// } +// } +// count := 0 +// for _, channel := range channels { +// count += <-channel +// } +// return []byte(strconv.Itoa(count)), nil + + total := 0 + for i := 0; i < len(db.groongaDBs); i++ { + if len(values[i]) != 0 { + bytes, err := json.Marshal(values[i]) + if err != nil { + return nil, err + } + options["values"] = string(bytes) +// fmt.Println("options:", options) + count, err := db.groongaDBs[i].Load(options) + if err != nil { + return nil, err + } + total += count + } + } + return []byte(strconv.Itoa(total)), nil +} + +type Output struct { + Name string + Type string + Values interface{} +} + +func (db *DB) groongaSelect(options map[string]string) ([]byte, error) { + options["cache"] = "no" + outputColumns, err := db.groongaDBs[0].Select(options) + if err != nil { + return nil, err + } + if len(outputColumns) == 0 { + return nil, fmt.Errorf("no output columns") + } + for i := 0; i < len(outputColumns); i++ { + if outputColumns[i].Name == "_id" { + values := outputColumns[i].Values.([]uint32) + for j := 0; j < len(values); j++ { + values[j] = uint32(((int(values[j]) - 1) * len(db.groongaDBs)) + 1) + } + } + } + for i := 1; i < len(db.groongaDBs); i++ { + result, err := db.groongaDBs[i].Select(options) + if err != nil { + return nil, err + } + for j := 0; j < len(result); j++ { + switch newValues := result[j].Values.(type) { + case []bool: + values := outputColumns[j].Values.([]bool) + values = append(values, newValues...) + outputColumns[j].Values = values + case []int8: + values := outputColumns[j].Values.([]int8) + values = append(values, newValues...) + outputColumns[j].Values = values + case []int16: + values := outputColumns[j].Values.([]int16) + values = append(values, newValues...) + outputColumns[j].Values = values + case []int32: + values := outputColumns[j].Values.([]int32) + values = append(values, newValues...) + outputColumns[j].Values = values + case []int64: + values := outputColumns[j].Values.([]int64) + values = append(values, newValues...) + outputColumns[j].Values = values + case []uint8: + values := outputColumns[j].Values.([]uint8) + values = append(values, newValues...) + outputColumns[j].Values = values + case []uint16: + values := outputColumns[j].Values.([]uint16) + values = append(values, newValues...) + outputColumns[j].Values = values + case []uint32: + if result[j].Name == "_id" { + for k := 0; k < len(newValues); k++ { + newValues[k] = + uint32(((int(newValues[k]) - 1) * len(db.groongaDBs)) + i + 1) + } + } + values := outputColumns[j].Values.([]uint32) + values = append(values, newValues...) + outputColumns[j].Values = values + case []uint64: + values := outputColumns[j].Values.([]uint64) + values = append(values, newValues...) + outputColumns[j].Values = values + case []float64: + values := outputColumns[j].Values.([]float64) + values = append(values, newValues...) + outputColumns[j].Values = values + case []time.Time: + values := outputColumns[j].Values.([]time.Time) + values = append(values, newValues...) + outputColumns[j].Values = values + case []string: + values := outputColumns[j].Values.([]string) + values = append(values, newValues...) + outputColumns[j].Values = values + } + } + } + + bytes, err := json.Marshal(outputColumns) + if err != nil { + return nil, err + } + return bytes, nil +} + +//func (db *DB) groongaSelect(options map[string]string) ([]byte, error) { +// options["cache"] = "no" +// count := 0 +// results := make([][][][]interface{}, len(db.groongaDBs)) +// for i := 0; i < len(results); i++ { +// bytes, err := db.groongaDBs[i].QueryEx("select", options) +// if err != nil { +// return nil, err +// } +// if err = json.Unmarshal(bytes, &results[i]); err != nil { +// return nil, err +// } +// count += len(results[i][0]) - 2 +// } +// var ids []int +// for i := 0; i < len(results[0][0][1]); i++ { +// name := results[0][0][1][i].([]interface{})[0].(string) +// if name == "_id" { +// ids = append(ids, i) +// } +// } +// mergedResult := make([][]interface{}, 1) +// mergedResult[0] = make([]interface{}, count+2) +// mergedResult[0][0] = []int{count} +// mergedResult[0][1] = results[0][0][1] +// output := mergedResult[0][2:] +// for i := 0; i < len(results); i++ { +// input := results[i][0][2:] +// for j := 0; j < len(input); j++ { +// output[j] = input[j] +// for _, idPos := range ids { +// id := int(input[j][idPos].(float64)) +// input[j][idPos] = ((id - 1) * len(results)) + i + 1 +// } +// } +// output = output[len(input):] +// } +// bytes, err := json.Marshal(mergedResult) +// if err != nil { +// return nil, err +// } +// return bytes, nil +//} + +func (db *DB) grnxxSelect(options map[string]string) ([]byte, error) { +// fmt.Println("grnxxSelect: options:", options) + if db.grnxxDB == nil { + return nil, fmt.Errorf("grnxx not available") + } + + table := db.grnxxDB.FindTable(options["table"]) + if table == nil { + return nil, fmt.Errorf("table not found: name = %s", options["table"]) + } + builder, err := grnxx.CreatePipelineBuilder(table) + if err != nil { + return nil, err + } + defer builder.Close() + cursor, err := table.CreateCursor(nil) + if err != nil { + return nil, err + } + builder.PushCursor(cursor) + + filterString, ok := options["grnxx_filter"] + if !ok { + filterString = "TRUE" + } + offset := 0 + offsetString, ok := options["grnxx_offset"] + if ok { + offset, err = strconv.Atoi(offsetString) + if err != nil { + return nil, err + } + } + limit := math.MaxInt32 + limitString, ok := options["grnxx_limit"] + if ok { + limit, err = strconv.Atoi(limitString) + if err != nil { + return nil, err + } + } + filter, err := grnxx.ParseExpression(table, filterString) + if err != nil { + return nil, err + } + if err = builder.PushFilter(filter, offset, limit); err != nil { + return nil, err + } + + pipeline, err := builder.Release(nil) + if err != nil { + return nil, err + } + defer pipeline.Close() + records, err := pipeline.Flush() + if err != nil { + return nil, err + } +// fmt.Println("records:", records) + + var outputs []Output + optionValue, ok := options["grnxx_output_columns"] + if ok { + values := strings.Split(optionValue, ",") + for _, value := range values { + value = strings.TrimFunc(value, unicode.IsSpace) + if len(value) != 0 { + outputs = append(outputs, Output{Name: value}) + } + } + } else { + outputs = append(outputs, Output{Name: "_id"}) + numColumns := table.NumColumns() + for i := 0; i < numColumns; i++ { + outputs = append(outputs, Output{Name: table.GetColumn(i).Name()}) + } + } +// fmt.Println("outputs:", outputs) + + for i := 0; i < len(outputs); i++ { + expression, err := grnxx.ParseExpression(table, outputs[i].Name) + if err != nil { + return nil, err + } + defer expression.Close() + + switch expression.DataType() { + case grnxx.BOOL: + outputs[i].Type = "Bool" + values := make([]grnxx.Bool, len(records)) + if err = expression.Evaluate(records, values); err != nil { + return nil, err + } + outputs[i].Values = values + case grnxx.INT: + outputs[i].Type = "Int" + values := make([]grnxx.Int, len(records)) + if err = expression.Evaluate(records, values); err != nil { + return nil, err + } + outputs[i].Values = values + case grnxx.FLOAT: + outputs[i].Type = "Float" + values := make([]grnxx.Float, len(records)) + if err = expression.Evaluate(records, values); err != nil { + return nil, err + } + outputs[i].Values = values + case grnxx.TEXT: + outputs[i].Type = "Text" + values := make([]grnxx.Text, len(records)) + if err = expression.Evaluate(records, values); err != nil { + return nil, err + } + outputValues := make([]string, len(records)) + for i := 0; i < len(records); i++ { + outputValues[i] = string(values[i]) + } + outputs[i].Values = outputValues + default: + return nil, fmt.Errorf("unsupported data type") + } + } +// fmt.Println("outputs:", outputs) + + bytes, err := json.Marshal(outputs) + if err != nil { + return nil, err + } + return bytes, nil +} + +func (db *DB) complexSelect(groongaOptions, grnxxOptions map[string]string) ( + []byte, error) { +// fmt.Println("groongaOptions: groongaOptions") +// fmt.Println("grnxxOptions: grnxxOptions") + if db.grnxxDB == nil { + return nil, fmt.Errorf("grnxx not available") + } + + groongaOptions["cache"] = "no" + groongaOptions["output_columns"] = "_id,_score" + var records []grnxx.Record + for i := 0; i < len(db.groongaDBs); i++ { + outputColumns, err := db.groongaDBs[i].Select(groongaOptions) + if err != nil { + return nil, err + } + rowIDs := outputColumns[0].Values.([]uint32) + scores := outputColumns[1].Values.([]int32) + newRecords := make([]grnxx.Record, len(rowIDs)) + for j := 0; j < len(rowIDs); j++ { + newRecords[j].RowID = + grnxx.Int(((int(rowIDs[j]) - 1) * len(db.groongaDBs)) + i + 1) + newRecords[j].Score = grnxx.Float(scores[j]) + } + records = append(records, newRecords...) + } + + table := db.grnxxDB.FindTable(groongaOptions["table"]) + if table == nil { + return nil, fmt.Errorf("table not found: name = %s", + groongaOptions["table"]) + } + + filterString, ok := grnxxOptions["grnxx_filter"] + if !ok { + filterString = "TRUE" + } + offset := 0 + offsetString, ok := grnxxOptions["grnxx_offset"] + if ok { + var err error + offset, err = strconv.Atoi(offsetString) + if err != nil { + return nil, err + } + } + limit := 10 + limitString, ok := grnxxOptions["grnxx_limit"] + if ok { + var err error + limit, err = strconv.Atoi(limitString) + if err != nil { + return nil, err + } + } + filter, err := grnxx.ParseExpression(table, filterString) + if err != nil { + return nil, err + } + defer filter.Close() + filter.FilterEx(&records, offset, limit) +// fmt.Println("records:", records) + + var outputs []Output + optionValue, ok := grnxxOptions["grnxx_output_columns"] + if ok { + values := strings.Split(optionValue, ",") + for _, value := range values { + value = strings.TrimFunc(value, unicode.IsSpace) + if len(value) != 0 { + outputs = append(outputs, Output{Name: value}) + } + } + } else { + outputs = append(outputs, Output{Name: "_id"}) + numColumns := table.NumColumns() + for i := 0; i < numColumns; i++ { + outputs = append(outputs, Output{Name: table.GetColumn(i).Name()}) + } + } +// fmt.Println("outputs:", outputs) + + for i := 0; i < len(outputs); i++ { + expression, err := grnxx.ParseExpression(table, outputs[i].Name) + if err != nil { + return nil, err + } + defer expression.Close() + + switch expression.DataType() { + case grnxx.BOOL: + outputs[i].Type = "Bool" + values := make([]grnxx.Bool, len(records)) + if err = expression.Evaluate(records, values); err != nil { + return nil, err + } + outputs[i].Values = values + case grnxx.INT: + outputs[i].Type = "Int" + values := make([]grnxx.Int, len(records)) + if err = expression.Evaluate(records, values); err != nil { + return nil, err + } + outputs[i].Values = values + case grnxx.FLOAT: + outputs[i].Type = "Float" + values := make([]grnxx.Float, len(records)) + if err = expression.Evaluate(records, values); err != nil { + return nil, err + } + outputs[i].Values = values + case grnxx.TEXT: + outputs[i].Type = "Text" + values := make([]grnxx.Text, len(records)) + if err = expression.Evaluate(records, values); err != nil { + return nil, err + } + outputValues := make([]string, len(records)) + for i := 0; i < len(records); i++ { + outputValues[i] = string(values[i]) + } + outputs[i].Values = outputValues + default: + return nil, fmt.Errorf("unsupported data type") + } + } +// fmt.Println("outputs:", outputs) + + bytes, err := json.Marshal(outputs) + if err != nil { + return nil, err + } + return bytes, nil +} + +func (db *DB) gnxSelect(command string) ([]byte, error) { + _, options, err := db.ParseCommand(command) + if err != nil { + return nil, err + } + groongaOptions := make(map[string]string) + grnxxOptions := make(map[string]string) + for key, value := range options { + if strings.HasPrefix(key, "grnxx_") { + grnxxOptions[key] = value + } else { + groongaOptions[key] = value + } + } + if _, ok := groongaOptions["table"]; !ok { + return nil, fmt.Errorf("table name missing") + } + if len(grnxxOptions) == 0 { + return db.groongaSelect(groongaOptions) + } else if len(groongaOptions) == 1 { + grnxxOptions["table"] = groongaOptions["table"] + return db.grnxxSelect(grnxxOptions) + } else { + return db.complexSelect(groongaOptions, grnxxOptions) + } +} + +func (db *DB) gnxSnapshot(command string) ([]byte, error) { + _, options, err := db.ParseCommand(command) + if err != nil { + return nil, err + } + + tableName, ok := options["table"] + if !ok { + return nil, fmt.Errorf("table name missing") + } + columnName, ok := options["column"] + if !ok { + return nil, fmt.Errorf("column name missing") + } +// fmt.Println("tableName:", tableName) +// fmt.Println("columnName:", columnName) + + idColumns := make([]groonga.OutputColumn, len(db.groongaDBs)) + valueColumns := make([]groonga.OutputColumn, len(db.groongaDBs)) + for i := 0; i < len(db.groongaDBs); i++ { + outputColumns, err := db.groongaDBs[i].Select(map[string]string{ + "table": tableName, "output_columns": fmt.Sprintf("_id,%s", columnName), + "limit": "-1", "cache": "no"}) + if err != nil { + return nil, err + } + if len(outputColumns) != 2 { + return nil, fmt.Errorf("column not found: name = %s", columnName) + } + if outputColumns[1].Name != columnName { + return nil, fmt.Errorf("column mismatch: actual = %s, request = %s", + outputColumns[1].Name, columnName) + } + idColumns[i] = outputColumns[0] + valueColumns[i] = outputColumns[1] + } +// fmt.Println("idColumns:", idColumns) +// fmt.Println("valueColumns:", valueColumns) + + var dataType grnxx.DataType + switch valueColumns[0].Type { + case "Bool": + dataType = grnxx.BOOL + case "Int8", "Int16", "Int32", "Int64", + "UInt8", "UInt16", "UInt32", "UInt64": + dataType = grnxx.INT + case "Float": + dataType = grnxx.FLOAT + case "Time": + dataType = grnxx.INT + case "ShortText", "Text", "LongText": + dataType = grnxx.TEXT + default: + return nil, fmt.Errorf("unsupported data type") + } + + if db.grnxxDB == nil { + if db.grnxxDB, err = grnxx.CreateDB(); err != nil { + return nil, err + } + } + table := db.grnxxDB.FindTable(tableName) + if table == nil { + if table, err = db.grnxxDB.CreateTable(tableName); err != nil { + return nil, err + } + } + column := table.FindColumn(columnName) + if column == nil { + if column, err = table.CreateColumn(columnName, dataType, nil); err != nil { + return nil, err + } + } + + for i := 0; i < len(idColumns); i++ { + ids := idColumns[i].Values.([]uint32) + for j := 0; j < len(ids); j++ { + rowID := grnxx.Int(int(ids[j] - 1) * len(idColumns) + i + 1) + if !table.TestRow(rowID) { + if err := table.InsertRowAt(rowID, nil); err != nil { + return nil, err + } + } + switch values := valueColumns[i].Values.(type) { + case []bool: + if values[j] { + err = column.Set(rowID, grnxx.TRUE) + } else { + err = column.Set(rowID, grnxx.FALSE) + } + case []int8: + err = column.Set(rowID, grnxx.Int(values[j])) + case []int16: + err = column.Set(rowID, grnxx.Int(values[j])) + case []int32: + err = column.Set(rowID, grnxx.Int(values[j])) + case []int64: + err = column.Set(rowID, grnxx.Int(values[j])) + case []uint8: + err = column.Set(rowID, grnxx.Int(values[j])) + case []uint16: + err = column.Set(rowID, grnxx.Int(values[j])) + case []uint32: + err = column.Set(rowID, grnxx.Int(values[j])) + case []uint64: + err = column.Set(rowID, grnxx.Int(values[j])) + case []float64: + err = column.Set(rowID, grnxx.Float(values[j])) + case []time.Time: + err = column.Set(rowID, grnxx.Int(values[j].UnixNano() / 1000)) + case []string: + err = column.Set(rowID, grnxx.Text(values[j])) + } + if err != nil { + return nil, err + } + } + } + return []byte("true"), nil +} + +func (db *DB) Query(command string) ([]byte, error) { + name, _, ok, err := db.tokenizeCommand(command) + if err != nil { + return nil, err + } + if !ok { + return []byte{}, nil + } + switch name { + case "gnx_load": + return db.gnxLoad(command) + case "gnx_select": + return db.gnxSelect(command) + case "gnx_snapshot": + return db.gnxSnapshot(command) + default: + // Query the command to all DBs. + if name == "select" { + command += " --cache no" + } + results := make([][]byte, len(db.groongaDBs)) + count := 1 + for i := 0; i < len(results); i++ { + bytes, err := db.groongaDBs[i].Query(command) + if err != nil { + return nil, err + } + results[i] = bytes + count += len(bytes) + 1 + } + bytes := make([]byte, 0, count) + bytes = append(bytes, '[') + bytes = append(bytes, results[0]...) + for i := 1; i < len(results); i++ { + bytes = append(bytes, ',') + bytes = append(bytes, results[i]...) + } + bytes = append(bytes, ']') + return bytes, nil + } +} + +func (db *DB) tokenizeCommand(s string) (string, string, bool, error) { + s = strings.TrimLeftFunc(s, unicode.IsSpace) + if len(s) == 0 { + return "", "", false, nil + } + switch s[0] { + case '#': + return "", "", false, nil + case '\'', '"': + quote := s[0] + pos := 1 + hasBackslash := false + for pos < len(s) { + if s[pos] == quote { + break + } else if s[pos] == '\\' { + hasBackslash = true + pos++ + } + pos++ + } + if pos >= len(s) { + return "", "", false, fmt.Errorf("quote missing") + } + token := s[1:pos] + if hasBackslash { + bytes := make([]byte, len(token)) + count := 0 + for i := 0; i < len(token); i++ { + if token[i] == '\\' { + i++ + } + bytes[count] = token[i] + count++ + } + token = string(bytes[:count]) + } + return token, s[pos+1:], true, nil + default: + pos := strings.IndexFunc(s, unicode.IsSpace) + if pos == -1 { + pos = len(s) + } + return s[0:pos], s[pos:], true, nil + } +} + +func (db *DB) tokenizeCommandAll(command string) ([]string, error) { + var tokens []string + s := command + for { + token, rest, ok, err := db.tokenizeCommand(s) + if err != nil { + return nil, err + } + if !ok { + break + } + tokens = append(tokens, token) + s = rest + } + return tokens, nil +} + +var commandOptionKeys = map[string][]string{ + "cache_limit": {"max"}, + "check": {"obj"}, + "column_create": {"table", "name", "flags", "type", "source"}, + "column_list": {"table"}, + "column_remove": {"table", "name"}, + "column_rename": {"table", "name", "new_name"}, + "define_selector": { + "name", "table", "match_columns", "query", "filter", "scorer", "sortby", + "output_columns", "offset", "limit", "drilldown", "drilldown_sortby", + "drilldown_output_columns", "drilldown_offset", "drilldown_limit"}, + "defrag": {"objname", "threshold"}, + "delete": {"table", "key", "id", "filter"}, + "dump": {"tables"}, + "gnx_load": {"values", "table", "columns", "ifexists", "input_type"}, + "gnx_select": { + "table", "match_columns", "query", "filter", "scorer", "sortby", + "output_columns", "offset", "limit", "drilldown", "drilldown_sortby", + "drilldown_output_columns", "drilldown_offset", "drilldown_limit", + "cache", "match_escalation_threshold", "query_expansion", "query_flags", + "query_expander", "adjuster", "drilldown_calc_types", + "drilldown_calc_target", + "grnxx_filter", "grnxx_output_columns", "grnxx_offset", "grnxx_limit"}, + "gnx_snapshot": {"table", "column"}, + "load": {"values", "table", "columns", "ifexists", "input_type"}, + "lock_clear": {"target_name"}, + "log_level": {"level"}, + "log_put": {"level", "message"}, + "log_reopen": {}, + "logical_count": { + "logical_table", "shared_key", "min", "min_border", "max", "max_border", + "filter"}, + "normalize": {"normalizer", "string", "flags"}, + "normalizer_list": {}, + "quit": {}, + // "range_filter": {}, + "register": {"path"}, + "request_cancel": {"id"}, + "ruby_eval": {"script"}, + "ruby_load": {"path"}, + "select": { + "table", "match_columns", "query", "filter", "scorer", "sortby", + "output_columns", "offset", "limit", "drilldown", "drilldown_sortby", + "drilldown_output_columns", "drilldown_offset", "drilldown_limit", + "cache", "match_escalation_threshold", "query_expansion", "query_flags", + "query_expander", "adjuster", "drilldown_calc_types", + "drilldown_calc_target"}, + "shutdown": {}, + "status": {}, + "suggest": {"types", "table", "column", "query", "sortby", "output_columns", + "offset", "limit", "frequency_threshold", + "conditional_probability_threshold", "prefix_search"}, + "table_create": { + "name", "flag", "key_type", "value_type", "default_tokenizer", + "normalizer", "token_filters"}, + "table_list": {}, + "table_remove": {"name"}, + "table_tokenize": {"table", "string", "flags", "mode"}, + "tokenize": { + "tokenizer", "string", "normalizer", "flags", "mode", "token_filters"}, + "tokenizer_list": {}, + "truncate": {"target_name"}} + +func (db *DB) parseCommandOptions(name string, tokens []string) ( + map[string]string, error) { + args := make([]string, 0) + options := make(map[string]string) + for i := 0; i < len(tokens); i++ { + if strings.HasPrefix(tokens[i], "--") { + key := tokens[i][2:] + i++ + if i >= len(tokens) { + return nil, fmt.Errorf("option argument missing") + } + options[key] = tokens[i] + } else { + args = append(args, tokens[i]) + } + } + keys := commandOptionKeys[name] + end := len(keys) + if end > len(args) { + end = len(args) + } + for i := 0; i < end; i++ { + options[keys[i]] = args[i] + } + return options, nil +} + +func (db *DB) ParseCommand(command string) (string, map[string]string, error) { + tokens, err := db.tokenizeCommandAll(command) + if err != nil { + return "", nil, err + } + if len(tokens) == 0 { + return "", nil, nil + } + // Parse tokens. + name := tokens[0] + options, err := db.parseCommandOptions(name, tokens[1:]) + if err != nil { + return "", nil, err + } + return name, options, nil +} Added: go/gnx/grnxx/grnxx.cpp (+1058 -0) 100644 =================================================================== --- /dev/null +++ go/gnx/grnxx/grnxx.cpp 2015-03-20 21:42:11 +0900 (f2505c6) @@ -0,0 +1,1058 @@ +#include "grnxx.h" + +#include <grnxx/db.hpp> +#include <grnxx/expression.hpp> +#include <grnxx/library.hpp> +#include <grnxx/pipeline.hpp> + +extern "C" { + +// -- Library -- + +const char *grnxx_package() { + return grnxx::Library::package(); +} + +const char *grnxx_version() { + return grnxx::Library::version(); +} + +// -- Component -- + +struct grnxx_db { + grnxx::DB *db() { + return reinterpret_cast<grnxx::DB *>(this); + } +}; + +struct grnxx_table { + grnxx::Table *table() { + return reinterpret_cast<grnxx::Table *>(this); + } +}; + +struct grnxx_column { + grnxx::Column *column() { + return reinterpret_cast<grnxx::Column *>(this); + } +}; + +struct grnxx_index { + grnxx::Index *index() { + return reinterpret_cast<grnxx::Index *>(this); + } +}; + +struct grnxx_cursor { + grnxx::Cursor *cursor() { + return reinterpret_cast<grnxx::Cursor *>(this); + } +}; + +struct grnxx_expression { + grnxx::Expression *expression() { + return reinterpret_cast<grnxx::Expression *>(this); + } +}; + +struct grnxx_expression_builder { + grnxx::ExpressionBuilder *builder() { + return reinterpret_cast<grnxx::ExpressionBuilder *>(this); + } +}; + +struct grnxx_sorter { + grnxx::Sorter *sorter() { + return reinterpret_cast<grnxx::Sorter *>(this); + } +}; + +struct grnxx_merger { + grnxx::Merger *merger() { + return reinterpret_cast<grnxx::Merger *>(this); + } +}; + +struct grnxx_pipeline { + grnxx::Pipeline *pipeline() { + return reinterpret_cast<grnxx::Pipeline *>(this); + } +}; + +struct grnxx_pipeline_builder { + grnxx::PipelineBuilder *builder() { + return reinterpret_cast<grnxx::PipelineBuilder *>(this); + } +}; + +// -- Utility -- + +static grnxx::Datum grnxx_value_to_datum(grnxx::DataType data_type, + const void *value) { + if (!value) { + return grnxx::Datum(grnxx::NA()); + } + switch (data_type) { + case GRNXX_BOOL: { + const grnxx_bool *x = static_cast<const grnxx_bool *>(value); + return grnxx::Datum((*x == GRNXX_BOOL_NA) ? grnxx::Bool::na() : + grnxx::Bool(*x != GRNXX_BOOL_FALSE)); + } + case GRNXX_INT: { + return grnxx::Datum(grnxx::Int(*static_cast<const int64_t *>(value))); + } + case GRNXX_FLOAT: { + return grnxx::Datum(grnxx::Float(*static_cast<const double *>(value))); + } + case GRNXX_GEO_POINT: { + const grnxx_geo_point *geo_point = + static_cast<const grnxx_geo_point *>(value); + return grnxx::GeoPoint(grnxx::Int(geo_point->latitude), + grnxx::Int(geo_point->longitude)); + } + case GRNXX_TEXT: { + const grnxx_text *x = static_cast<const grnxx_text *>(value); + return grnxx::Text(x->data, x->size); + } + } + return grnxx::Datum(grnxx::NA()); +} + +// -- DB -- + +grnxx_db *grnxx_db_create() try { + auto db = grnxx::open_db(""); + return reinterpret_cast<grnxx_db *>(db.release()); +} catch (...) { + return nullptr; +} + +void grnxx_db_close(grnxx_db *db) { + delete db->db(); +} + +size_t grnxx_db_num_tables(grnxx_db *db) { + return db->db()->num_tables(); +} + +grnxx_table *grnxx_db_create_table(grnxx_db *db, const char *name) try { + return reinterpret_cast<grnxx_table *>(db->db()->create_table(name)); +} catch (...) { + return nullptr; +} + +bool grnxx_db_remove_table(grnxx_db *db, const char *name) try { + db->db()->remove_table(name); + return true; +} catch (...) { + return false; +} + +bool grnxx_db_rename_table(grnxx_db *db, + const char *name, + const char *new_name) try { + db->db()->rename_table(name, new_name); + return true; +} catch (...) { + return false; +} + +bool grnxx_db_reorder_table(grnxx_db *db, + const char *name, + const char *prev_name) try { + db->db()->reorder_table(name, prev_name); + return true; +} catch (...) { + return false; +} + +grnxx_table *grnxx_db_get_table(grnxx_db *db, size_t table_id) { + return reinterpret_cast<grnxx_table *>(db->db()->get_table(table_id)); +} + +grnxx_table *grnxx_db_find_table(grnxx_db *db, const char *name) { + return reinterpret_cast<grnxx_table *>(db->db()->find_table(name)); +} + +// -- Table -- + +grnxx_db *grnxx_table_db(grnxx_table *table) { + return reinterpret_cast<grnxx_db *>(table->table()->db()); +} + +const char *grnxx_table_name(grnxx_table *table, size_t *size) { + *size = table->table()->name().size(); + return table->table()->name().data(); +} + +size_t grnxx_table_num_columns(grnxx_table *table) { + return table->table()->num_columns(); +} + +grnxx_column *grnxx_table_key_column(grnxx_table *table) { + return reinterpret_cast<grnxx_column *>(table->table()->key_column()); +} + +size_t grnxx_table_num_rows(grnxx_table *table) { + return table->table()->num_rows(); +} + +int64_t grnxx_table_max_row_id(grnxx_table *table) { + return table->table()->max_row_id().raw(); +} + +bool grnxx_table_is_empty(grnxx_table *table) { + return table->table()->is_empty(); +} + +bool grnxx_table_is_full(grnxx_table *table) { + return table->table()->is_full(); +} + +grnxx_column *grnxx_table_create_column( + grnxx_table *table, + const char *name, + grnxx_data_type data_type, + const grnxx_column_options *options) try { + grnxx::ColumnOptions internal_options; + if (options) { + internal_options.reference_table_name = options->reference_table_name; + } + return reinterpret_cast<grnxx_column *>( + table->table()->create_column(name, data_type, internal_options)); +} catch (...) { + return nullptr; +} + +bool grnxx_table_remove_column(grnxx_table *table, const char *name) try { + table->table()->remove_column(name); + return true; +} catch (...) { + return false; +} + +bool grnxx_table_rename_column(grnxx_table *table, + const char *name, + const char *new_name) try { + table->table()->rename_column(name, new_name); + return true; +} catch (...) { + return false; +} + +bool grnxx_table_reorder_column(grnxx_table *table, + const char *name, + const char *prev_name) try { + table->table()->reorder_column(name, prev_name); + return true; +} catch (...) { + return false; +} + +grnxx_column *grnxx_table_get_column(grnxx_table *table, size_t column_id) { + return reinterpret_cast<grnxx_column *>( + table->table()->get_column(column_id)); +} + +grnxx_column *grnxx_table_find_column(grnxx_table *table, const char *name) { + return reinterpret_cast<grnxx_column *>(table->table()->find_column(name)); +} + +bool grnxx_table_set_key_column(grnxx_table *table, const char *name) try { + table->table()->set_key_column(name); + return true; +} catch (...) { + return false; +} + +bool grnxx_table_unset_key_column(grnxx_table *table) try { + table->table()->unset_key_column(); + return true; +} catch (...) { + return false; +} + +int64_t grnxx_table_insert_row(grnxx_table *table, const void *key) try { + auto key_column = table->table()->key_column(); + if (key_column) { + table->table()->insert_row( + grnxx_value_to_datum(key_column->data_type(), key)); + } else { + return table->table()->insert_row().raw(); + } +} catch (...) { + return grnxx::Int::raw_na(); +} + +size_t grnxx_table_insert_rows(grnxx_table *table, + size_t num_keys, + const void *keys, + int64_t *row_ids) { + size_t count = 0; + auto key_column = table->table()->key_column(); + if (key_column) { + grnxx::Datum key; + while (count < num_keys) try { + switch (key_column->data_type()) { + case GRNXX_BOOL: { + auto value = static_cast<const grnxx_bool *>(keys)[count]; + key = (value == GRNXX_BOOL_NA) ? grnxx::Bool(grnxx::NA()) : + grnxx::Bool(value == GRNXX_BOOL_TRUE); + break; + } + case GRNXX_INT: { + auto value = static_cast<const int64_t *>(keys)[count]; + key = grnxx::Int(value); + break; + } + case GRNXX_FLOAT: { + auto value = static_cast<const double *>(keys)[count]; + key = grnxx::Float(value); + break; + } + case GRNXX_GEO_POINT: { + auto value = static_cast<const grnxx_geo_point *>(keys)[count]; + key = grnxx::GeoPoint(grnxx::Int(value.latitude), + grnxx::Int(value.longitude)); + break; + } + case GRNXX_TEXT: { + auto value = static_cast<const grnxx_text *>(keys)[count]; + key = grnxx::Text(value.data, value.size); + break; + } + } + row_ids[count] = table->table()->insert_row(key).raw(); + ++count; + } catch (...) { + return count; + } + } else { + while (count < num_keys) try { + row_ids[count] = table->table()->insert_row().raw(); + ++count; + } catch (...) { + return count; + } + } + return count; +} + +bool grnxx_table_insert_row_at(grnxx_table *table, + int64_t row_id, + const void *key) try { + auto key_column = table->table()->key_column(); + if (key_column) { + table->table()->insert_row_at( + grnxx::Int(row_id), + grnxx_value_to_datum(key_column->data_type(), key)); + } else { + table->table()->insert_row_at(grnxx::Int(row_id)); + } + return true; +} catch (...) { + return false; +} + +int64_t grnxx_table_find_or_insert_row(grnxx_table *table, + const void *key, + bool *inserted) try { + auto key_column = table->table()->key_column(); + return table->table()->find_or_insert_row( + grnxx_value_to_datum(key_column->data_type(), key), inserted).raw(); +} catch (...) { + return grnxx::Int::raw_na(); +} + +bool grnxx_table_remove_row(grnxx_table *table, int64_t row_id) try { + table->table()->remove_row(grnxx::Int(row_id)); + return true; +} catch (...) { + return false; +} + +bool grnxx_table_test_row(grnxx_table *table, int64_t row_id) try { + return table->table()->test_row(grnxx::Int(row_id)); +} catch (...) { + return false; +} + +int64_t grnxx_table_find_row(grnxx_table *table, const void *key) try { + auto key_column = table->table()->key_column(); + if (!key_column) { + return grnxx::Int::raw_na(); + } + return table->table()->find_row( + grnxx_value_to_datum(key_column->data_type(), key)).raw(); +} catch (...) { + return false; +} + +grnxx_cursor *grnxx_table_create_cursor(grnxx_table *table, + const grnxx_cursor_options *options) try { + auto cursor = table->table()->create_cursor( + *reinterpret_cast<const grnxx::CursorOptions *>(options)); + return reinterpret_cast<grnxx_cursor *>(cursor.release()); +} catch (...) { + return nullptr; +} + +// -- Column -- + +grnxx_table *grnxx_column_table(grnxx_column *column) { + return reinterpret_cast<grnxx_table *>(column->column()->table()); +} + +const char *grnxx_column_name(grnxx_column *column, size_t *size) { + *size = column->column()->name().size(); + return column->column()->name().data(); +} + +grnxx_data_type grnxx_column_data_type(grnxx_column *column) { + return column->column()->data_type(); +} + +grnxx_table *grnxx_column_reference_table(grnxx_column *column) { + return reinterpret_cast<grnxx_table *>(column->column()->reference_table()); +} + +bool grnxx_column_is_key(grnxx_column *column) { + return column->column()->is_key(); +} + +size_t grnxx_column_num_indexes(grnxx_column *column) { + return column->column()->num_indexes(); +} + +grnxx_index *grnxx_column_create_index(grnxx_column *column, + const char *name, + grnxx_index_type index_type) try { + switch (index_type) { + case GRNXX_TREE_INDEX: { + return reinterpret_cast<grnxx_index *>( + column->column()->create_index(name, GRNXX_TREE_INDEX)); + } + case GRNXX_HASH_INDEX: { + return reinterpret_cast<grnxx_index *>( + column->column()->create_index(name, GRNXX_HASH_INDEX)); + } + default: { + return nullptr; + } + } +} catch (...) { + return nullptr; +} + +bool grnxx_column_remove_index(grnxx_column *column, const char *name) try { + column->column()->remove_index(name); + return true; +} catch (...) { + return false; +} + +bool grnxx_column_rename_index(grnxx_column *column, + const char *name, + const char *new_name) try { + column->column()->rename_index(name, new_name); + return true; +} catch (...) { + return false; +} + +bool grnxx_column_reorder_index(grnxx_column *column, + const char *name, + const char *prev_name) try { + column->column()->reorder_index(name, prev_name); + return true; +} catch (...) { + return false; +} + +grnxx_index *grnxx_column_get_index(grnxx_column *column, size_t index_id) { + return reinterpret_cast<grnxx_index *>( + column->column()->get_index(index_id)); +} + +grnxx_index *grnxx_column_find_index(grnxx_column *column, const char *name) { + return reinterpret_cast<grnxx_index *>(column->column()->find_index(name)); +} + +bool grnxx_column_set(grnxx_column *column, + int64_t row_id, + const void *value) try { + if (!value) { + column->column()->set(grnxx::Int(row_id), grnxx::NA()); + return true; + } + column->column()->set( + grnxx::Int(row_id), + grnxx_value_to_datum(column->column()->data_type(), value)); + return true; +} catch (...) { + return false; +} + +bool grnxx_column_get(grnxx_column *column, int64_t row_id, void *value) try { + grnxx::Datum datum; + column->column()->get(grnxx::Int(row_id), &datum); + switch (column->column()->data_type()) { + case GRNXX_BOOL: { + *static_cast<grnxx_bool *>(value) = datum.force_bool().raw(); + break; + } + case GRNXX_INT: { + *static_cast<int64_t *>(value) = datum.force_int().raw(); + break; + } + case GRNXX_FLOAT: { + *static_cast<double *>(value) = datum.force_float().raw(); + break; + } + case GRNXX_GEO_POINT: { + grnxx_geo_point *geo_point = static_cast<grnxx_geo_point *>(value); + geo_point->latitude = datum.force_geo_point().raw_latitude(); + geo_point->longitude = datum.force_geo_point().raw_longitude(); + break; + } + case GRNXX_TEXT: { + grnxx_text *text = static_cast<grnxx_text *>(value); + const grnxx::Text &stored_text = datum.force_text(); + if (stored_text.is_na()) { + text->data = nullptr; + text->size = grnxx::Text::raw_na_size(); + } else { + text->data = datum.force_text().raw_data(); + text->size = datum.force_text().raw_size(); + } + break; + } + default: { + return false; + } + } + return true; +} catch (...) { + return false; +} + +bool grnxx_column_contains(grnxx_column *column, const void *value) try { + if (!value) { + return column->column()->contains(grnxx::NA()); + } + return column->column()->contains( + grnxx_value_to_datum(column->column()->data_type(), value)); +} catch (...) { + return false; +} + +int64_t grnxx_column_find_one(grnxx_column *column, const void *value) try { + if (!value) { + return column->column()->find_one(grnxx::NA()).raw(); + } + return column->column()->find_one( + grnxx_value_to_datum(column->column()->data_type(), value)).raw(); +} catch (...) { + return grnxx::Int::raw_na(); +} + +// -- Index -- + +grnxx_column *grnxx_index_column(grnxx_index *index) { + return reinterpret_cast<grnxx_column *>(index->index()->column()); +} + +const char *grnxx_index_name(grnxx_index *index, size_t *size) { + *size = index->index()->name().size(); + return index->index()->name().data(); +} + +grnxx_index_type grnxx_index_index_type(grnxx_index *index) { + switch (index->index()->type()) { + case GRNXX_TREE_INDEX: { + return GRNXX_TREE_INDEX; + } + case GRNXX_HASH_INDEX: { + return GRNXX_HASH_INDEX; + } + } +} + +size_t grnxx_index_num_entries(grnxx_index *index) { + return index->index()->num_entries(); +} + +bool grnxx_index_test_uniqueness(grnxx_index *index) try { + return index->index()->test_uniqueness(); +} catch (...) { + return false; +} + +bool grnxx_index_contains(grnxx_index *index, const void *value) try { + auto column = index->index()->column(); + if (!value) { + return column->contains(grnxx::NA()); + } + return index->index()->contains( + grnxx_value_to_datum(column->data_type(), value)); +} catch (...) { + return false; +} + +int64_t grnxx_index_find_one(grnxx_index *index, const void *value) try { + auto column = index->index()->column(); + if (!value) { + return column->find_one(grnxx::NA()).raw(); + } + return index->index()->find_one( + grnxx_value_to_datum(column->data_type(), value)).raw(); +} catch (...) { + return grnxx::Int::raw_na(); +} + +grnxx_cursor *grnxx_index_find(grnxx_index *index, + const void *value, + const grnxx_cursor_options *options) try { + const grnxx::CursorOptions *cursor_options = + reinterpret_cast<const grnxx::CursorOptions *>(options); + return reinterpret_cast<grnxx_cursor *>(index->index()->find( + grnxx_value_to_datum(index->index()->column()->data_type(), value), + *cursor_options).release()); +} catch (...) { + return nullptr; +} + +grnxx_cursor *grnxx_index_find_in_range(grnxx_index *index, + const void *lower_bound_value, + bool lower_bound_is_inclusive, + const void *upper_bound_value, + bool upper_bound_is_inclusive, + const grnxx_cursor_options *options) try { + const grnxx::CursorOptions *cursor_options = + reinterpret_cast<const grnxx::CursorOptions *>(options); + grnxx::DataType data_type = index->index()->column()->data_type(); + grnxx::IndexRange range; + range.set_lower_bound( + grnxx_value_to_datum(data_type, lower_bound_value), + lower_bound_is_inclusive ? grnxx::INCLUSIVE_END_POINT : + grnxx::EXCLUSIVE_END_POINT); + range.set_upper_bound( + grnxx_value_to_datum(data_type, upper_bound_value), + upper_bound_is_inclusive ? grnxx::INCLUSIVE_END_POINT : + grnxx::EXCLUSIVE_END_POINT); + return reinterpret_cast<grnxx_cursor *>( + index->index()->find_in_range(range, *cursor_options).release()); +} catch (...) { + return nullptr; +} + +grnxx_cursor *grnxx_index_find_starts_with(grnxx_index *index, + const void *prefix, + bool prefix_is_inclusive, + const grnxx_cursor_options *options) try { + const grnxx::CursorOptions *cursor_options = + reinterpret_cast<const grnxx::CursorOptions *>(options); + grnxx::EndPoint end_point; + if (prefix_is_inclusive) { + end_point.type = grnxx::INCLUSIVE_END_POINT; + } else { + end_point.type = grnxx::EXCLUSIVE_END_POINT; + } + end_point.value = + grnxx_value_to_datum(index->index()->column()->data_type(), prefix); + return reinterpret_cast<grnxx_cursor *>( + index->index()->find_starts_with(end_point, *cursor_options).release()); +} catch (...) { + return nullptr; +} + +grnxx_cursor *grnxx_index_find_prefixes(grnxx_index *index, + const void *value, + const grnxx_cursor_options *options) try { + const grnxx::CursorOptions *cursor_options = + reinterpret_cast<const grnxx::CursorOptions *>(options); + return reinterpret_cast<grnxx_cursor *>(index->index()->find_prefixes( + grnxx_value_to_datum(index->index()->column()->data_type(), value), + *cursor_options).release()); +} catch (...) { + return nullptr; +} + +// -- Cursor -- + +void grnxx_cursor_close(grnxx_cursor *cursor) { + delete cursor->cursor(); +} + +size_t grnxx_cursor_read(grnxx_cursor *cursor, + grnxx_record *records, + size_t size) try { + return cursor->cursor()->read(grnxx::ArrayRef<grnxx::Record>( + reinterpret_cast<grnxx::Record *>(records), size)); +} catch (...) { + return 0; +} + +// -- Expression -- + +grnxx_expression *grnxx_expression_parse(grnxx_table *table, + const char *query) try { + return reinterpret_cast<grnxx_expression *>( + grnxx::Expression::parse(table->table(), query).release()); +} catch (...) { + return nullptr; +} + +void grnxx_expression_close(grnxx_expression *expression) { + delete expression->expression(); +} + +grnxx_table *grnxx_expression_table(grnxx_expression *expression) { + return reinterpret_cast<grnxx_table *>( + const_cast<grnxx::Table *>(expression->expression()->table())); +} + +grnxx_data_type grnxx_expression_data_type(grnxx_expression *expression) { + return expression->expression()->data_type(); +} + +bool grnxx_expression_is_row_id(grnxx_expression *expression) { + return expression->expression()->is_row_id(); +} + +bool grnxx_expression_is_score(grnxx_expression *expression) { + return expression->expression()->is_score(); +} + +size_t grnxx_expression_block_size(grnxx_expression *expression) { + return expression->expression()->block_size(); +} + +bool grnxx_expression_filter(grnxx_expression *expression, + grnxx_record *records, + size_t *size, + size_t offset, + size_t limit) try { + grnxx::ArrayRef<grnxx::Record> array( + reinterpret_cast<grnxx::Record *>(records), *size); + expression->expression()->filter(array, &array, offset, limit); + *size = array.size(); + return true; +} catch (...) { + return false; +} + +bool grnxx_expression_adjust(grnxx_expression *expression, + grnxx_record *records, + size_t size) try { + grnxx::ArrayRef<grnxx::Record> array( + reinterpret_cast<grnxx::Record *>(records), size); + expression->expression()->adjust(array); + return true; +} catch (...) { + return false; +} + +bool grnxx_expression_evaluate(grnxx_expression *expression, + const grnxx_record *records, + size_t size, + void *values) try { + grnxx::ArrayCRef<grnxx::Record> internal_records( + reinterpret_cast<const grnxx::Record *>(records), size); + switch (expression->expression()->data_type()) { + case GRNXX_BOOL: { + grnxx::ArrayRef<grnxx::Bool> internal_values( + reinterpret_cast<grnxx::Bool *>(values), size); + expression->expression()->evaluate(internal_records, internal_values); + break; + } + case GRNXX_INT: { + grnxx::ArrayRef<grnxx::Int> internal_values( + reinterpret_cast<grnxx::Int *>(values), size); + expression->expression()->evaluate(internal_records, internal_values); + break; + } + case GRNXX_FLOAT: { + grnxx::ArrayRef<grnxx::Float> internal_values( + reinterpret_cast<grnxx::Float *>(values), size); + expression->expression()->evaluate(internal_records, internal_values); + break; + } + case GRNXX_GEO_POINT: { + grnxx::ArrayRef<grnxx::GeoPoint> internal_values( + reinterpret_cast<grnxx::GeoPoint *>(values), size); + expression->expression()->evaluate(internal_records, internal_values); + break; + } + case GRNXX_TEXT: { + grnxx::ArrayRef<grnxx::Text> internal_values( + reinterpret_cast<grnxx::Text *>(values), size); + expression->expression()->evaluate(internal_records, internal_values); + break; + } + default: { + return false; + } + } + return true; +} catch (...) { + return false; +} + +// -- ExpressionBuilder -- + +grnxx_expression_builder *grnxx_expression_builder_create(grnxx_table *table) try { + return reinterpret_cast<grnxx_expression_builder *>( + grnxx::ExpressionBuilder::create(table->table()).release()); +} catch (...) { + return nullptr; +} + +void grnxx_expression_builder_close(grnxx_expression_builder *builder) { + delete builder->builder(); +} + +grnxx_table *grnxx_expression_builder_table(grnxx_expression_builder *builder) { + return reinterpret_cast<grnxx_table *>( + const_cast<grnxx::Table *>(builder->builder()->table())); +} + +bool grnxx_expression_builder_push_constant(grnxx_expression_builder *builder, + grnxx_data_type data_type, + const void *value) try { + builder->builder()->push_constant(grnxx_value_to_datum(data_type, value)); + return true; +} catch (...) { + return false; +} + +bool grnxx_expression_builder_push_row_id(grnxx_expression_builder *builder) try { + builder->builder()->push_row_id(); + return true; +} catch (...) { + return false; +} + +bool grnxx_expression_builder_push_score(grnxx_expression_builder *builder) try { + builder->builder()->push_score(); + return true; +} catch (...) { + return false; +} + +bool grnxx_expression_builder_push_column(grnxx_expression_builder *builder, + const char *column_name) try { + builder->builder()->push_column(column_name); + return true; +} catch (...) { + return false; +} + +bool grnxx_expression_builder_push_operator( + grnxx_expression_builder *builder, + grnxx_operator_type operator_type) try { + builder->builder()->push_operator(operator_type); + return true; +} catch (...) { + return false; +} + +bool grnxx_expression_builder_begin_subexpression( + grnxx_expression_builder *builder) try { + builder->builder()->begin_subexpression(); + return true; +} catch (...) { + return false; +} + +bool grnxx_expression_builder_end_subexpression( + grnxx_expression_builder *builder) try { + builder->builder()->end_subexpression(); + return true; +} catch (...) { + return false; +} + +void grnxx_expression_builder_clear(grnxx_expression_builder *builder) { + builder->builder()->clear(); +} + +grnxx_expression *grnxx_expression_builder_release( + grnxx_expression_builder *builder) try { + return reinterpret_cast<grnxx_expression *>( + builder->builder()->release().release()); +} catch (...) { + return nullptr; +} + +// -- Sorter -- + +grnxx_sorter *grnxx_sorter_create(grnxx_sorter_order *orders, + size_t num_orders, + const grnxx_sorter_options *options) try { + grnxx::Array<grnxx::SorterOrder> internal_orders; + internal_orders.resize(num_orders); + for (size_t i = 0; i < num_orders; ++i) { + internal_orders[i].expression.reset(orders[i].expression->expression()); + internal_orders[i].type = orders[i].order_type; + } + grnxx::SorterOptions internal_options; + if (options) { + internal_options.offset = options->offset; + internal_options.limit = options->limit; + } + auto sorter = grnxx::Sorter::create( + std::move(internal_orders), internal_options); + return reinterpret_cast<grnxx_sorter *>(sorter.release()); +} catch (...) { + return nullptr; +} + +void grnxx_sorter_close(grnxx_sorter *sorter) { + delete sorter->sorter(); +} + +// -- Merger -- + +static grnxx::MergerOptions grnxx_merger_convert_options( + const grnxx_merger_options *options) { + grnxx::MergerOptions internal_options; + if (options) { + internal_options.logical_operator_type = options->logical_operator_type; + internal_options.score_operator_type = options->score_operator_type; + internal_options.missing_score = grnxx::Float(options->missing_score); + internal_options.offset = options->offset; + internal_options.limit = options->limit; + } + return internal_options; +} + +grnxx_merger *grnxx_merger_create(const grnxx_merger_options *options) try { + auto internal_options = grnxx_merger_convert_options(options); + auto merger = grnxx::Merger::create(std::move(internal_options)); + return reinterpret_cast<grnxx_merger *>(merger.release()); +} catch (...) { + return nullptr; +} + +void grnxx_merger_close(grnxx_merger *merger) { + delete merger->merger(); +} + +// -- Pipeline -- + +void grnxx_pipeline_close(grnxx_pipeline *pipeline) { + delete pipeline->pipeline(); +} + +grnxx_table *grnxx_pipeline_table(grnxx_pipeline *pipeline) { + return reinterpret_cast<grnxx_table *>( + const_cast<grnxx::Table *>(pipeline->pipeline()->table())); +} + +bool grnxx_pipeline_flush(grnxx_pipeline *pipeline, + grnxx_record **records, + size_t *size) try { + grnxx::Array<grnxx::Record> internal_records; + pipeline->pipeline()->flush(&internal_records); + // TODO: Deep copy should be skipped. + *records = static_cast<grnxx_record *>( + std::malloc(sizeof(grnxx_record) * internal_records.size())); + if (!*records) { + return false; + } + for (size_t i = 0; i < internal_records.size(); ++i) { + (*records)[i].row_id = internal_records[i].row_id.raw(); + (*records)[i].score = internal_records[i].score.raw(); + } + *size = internal_records.size(); + return true; +} catch (...) { + return false; +} + +// -- PipelineBuilder -- + +grnxx_pipeline_builder *grnxx_pipeline_builder_create(grnxx_table *table) try { + auto builder = grnxx::PipelineBuilder::create(table->table()); + return reinterpret_cast<grnxx_pipeline_builder *>(builder.release()); +} catch (...) { + return nullptr; +} + +void grnxx_pipeline_builder_close(grnxx_pipeline_builder *builder) { + delete builder->builder(); +} + +grnxx_table *grnxx_pipeline_builder_table(grnxx_pipeline_builder *builder) { + return reinterpret_cast<grnxx_table *>( + const_cast<grnxx::Table *>(builder->builder()->table())); +} + +bool grnxx_pipeline_builder_push_cursor(grnxx_pipeline_builder *builder, + grnxx_cursor *cursor) try { + builder->builder()->push_cursor( + std::unique_ptr<grnxx::Cursor>(cursor->cursor())); + return true; +} catch (...) { + return false; +} + +bool grnxx_pipeline_builder_push_filter(grnxx_pipeline_builder *builder, + grnxx_expression *expression, + size_t offset, + size_t limit) try { + builder->builder()->push_filter( + std::unique_ptr<grnxx::Expression>(expression->expression()), + offset, limit); + return true; +} catch (...) { + return false; +} + +bool grnxx_pipeline_builder_push_adjuster(grnxx_pipeline_builder *builder, + grnxx_expression *expression) try { + builder->builder()->push_adjuster( + std::unique_ptr<grnxx::Expression>(expression->expression())); + return true; +} catch (...) { + return false; +} + +bool grnxx_pipeline_builder_push_sorter(grnxx_pipeline_builder *builder, + grnxx_sorter *sorter) try { + builder->builder()->push_sorter( + std::unique_ptr<grnxx::Sorter>(sorter->sorter())); + return true; +} catch (...) { + return false; +} + +bool grnxx_pipeline_builder_push_merger(grnxx_pipeline_builder *builder, + const grnxx_merger_options *options) try { + auto internal_options = grnxx_merger_convert_options(options); + builder->builder()->push_merger(internal_options); + return true; +} catch (...) { + return false; +} + + +void grnxx_pipeline_builder_clear(grnxx_pipeline_builder *builder) { + builder->builder()->clear(); +} + +grnxx_pipeline *grnxx_pipeline_builder_release( + grnxx_pipeline_builder *builder, + const grnxx_pipeline_options *options) try { + grnxx::PipelineOptions internal_options; + if (options) { + // Nothing to do. + } + auto pipeline = builder->builder()->release(internal_options); + return reinterpret_cast<grnxx_pipeline *>(pipeline.release()); +} catch (...) { + return nullptr; +} + +} // extern "C" Added: go/gnx/grnxx/grnxx.go (+1422 -0) 100644 =================================================================== --- /dev/null +++ go/gnx/grnxx/grnxx.go 2015-03-20 21:42:11 +0900 (5cfb55d) @@ -0,0 +1,1422 @@ +package grnxx + +// #cgo CXXFLAGS: -std=c++11 +// #cgo LDFLAGS: -lgrnxx -lstdc++ +// +// #include "grnxx.h" +// +// #include <stdlib.h> +import "C" +import "fmt" +import "math" +import "reflect" +import "unsafe" + +// -- Data types -- + +type Bool uint8 +type Int int64 +type Float float64 +type GeoPoint struct { + Latitude int32 + Longitude int32 +} +type Text []byte + +type Valuer interface { + IsNA() bool +} + +func NABool() Bool { + return Bool(1) +} +func NAInt() Int { + return Int(math.MinInt64) +} +func NAFloat() Float { + return Float(math.NaN()) +} +func NAGeoPoint() GeoPoint { + return GeoPoint{math.MinInt32, math.MinInt32} +} +func NAText() Text { + return nil +} + +func (this Bool) IsNA() bool { + return this == 1 +} +func (this Int) IsNA() bool { + return this == math.MinInt64 +} +func (this Float) IsNA() bool { + return math.IsNaN(float64(this)) +} +func (this GeoPoint) IsNA() bool { + return this.Latitude == math.MinInt32 +} +func (this Text) IsNA() bool { + return this == nil +} + +func (this Text) Convert() C.grnxx_text { + switch { + case this == nil: + return C.grnxx_text{nil, C.int64_t(math.MinInt64)} + case len(this) == 0: + return C.grnxx_text{nil, C.int64_t(0)} + default: + ptr := (*C.char)(unsafe.Pointer(&this[0])) + return C.grnxx_text{ptr, C.int64_t(len(this))} + } +} + +type Record struct { + RowID Int + Score Float +} + +// -- Constants -- + +const ( + NA = Bool(1) + TRUE = Bool(3) + FALSE = Bool(0) +) + +type DataType int + +const ( + BOOL = DataType(C.GRNXX_BOOL) + INT = DataType(C.GRNXX_INT) + FLOAT = DataType(C.GRNXX_FLOAT) + GEO_POINT = DataType(C.GRNXX_GEO_POINT) + TEXT = DataType(C.GRNXX_TEXT) +) + +type IndexType int + +const ( + TREE_INDEX = IndexType(C.GRNXX_TREE_INDEX) + HASH_INDEX = IndexType(C.GRNXX_HASH_INDEX) +) + +type OrderType int + +const ( + REGULAR_ORDER = OrderType(C.GRNXX_REGULAR_ORDER) + REVERSE_ORDER = OrderType(C.GRNXX_REVERSE_ORDER) +) + +type OperatorType int + +const ( + LOGICAL_NOT = OperatorType(C.GRNXX_LOGICAL_NOT) + BITWISE_NOT = OperatorType(C.GRNXX_BITWISE_NOT) + POSITIVE = OperatorType(C.GRNXX_POSITIVE) + NEGATIVE = OperatorType(C.GRNXX_NEGATIVE) + TO_INT = OperatorType(C.GRNXX_TO_INT) + TO_FLOAT = OperatorType(C.GRNXX_TO_FLOAT) + LOGICAL_AND = OperatorType(C.GRNXX_LOGICAL_AND) + LOGICAL_OR = OperatorType(C.GRNXX_LOGICAL_OR) + EQUAL = OperatorType(C.GRNXX_EQUAL) + NOT_EQUAL = OperatorType(C.GRNXX_NOT_EQUAL) + LESS = OperatorType(C.GRNXX_LESS) + LESS_EQUAL = OperatorType(C.GRNXX_LESS_EQUAL) + GREATER = OperatorType(C.GRNXX_GREATER) + GREATER_EQUAL = OperatorType(C.GRNXX_GREATER_EQUAL) + BITWISE_AND = OperatorType(C.GRNXX_BITWISE_AND) + BITWISE_OR = OperatorType(C.GRNXX_BITWISE_OR) + BITWISE_XOR = OperatorType(C.GRNXX_BITWISE_XOR) + PLUS = OperatorType(C.GRNXX_PLUS) + MINUS = OperatorType(C.GRNXX_MINUS) + MULTIPLICATION = OperatorType(C.GRNXX_MULTIPLICATION) + DIVISION = OperatorType(C.GRNXX_DIVISION) + MODULUS = OperatorType(C.GRNXX_MODULUS) + STARTS_WITH = OperatorType(C.GRNXX_STARTS_WITH) + ENDS_WITH = OperatorType(C.GRNXX_ENDS_WITH) + CONTAINS = OperatorType(C.GRNXX_CONTAINS) + SUBSCRIPT = OperatorType(C.GRNXX_SUBSCRIPT) +) + +type MergerOperatorType int + +const ( + MERGER_AND = MergerOperatorType(C.GRNXX_MERGER_AND) + MERGER_OR = MergerOperatorType(C.GRNXX_MERGER_OR) + MERGER_XOR = MergerOperatorType(C.GRNXX_MERGER_XOR) + MERGER_PLUS = MergerOperatorType(C.GRNXX_MERGER_PLUS) + MERGER_MINUS = MergerOperatorType(C.GRNXX_MERGER_MINUS) + MERGER_MULTIPLICATION = MergerOperatorType(C.GRNXX_MERGER_MULTIPLICATION) + MERGER_LEFT = MergerOperatorType(C.GRNXX_MERGER_LEFT) + MERGER_RIGHT = MergerOperatorType(C.GRNXX_MERGER_RIGHT) + MERGER_ZERO = MergerOperatorType(C.GRNXX_MERGER_ZERO) +) + +// -- Library -- + +func Package() string { + return C.GoString(C.grnxx_package()) +} + +func Version() string { + return C.GoString(C.grnxx_version()) +} + +// -- DB -- + +type DB struct { + handle *C.grnxx_db +} + +func CreateDB() (*DB, error) { + db := C.grnxx_db_create() + if db == nil { + return nil, fmt.Errorf("grnxx_db_create() failed") + } + return &DB{db}, nil +} + +func (db *DB) Close() error { + C.grnxx_db_close(db.handle) + return nil +} + +func (db *DB) NumTables() int { + return int(C.grnxx_db_num_tables(db.handle)) +} + +func (db *DB) CreateTable(name string) (*Table, error) { + nameCString := C.CString(name) + defer C.free(unsafe.Pointer(nameCString)) + table := C.grnxx_db_create_table(db.handle, nameCString) + if table == nil { + return nil, fmt.Errorf("grnxx_db_create_table() failed") + } + return &Table{table}, nil +} + +func (db *DB) RemoveTable(name string) error { + nameCString := C.CString(name) + defer C.free(unsafe.Pointer(nameCString)) + if !C.grnxx_db_remove_table(db.handle, nameCString) { + return fmt.Errorf("grnxx_db_remove_table() failed") + } + return nil +} + +func (db *DB) RenameTable(name string, newName string) error { + nameCString := C.CString(name) + defer C.free(unsafe.Pointer(nameCString)) + newNameCString := C.CString(newName) + defer C.free(unsafe.Pointer(newNameCString)) + if !C.grnxx_db_rename_table(db.handle, nameCString, newNameCString) { + return fmt.Errorf("grnxx_db_rename_table() failed") + } + return nil +} + +func (db *DB) ReorderTable(name string, prevName string) error { + nameCString := C.CString(name) + defer C.free(unsafe.Pointer(nameCString)) + prevNameCString := C.CString(prevName) + defer C.free(unsafe.Pointer(prevNameCString)) + if !C.grnxx_db_reorder_table(db.handle, nameCString, prevNameCString) { + return fmt.Errorf("grnxx_db_reorder_table() failed") + } + return nil +} + +func (db *DB) GetTable(tableID int) *Table { + return &Table{C.grnxx_db_get_table(db.handle, C.size_t(tableID))} +} + +func (db *DB) FindTable(name string) *Table { + nameCString := C.CString(name) + defer C.free(unsafe.Pointer(nameCString)) + table := C.grnxx_db_find_table(db.handle, nameCString) + if table == nil { + return nil + } + return &Table{table} +} + +// -- Table -- + +type Table struct { + handle *C.grnxx_table +} + +func (table *Table) DB() (db *DB) { + return &DB{C.grnxx_table_db(table.handle)} +} + +func (table *Table) Name() string { + var size C.size_t + name := C.grnxx_table_name(table.handle, &size) + return C.GoStringN(name, C.int(size)) +} + +func (table *Table) NumColumns() int { + return int(C.grnxx_table_num_columns(table.handle)) +} + +func (table *Table) KeyColumn() *Column { + column := C.grnxx_table_key_column(table.handle) + if column == nil { + return nil + } + return &Column{column} +} + +func (table *Table) NumRows() int { + return int(C.grnxx_table_num_rows(table.handle)) +} + +func (table *Table) MaxRowID() Int { + return Int(C.grnxx_table_max_row_id(table.handle)) +} + +func (table *Table) IsEmpty() bool { + return bool(C.grnxx_table_is_empty(table.handle)) +} + +func (table *Table) IsFull() bool { + return bool(C.grnxx_table_is_full(table.handle)) +} + +func (table *Table) CreateColumn( + name string, dataType DataType, options *ColumnOptions) (*Column, error) { + var internalOptions *C.grnxx_column_options + if options != nil { + cString := C.CString(options.ReferenceTableName) + defer C.free(unsafe.Pointer(cString)) + internalOptions = &C.grnxx_column_options{cString} + } + nameCString := C.CString(name) + defer C.free(unsafe.Pointer(nameCString)) + column := C.grnxx_table_create_column(table.handle, nameCString, + C.grnxx_data_type(dataType), internalOptions) + if column == nil { + return nil, fmt.Errorf("grnxx_table_create_column() failed") + } + return &Column{column}, nil +} + +func (table *Table) RemoveColumn(name string) error { + nameCString := C.CString(name) + defer C.free(unsafe.Pointer(nameCString)) + if !C.grnxx_table_remove_column(table.handle, nameCString) { + return fmt.Errorf("grnxx_table_remove_column() failed") + } + return nil +} + +func (table *Table) RenameColumn(name string, newName string) error { + nameCString := C.CString(name) + defer C.free(unsafe.Pointer(nameCString)) + newNameCString := C.CString(newName) + defer C.free(unsafe.Pointer(newNameCString)) + if !C.grnxx_table_rename_column(table.handle, nameCString, newNameCString) { + return fmt.Errorf("grnxx_table_rename_column() failed") + } + return nil +} + +func (table *Table) ReorderColumn(name string, prevName string) error { + nameCString := C.CString(name) + defer C.free(unsafe.Pointer(nameCString)) + prevNameCString := C.CString(prevName) + defer C.free(unsafe.Pointer(prevNameCString)) + if !C.grnxx_table_reorder_column(table.handle, nameCString, prevNameCString) { + return fmt.Errorf("grnxx_table_reorder_column() failed") + } + return nil +} + +func (table *Table) GetColumn(columnID int) *Column { + return &Column{C.grnxx_table_get_column(table.handle, C.size_t(columnID))} +} + +func (table *Table) FindColumn(name string) *Column { + nameCString := C.CString(name) + defer C.free(unsafe.Pointer(nameCString)) + column := C.grnxx_table_find_column(table.handle, nameCString) + if column == nil { + return nil + } + return &Column{column} +} + +func (table *Table) SetKeyColumn(name string) error { + nameCString := C.CString(name) + defer C.free(unsafe.Pointer(nameCString)) + if !C.grnxx_table_set_key_column(table.handle, nameCString) { + return fmt.Errorf("grnxx_table_set_key_column() failed") + } + return nil +} + +func (table *Table) UnsetKeyColumn() error { + if !C.grnxx_table_unset_key_column(table.handle) { + return fmt.Errorf("grnxx_table_unset_key_column() failed") + } + return nil +} + +func (table *Table) InsertRow(key Valuer) (Int, error) { + var rowID C.int64_t + switch x := key.(type) { + case nil: + rowID = C.grnxx_table_insert_row(table.handle, nil) + case Bool: + rowID = C.grnxx_table_insert_row(table.handle, unsafe.Pointer(&x)) + case Int: + rowID = C.grnxx_table_insert_row(table.handle, unsafe.Pointer(&x)) + case Float: + rowID = C.grnxx_table_insert_row(table.handle, unsafe.Pointer(&x)) + case GeoPoint: + rowID = C.grnxx_table_insert_row(table.handle, unsafe.Pointer(&x)) + case Text: + text := x.Convert() + rowID = C.grnxx_table_insert_row(table.handle, unsafe.Pointer(&text)) + default: + return NAInt(), fmt.Errorf("unsupported data type") + } + if rowID < 0 { + return Int(rowID), fmt.Errorf("grnxx_table_insert_row() failed") + } + return Int(rowID), nil +} + +func (table *Table) InsertRows(keys interface{}) ([]Int, error) { + var rowIDs []Int + var count int + switch x := keys.(type) { + case []Bool: + if len(x) == 0 { + return nil, fmt.Errorf("no data") + } + rowIDs = make([]Int, len(x)) + count = int(C.grnxx_table_insert_rows(table.handle, C.size_t(len(x)), + unsafe.Pointer(&x[0]), (*C.int64_t)(unsafe.Pointer(&rowIDs[0])))) + case []Int: + if len(x) == 0 { + return nil, fmt.Errorf("no data") + } + rowIDs = make([]Int, len(x)) + count = int(C.grnxx_table_insert_rows(table.handle, C.size_t(len(x)), + unsafe.Pointer(&x[0]), (*C.int64_t)(unsafe.Pointer(&rowIDs[0])))) + case []Float: + if len(x) == 0 { + return nil, fmt.Errorf("no data") + } + rowIDs = make([]Int, len(x)) + count = int(C.grnxx_table_insert_rows(table.handle, C.size_t(len(x)), + unsafe.Pointer(&x[0]), (*C.int64_t)(unsafe.Pointer(&rowIDs[0])))) + case []GeoPoint: + if len(x) == 0 { + return nil, fmt.Errorf("no data") + } + rowIDs = make([]Int, len(x)) + count = int(C.grnxx_table_insert_rows(table.handle, C.size_t(len(x)), + unsafe.Pointer(&x[0]), (*C.int64_t)(unsafe.Pointer(&rowIDs[0])))) + case []Text: + if len(x) == 0 { + return nil, fmt.Errorf("no data") + } + internalKeys := make([]C.grnxx_text, len(x)) + for i := 0; i < len(x); i++ { + internalKeys[i].data = (*C.char)(unsafe.Pointer(&x[i][0])) + internalKeys[i].size = C.int64_t(len(x[i])) + } + rowIDs = make([]Int, len(x)) + count = int(C.grnxx_table_insert_rows(table.handle, C.size_t(len(x)), + unsafe.Pointer(&internalKeys[0]), (*C.int64_t)(unsafe.Pointer(&rowIDs[0])))) + default: + return nil, fmt.Errorf("unsupported data type") + } + if count < len(rowIDs) { + return rowIDs[:count], fmt.Errorf("grnxx_table_insert_rows() failed") + } + return rowIDs, nil +} + +func (table *Table) InsertRowAt(rowID Int, key Valuer) error { + var result C.bool + switch x := key.(type) { + case nil: + result = C.grnxx_table_insert_row_at( + table.handle, C.int64_t(rowID), nil) + case Bool: + result = C.grnxx_table_insert_row_at( + table.handle, C.int64_t(rowID), unsafe.Pointer(&x)) + case Int: + result = C.grnxx_table_insert_row_at( + table.handle, C.int64_t(rowID), unsafe.Pointer(&x)) + case Float: + result = C.grnxx_table_insert_row_at( + table.handle, C.int64_t(rowID), unsafe.Pointer(&x)) + case GeoPoint: + result = C.grnxx_table_insert_row_at( + table.handle, C.int64_t(rowID), unsafe.Pointer(&x)) + case Text: + text := x.Convert() + result = C.grnxx_table_insert_row_at( + table.handle, C.int64_t(rowID), unsafe.Pointer(&text)) + default: + return fmt.Errorf("unsupported data type") + } + if !result { + return fmt.Errorf("grnxx_table_insert_row_at() failed") + } + return nil +} + +func (table *Table) FindOrInsertRow(key Valuer) (Int, bool, error) { + var inserted C.bool + var internalRowID C.int64_t + switch x := key.(type) { + case nil: + internalRowID = C.grnxx_table_find_or_insert_row( + table.handle, nil, &inserted) + case Bool: + internalRowID = C.grnxx_table_find_or_insert_row( + table.handle, unsafe.Pointer(&x), &inserted) + case Int: + internalRowID = C.grnxx_table_find_or_insert_row( + table.handle, unsafe.Pointer(&x), &inserted) + case Float: + internalRowID = C.grnxx_table_find_or_insert_row( + table.handle, unsafe.Pointer(&x), &inserted) + case GeoPoint: + internalRowID = C.grnxx_table_find_or_insert_row( + table.handle, unsafe.Pointer(&x), &inserted) + case Text: + text := x.Convert() + internalRowID = C.grnxx_table_find_or_insert_row( + table.handle, unsafe.Pointer(&text), &inserted) + default: + return NAInt(), false, fmt.Errorf("unsupported data type") + } + rowID := Int(internalRowID) + if rowID.IsNA() { + return rowID, false, fmt.Errorf("grnxx_table_find_or_insert_row() failed") + } + return rowID, bool(inserted), nil +} + +func (table *Table) RemoveRow(rowID Int) error { + if !C.grnxx_table_remove_row(table.handle, C.int64_t(rowID)) { + return fmt.Errorf("grnxx_table_remove_row() failed") + } + return nil +} + +func (table *Table) TestRow(rowID Int) bool { + return bool(C.grnxx_table_test_row(table.handle, C.int64_t(rowID))) +} + +func (table *Table) FindRow(key Valuer) Int { + switch x := key.(type) { + case Bool: + return Int(C.grnxx_table_find_row(table.handle, unsafe.Pointer(&x))) + case Int: + return Int(C.grnxx_table_find_row(table.handle, unsafe.Pointer(&x))) + case Float: + return Int(C.grnxx_table_find_row(table.handle, unsafe.Pointer(&x))) + case GeoPoint: + return Int(C.grnxx_table_find_row(table.handle, unsafe.Pointer(&x))) + case Text: + text := x.Convert() + return Int(C.grnxx_table_find_row(table.handle, unsafe.Pointer(&text))) + } + return NAInt() +} + +func (table *Table) CreateCursor(options *CursorOptions) (*Cursor, error) { + cursor := C.grnxx_table_create_cursor( + table.handle, convertCursorOptions(options)) + if cursor == nil { + return nil, fmt.Errorf("grnxx_table_create_cursor() failed") + } + return &Cursor{cursor}, nil +} + +// -- Column -- + +type ColumnOptions struct { + ReferenceTableName string +} + +type Column struct { + handle *C.grnxx_column +} + +func (column *Column) Table() *Table { + return &Table{C.grnxx_column_table(column.handle)} +} + +func (column *Column) Name() string { + var size C.size_t + name := C.grnxx_column_name(column.handle, &size) + return C.GoStringN(name, C.int(size)) +} + +func (column *Column) DataType() DataType { + return DataType(C.grnxx_column_data_type(column.handle)) +} + +func (column *Column) ReferenceTable() *Table { + referenceTable := C.grnxx_column_reference_table(column.handle) + if referenceTable == nil { + return nil + } + return &Table{referenceTable} +} + +func (column *Column) IsKey() bool { + return bool(C.grnxx_column_is_key(column.handle)) +} + +func (column *Column) NumIndexes() int { + return int(C.grnxx_column_num_indexes(column.handle)) +} + +func (column *Column) CreateIndex(name string, indexType IndexType) (*Index, error) { + nameCString := C.CString(name) + index := C.grnxx_column_create_index(column.handle, nameCString, + C.grnxx_index_type(indexType)) + defer C.free(unsafe.Pointer(nameCString)) + if index == nil { + return nil, fmt.Errorf("grnxx_column_create_index() failed") + } + return &Index{index}, nil +} + +func (column *Column) RemoveIndex(name string) error { + nameCString := C.CString(name) + defer C.free(unsafe.Pointer(nameCString)) + if !C.grnxx_column_remove_index(column.handle, nameCString) { + return fmt.Errorf("grnxx_column_remove_index() failed") + } + return nil +} + +func (column *Column) RenameIndex(name string, newName string) error { + nameCString := C.CString(name) + defer C.free(unsafe.Pointer(nameCString)) + newNameCString := C.CString(newName) + defer C.free(unsafe.Pointer(newNameCString)) + if !C.grnxx_column_rename_index(column.handle, nameCString, newNameCString) { + return fmt.Errorf("grnxx_column_rename_index() failed") + } + return nil +} + +func (column *Column) ReorderIndex(name string, prevName string) error { + nameCString := C.CString(name) + defer C.free(unsafe.Pointer(nameCString)) + prevNameCString := C.CString(prevName) + defer C.free(unsafe.Pointer(prevNameCString)) + if !C.grnxx_column_reorder_index(column.handle, nameCString, prevNameCString) { + return fmt.Errorf("grnxx_column_reorder_index() failed") + } + return nil +} + +func (column *Column) GetIndex(indexID int) *Index { + return &Index{C.grnxx_column_get_index(column.handle, C.size_t(indexID))} +} + +func (column *Column) FindIndex(name string) *Index { + nameCString := C.CString(name) + defer C.free(unsafe.Pointer(nameCString)) + index := C.grnxx_column_find_index(column.handle, nameCString) + if index == nil { + return nil + } + return &Index{index} +} + +func (column *Column) Set(rowID Int, value Valuer) error { + switch x := value.(type) { + case nil: + if !C.grnxx_column_set(column.handle, C.int64_t(rowID), nil) { + return fmt.Errorf("grnxx_column_set() failed") + } + case Bool: + if !C.grnxx_column_set(column.handle, C.int64_t(rowID), unsafe.Pointer(&x)) { + return fmt.Errorf("grnxx_column_set() failed") + } + case Int: + if !C.grnxx_column_set(column.handle, C.int64_t(rowID), unsafe.Pointer(&x)) { + return fmt.Errorf("grnxx_column_set() failed") + } + case Float: + if !C.grnxx_column_set(column.handle, C.int64_t(rowID), unsafe.Pointer(&x)) { + return fmt.Errorf("grnxx_column_set() failed") + } + case GeoPoint: + if !C.grnxx_column_set(column.handle, C.int64_t(rowID), unsafe.Pointer(&x)) { + return fmt.Errorf("grnxx_column_set() failed") + } + case Text: + text := x.Convert() + if !C.grnxx_column_set(column.handle, C.int64_t(rowID), unsafe.Pointer(&text)) { + return fmt.Errorf("grnxx_column_set() failed") + } + default: + return fmt.Errorf("undefined type") + } + return nil +} + +func (column *Column) Get(rowID Int, value interface{}) error { + switch p := value.(type) { + case *Bool: + if !C.grnxx_column_get(column.handle, C.int64_t(rowID), unsafe.Pointer(p)) { + return fmt.Errorf("grnxx_column_get() failed") + } + case *Int: + if !C.grnxx_column_get(column.handle, C.int64_t(rowID), unsafe.Pointer(p)) { + return fmt.Errorf("grnxx_column_get() failed") + } + case *Float: + if !C.grnxx_column_get(column.handle, C.int64_t(rowID), unsafe.Pointer(p)) { + return fmt.Errorf("grnxx_column_get() failed") + } + case *GeoPoint: + if !C.grnxx_column_get(column.handle, C.int64_t(rowID), unsafe.Pointer(p)) { + return fmt.Errorf("grnxx_column_get() failed") + } + case *Text: + var text C.grnxx_text + if !C.grnxx_column_get(column.handle, C.int64_t(rowID), unsafe.Pointer(&text)) { + return fmt.Errorf("grnxx_column_get() failed") + } + if Int(text.size).IsNA() { + *p = nil + } else { + *p = C.GoBytes(unsafe.Pointer(text.data), C.int(text.size)) + } + default: + return fmt.Errorf("undefined type") + } + return nil +} + +func (column *Column) Contains(value Valuer) bool { + switch x := value.(type) { + case nil: + return bool(C.grnxx_column_contains(column.handle, nil)) + case Bool: + return bool(C.grnxx_column_contains(column.handle, unsafe.Pointer(&x))) + case Int: + return bool(C.grnxx_column_contains(column.handle, unsafe.Pointer(&x))) + case Float: + return bool(C.grnxx_column_contains(column.handle, unsafe.Pointer(&x))) + case GeoPoint: + return bool(C.grnxx_column_contains(column.handle, unsafe.Pointer(&x))) + case Text: + text := x.Convert() + return bool(C.grnxx_column_contains(column.handle, unsafe.Pointer(&text))) + } + return false +} + +func (column *Column) FindOne(value Valuer) Int { + switch x := value.(type) { + case nil: + return Int(C.grnxx_column_find_one(column.handle, nil)) + case Bool: + return Int(C.grnxx_column_find_one(column.handle, unsafe.Pointer(&x))) + case Int: + return Int(C.grnxx_column_find_one(column.handle, unsafe.Pointer(&x))) + case Float: + return Int(C.grnxx_column_find_one(column.handle, unsafe.Pointer(&x))) + case GeoPoint: + return Int(C.grnxx_column_find_one(column.handle, unsafe.Pointer(&x))) + case Text: + text := x.Convert() + return Int(C.grnxx_column_find_one(column.handle, unsafe.Pointer(&text))) + } + return NAInt() +} + +// -- Index -- + +type Index struct { + handle *C.grnxx_index +} + +func (index *Index) Column() *Column { + return &Column{C.grnxx_index_column(index.handle)} +} + +func (index *Index) Name() string { + var size C.size_t + name := C.grnxx_index_name(index.handle, &size) + return C.GoStringN(name, C.int(size)) +} + +func (index *Index) IndexType() IndexType { + return IndexType(C.grnxx_index_index_type(index.handle)) +} + +func (index *Index) NumEntries() int { + return int(C.grnxx_index_num_entries(index.handle)) +} + +func (index *Index) TestUniqueness() bool { + return bool(C.grnxx_index_test_uniqueness(index.handle)) +} + +func (index *Index) Contains(value Valuer) bool { + switch x := value.(type) { + case nil: + return bool(C.grnxx_index_contains(index.handle, nil)) + case Bool: + return bool(C.grnxx_index_contains(index.handle, unsafe.Pointer(&x))) + case Int: + return bool(C.grnxx_index_contains(index.handle, unsafe.Pointer(&x))) + case Float: + return bool(C.grnxx_index_contains(index.handle, unsafe.Pointer(&x))) + case GeoPoint: + return bool(C.grnxx_index_contains(index.handle, unsafe.Pointer(&x))) + case Text: + text := x.Convert() + return bool(C.grnxx_index_contains(index.handle, unsafe.Pointer(&text))) + } + return false +} + +func (index *Index) FindOne(value Valuer) Int { + switch x := value.(type) { + case nil: + return Int(C.grnxx_index_find_one(index.handle, nil)) + case Bool: + return Int(C.grnxx_index_find_one(index.handle, unsafe.Pointer(&x))) + case Int: + return Int(C.grnxx_index_find_one(index.handle, unsafe.Pointer(&x))) + case Float: + return Int(C.grnxx_index_find_one(index.handle, unsafe.Pointer(&x))) + case GeoPoint: + return Int(C.grnxx_index_find_one(index.handle, unsafe.Pointer(&x))) + case Text: + text := x.Convert() + return Int(C.grnxx_index_find_one(index.handle, unsafe.Pointer(&text))) + } + return NAInt() +} + +func (index *Index) Find(value Valuer, options *CursorOptions) (*Cursor, error) { + cursorOptions := convertCursorOptions(options) + var cursor *C.grnxx_cursor + switch x := value.(type) { + case nil: + cursor = C.grnxx_index_find(index.handle, nil, cursorOptions) + case Bool: + cursor = C.grnxx_index_find(index.handle, unsafe.Pointer(&x), cursorOptions) + case Int: + cursor = C.grnxx_index_find(index.handle, unsafe.Pointer(&x), cursorOptions) + case Float: + cursor = C.grnxx_index_find(index.handle, unsafe.Pointer(&x), cursorOptions) + case GeoPoint: + cursor = C.grnxx_index_find(index.handle, unsafe.Pointer(&x), cursorOptions) + case Text: + text := x.Convert() + cursor = C.grnxx_index_find(index.handle, unsafe.Pointer(&text), cursorOptions) + } + if cursor == nil { + return nil, fmt.Errorf("grnxx_index_find() failed") + } + return &Cursor{cursor}, nil +} + +func (index *Index) FindInRange( + lowerBound Valuer, lowerBoundInclusive bool, + upperBound Valuer, upperBoundInclusive bool, + options *CursorOptions) (*Cursor, error) { + cursorOptions := convertCursorOptions(options) + var lowerBoundPointer unsafe.Pointer + switch x := lowerBound.(type) { + case nil: + lowerBoundPointer = nil + case Bool: + lowerBoundPointer = unsafe.Pointer(&x) + case Int: + lowerBoundPointer = unsafe.Pointer(&x) + case Float: + lowerBoundPointer = unsafe.Pointer(&x) + case GeoPoint: + lowerBoundPointer = unsafe.Pointer(&x) + case Text: + text := x.Convert() + lowerBoundPointer = unsafe.Pointer(&text) + } + var upperBoundPointer unsafe.Pointer + switch x := upperBound.(type) { + case nil: + upperBoundPointer = nil + case Bool: + upperBoundPointer = unsafe.Pointer(&x) + case Int: + upperBoundPointer = unsafe.Pointer(&x) + case Float: + upperBoundPointer = unsafe.Pointer(&x) + case GeoPoint: + upperBoundPointer = unsafe.Pointer(&x) + case Text: + text := x.Convert() + upperBoundPointer = unsafe.Pointer(&text) + } + cursor := C.grnxx_index_find_in_range( + index.handle, lowerBoundPointer, C.bool(lowerBoundInclusive), + upperBoundPointer, C.bool(upperBoundInclusive), cursorOptions) + if cursor == nil { + return nil, fmt.Errorf("grnxx_index_find_in_range() failed") + } + return &Cursor{cursor}, nil +} + +func (index *Index) FindStartsWith( + value Valuer, inclusive bool, options *CursorOptions) (*Cursor, error) { + cursorOptions := convertCursorOptions(options) + var cursor *C.grnxx_cursor + switch x := value.(type) { + case nil: + cursor = C.grnxx_index_find_starts_with( + index.handle, nil, C.bool(inclusive), cursorOptions) + case Bool: + cursor = C.grnxx_index_find_starts_with( + index.handle, unsafe.Pointer(&x), C.bool(inclusive), cursorOptions) + case Int: + cursor = C.grnxx_index_find_starts_with( + index.handle, unsafe.Pointer(&x), C.bool(inclusive), cursorOptions) + case Float: + cursor = C.grnxx_index_find_starts_with( + index.handle, unsafe.Pointer(&x), C.bool(inclusive), cursorOptions) + case GeoPoint: + cursor = C.grnxx_index_find_starts_with( + index.handle, unsafe.Pointer(&x), C.bool(inclusive), cursorOptions) + case Text: + text := x.Convert() + cursor = C.grnxx_index_find_starts_with( + index.handle, unsafe.Pointer(&text), C.bool(inclusive), cursorOptions) + } + if cursor == nil { + return nil, fmt.Errorf("grnxx_index_find_starts_with() failed") + } + return &Cursor{cursor}, nil +} + +func (index *Index) FindPrefixes( + value Valuer, options *CursorOptions) (*Cursor, error) { + cursorOptions := convertCursorOptions(options) + var cursor *C.grnxx_cursor + switch x := value.(type) { + case nil: + cursor = C.grnxx_index_find_prefixes( + index.handle, nil, cursorOptions) + case Bool: + cursor = C.grnxx_index_find_prefixes( + index.handle, unsafe.Pointer(&x), cursorOptions) + case Int: + cursor = C.grnxx_index_find_prefixes( + index.handle, unsafe.Pointer(&x), cursorOptions) + case Float: + cursor = C.grnxx_index_find_prefixes( + index.handle, unsafe.Pointer(&x), cursorOptions) + case GeoPoint: + cursor = C.grnxx_index_find_prefixes( + index.handle, unsafe.Pointer(&x), cursorOptions) + case Text: + text := x.Convert() + cursor = C.grnxx_index_find_prefixes( + index.handle, unsafe.Pointer(&text), cursorOptions) + } + if cursor == nil { + return nil, fmt.Errorf("grnxx_index_find_prefixes() failed") + } + return &Cursor{cursor}, nil +} + +// -- Cursor -- + +// TODO: CursorOptions should be generated with NewCursorOptions(). +type CursorOptions struct { + Offset int + Limit int + OrderType OrderType +} + +func convertCursorOptions(options *CursorOptions) *C.grnxx_cursor_options { + var cursorOptions C.grnxx_cursor_options + if options == nil { + cursorOptions.offset = 0 + cursorOptions.limit = math.MaxInt32 + cursorOptions.order_type = C.GRNXX_REGULAR_ORDER + } else { + cursorOptions.offset = C.size_t(options.Offset) + cursorOptions.limit = C.size_t(options.Limit) + cursorOptions.order_type = C.grnxx_order_type(options.OrderType) + } + return &cursorOptions +} + +type Cursor struct { + handle *C.grnxx_cursor +} + +func (cursor *Cursor) Close() error { + C.grnxx_cursor_close(cursor.handle) + return nil +} + +func (cursor *Cursor) Read(records []Record) int { + if len(records) == 0 { + return 0 + } + return int(C.grnxx_cursor_read(cursor.handle, + (*C.grnxx_record)(unsafe.Pointer(&records[0])), C.size_t(len(records)))) +} + +// -- Expression -- + +type Expression struct { + handle *C.grnxx_expression +} + +func ParseExpression(table *Table, query string) (*Expression, error) { + queryCString := C.CString(query) + defer C.free(unsafe.Pointer(queryCString)) + expression := C.grnxx_expression_parse(table.handle, queryCString) + if expression == nil { + return nil, fmt.Errorf("grnxx_expression_parse() failed") + } + return &Expression{expression}, nil +} + +func (this *Expression) Close() { + C.grnxx_expression_close(this.handle) +} + +func (this *Expression) Table() *Table { + return &Table{C.grnxx_expression_table(this.handle)} +} + +func (this *Expression) DataType() DataType { + return DataType(C.grnxx_expression_data_type(this.handle)) +} + +func (this *Expression) IsRowID() bool { + return bool(C.grnxx_expression_is_row_id(this.handle)) +} + +func (this *Expression) IsScore() bool { + return bool(C.grnxx_expression_is_score(this.handle)) +} + +func (this *Expression) BlockSize() int { + return int(C.grnxx_expression_block_size(this.handle)) +} + +func (this *Expression) Filter(records *[]Record) error { + if len(*records) == 0 { + return nil + } + internalRecords := (*C.grnxx_record)(unsafe.Pointer(&(*records)[0])) + size := C.size_t(len(*records)) + if !C.grnxx_expression_filter(this.handle, internalRecords, + &size, C.size_t(0), C.size_t(math.MaxInt32)) { + return fmt.Errorf("grnxx_expression_filter() failed") + } + *records = (*records)[:int(size)] + return nil +} + +func (this *Expression) FilterEx( + records *[]Record, offset int, limit int) error { + if len(*records) == 0 { + return nil + } + internalRecords := (*C.grnxx_record)(unsafe.Pointer(&(*records)[0])) + size := C.size_t(len(*records)) + if !C.grnxx_expression_filter(this.handle, internalRecords, + &size, C.size_t(offset), C.size_t(limit)) { + return fmt.Errorf("grnxx_expression_filter() failed") + } + *records = (*records)[:int(size)] + return nil +} + +func (this *Expression) Adjust(records []Record) error { + if len(records) == 0 { + return nil + } + internalRecords := (*C.grnxx_record)(unsafe.Pointer(&records[0])) + size := C.size_t(len(records)) + if !C.grnxx_expression_adjust(this.handle, internalRecords, size) { + return fmt.Errorf("grnxx_expression_adjust() failed") + } + return nil +} + +func (this *Expression) Evaluate(records []Record, values interface{}) error { + if len(records) == 0 { + return nil + } + internalRecords := (*C.grnxx_record)(unsafe.Pointer(&records[0])) + size := C.size_t(len(records)) + var result C.bool + switch x := values.(type) { + case []Bool: + result = C.grnxx_expression_evaluate( + this.handle, internalRecords, size, unsafe.Pointer(&x[0])) + case []Int: + result = C.grnxx_expression_evaluate( + this.handle, internalRecords, size, unsafe.Pointer(&x[0])) + case []Float: + result = C.grnxx_expression_evaluate( + this.handle, internalRecords, size, unsafe.Pointer(&x[0])) + case []GeoPoint: + result = C.grnxx_expression_evaluate( + this.handle, internalRecords, size, unsafe.Pointer(&x[0])) + case []Text: + internalValues := make([]C.grnxx_text, len(records)) + result = C.grnxx_expression_evaluate( + this.handle, internalRecords, size, unsafe.Pointer(&internalValues[0])) + for i := 0; i < len(records); i++ { + // TODO: Deep-copy should not be done? + x[i] = C.GoBytes(unsafe.Pointer(internalValues[i].data), + C.int(internalValues[i].size)) + } + default: + return fmt.Errorf("unsupported data type") + } + if !result { + return fmt.Errorf("grnxx_expression_evaluate() failed") + } + return nil +} + +// -- ExpressionBuilder -- + +type ExpressionBuilder struct { + handle *C.grnxx_expression_builder +} + +func CreateExpressionBuilder(table *Table) (*ExpressionBuilder, error) { + builder := C.grnxx_expression_builder_create(table.handle) + if builder == nil { + return nil, fmt.Errorf("grnxx_expression_builder_create() failed") + } + return &ExpressionBuilder{builder}, nil +} + +func (this *ExpressionBuilder) Close() { + C.grnxx_expression_builder_close(this.handle) +} + +func (this *ExpressionBuilder) Table() *Table { + return &Table{C.grnxx_expression_builder_table(this.handle)} +} + +func (this *ExpressionBuilder) PushConstant(value Valuer) error { + var result C.bool + switch x := value.(type) { + case Bool: + result = C.grnxx_expression_builder_push_constant( + this.handle, C.GRNXX_BOOL, unsafe.Pointer(&x)) + case Int: + result = C.grnxx_expression_builder_push_constant( + this.handle, C.GRNXX_INT, unsafe.Pointer(&x)) + case Float: + result = C.grnxx_expression_builder_push_constant( + this.handle, C.GRNXX_FLOAT, unsafe.Pointer(&x)) + case GeoPoint: + result = C.grnxx_expression_builder_push_constant( + this.handle, C.GRNXX_GEO_POINT, unsafe.Pointer(&x)) + case Text: + text := x.Convert() + result = C.grnxx_expression_builder_push_constant( + this.handle, C.GRNXX_TEXT, unsafe.Pointer(&text)) + default: + return fmt.Errorf("unsupported data type") + } + if !result { + return fmt.Errorf("grnxx_expression_builder_push_constant() failed") + } + return nil +} + +func (this *ExpressionBuilder) PushRowID() error { + if !C.grnxx_expression_builder_push_row_id(this.handle) { + fmt.Errorf("grnxx_expression_builder_push_row_id() failed") + } + return nil +} + +func (this *ExpressionBuilder) PushScore() error { + if !C.grnxx_expression_builder_push_score(this.handle) { + fmt.Errorf("grnxx_expression_builder_push_score() failed") + } + return nil +} + +func (this *ExpressionBuilder) PushColumn(name string) error { + nameCString := C.CString(name) + defer C.free(unsafe.Pointer(nameCString)) + if !C.grnxx_expression_builder_push_column(this.handle, nameCString) { + fmt.Errorf("grnxx_expression_builder_push_column() failed") + } + return nil +} + +func (this *ExpressionBuilder) PushOperator(operatorType OperatorType) error { + if !C.grnxx_expression_builder_push_operator( + this.handle, C.grnxx_operator_type(operatorType)) { + fmt.Errorf("grnxx_expression_builder_push_operator() failed") + } + return nil +} + +func (this *ExpressionBuilder) BeginSubexpression() error { + if !C.grnxx_expression_builder_begin_subexpression(this.handle) { + return fmt.Errorf("grnxx_expression_builder_begin_subexpression() failed") + } + return nil +} + +func (this *ExpressionBuilder) EndSubexpression() error { + if !C.grnxx_expression_builder_end_subexpression(this.handle) { + return fmt.Errorf("grnxx_expression_builder_end_subexpression() failed") + } + return nil +} + +func (this *ExpressionBuilder) Clear() { + C.grnxx_expression_builder_clear(this.handle) +} + +func (this *ExpressionBuilder) Release() (*Expression, error) { + expression := C.grnxx_expression_builder_release(this.handle) + if expression == nil { + return nil, fmt.Errorf("grnxx_expression_builder_release() failed") + } + return &Expression{expression}, nil +} + +// -- Sorter -- + +type SorterOrder struct { + Expression *Expression + OrderType OrderType +} + +func convertSorterOrders(orders []SorterOrder) []C.grnxx_sorter_order { + internalOrders := make([]C.grnxx_sorter_order, len(orders)) + for i := 0; i < len(orders); i++ { + internalOrders[i].expression = orders[i].Expression.handle + internalOrders[i].order_type = C.grnxx_order_type(orders[i].OrderType) + } + return internalOrders +} + +type SorterOptions struct { + Offset int + Limit int +} + +func convertSorterOptions(options *SorterOptions) *C.grnxx_sorter_options { + if options == nil { + return nil + } + var internalOptions C.grnxx_sorter_options + internalOptions.offset = C.size_t(options.Offset) + internalOptions.limit = C.size_t(options.Limit) + return &internalOptions +} + +type Sorter struct { + handle *C.grnxx_sorter +} + +func CreateSorter(orders []SorterOrder, options *SorterOptions) (*Sorter, error) { + if len(orders) == 0 { + return nil, fmt.Errorf("no orders") + } + internalOrders := convertSorterOrders(orders) + sorter := C.grnxx_sorter_create( + &internalOrders[0], C.size_t(len(orders)), convertSorterOptions(options)) + if sorter == nil { + return nil, fmt.Errorf("grnxx_sorter_create() failed") + } + return &Sorter{sorter}, nil +} + +func (this *Sorter) Close() error { + C.grnxx_sorter_close(this.handle) + return nil +} + +// -- Merger -- + +type MergerOptions struct { + LogicalOperatorType MergerOperatorType + ScoreOperatorType MergerOperatorType + MissingScore Float + Offset int + Limit int +} + +func convertMergerOptions(options *MergerOptions) *C.grnxx_merger_options { + if options == nil { + return nil + } + var internalOptions C.grnxx_merger_options + internalOptions.logical_operator_type = + C.grnxx_merger_operator_type(options.LogicalOperatorType) + internalOptions.score_operator_type = + C.grnxx_merger_operator_type(options.ScoreOperatorType) + internalOptions.missing_score = C.double(options.MissingScore) + internalOptions.offset = C.size_t(options.Offset) + internalOptions.limit = C.size_t(options.Limit) + return &internalOptions +} + +type Merger struct { + handle *C.grnxx_merger +} + +func CreateMerger(options *MergerOptions) (*Merger, error) { + merger := C.grnxx_merger_create(convertMergerOptions(options)) + if merger == nil { + return nil, fmt.Errorf("grnxx_merger_create() failed") + } + return &Merger{merger}, nil +} + +func (this *Merger) Close() error { + C.grnxx_merger_close(this.handle) + return nil +} + +// -- Pipeline -- + +type Pipeline struct { + handle *C.grnxx_pipeline +} + +func (this *Pipeline) Close() error { + C.grnxx_pipeline_close(this.handle) + return nil +} + +func (this *Pipeline) Table() *Table { + return &Table{C.grnxx_pipeline_table(this.handle)} +} + +func (this *Pipeline) Flush() ([]Record, error) { + var ptr *C.grnxx_record + var size C.size_t + if !C.grnxx_pipeline_flush(this.handle, &ptr, &size) { + return nil, fmt.Errorf("grnxx_pipeline_flush() failed") + } + defer C.free(unsafe.Pointer(ptr)) + header := reflect.SliceHeader{ + Data: uintptr(unsafe.Pointer(ptr)), + Len: int(size), + Cap: int(size), + } + internalRecords := *(*[]C.grnxx_record)(unsafe.Pointer(&header)) + records := make([]Record, int(size)) + for i := 0; i < len(records); i++ { + records[i].RowID = Int(internalRecords[i].row_id) + records[i].Score = Float(internalRecords[i].score) + } + return records, nil +} + +// -- PipelineBuilder -- + +type PipelineOptions struct { +} + +func convertPipelineOptions(options *PipelineOptions) *C.grnxx_pipeline_options { + return nil +} + +type PipelineBuilder struct { + handle *C.grnxx_pipeline_builder +} + +func CreatePipelineBuilder(table *Table) (*PipelineBuilder, error) { + builder := C.grnxx_pipeline_builder_create(table.handle) + if builder == nil { + return nil, fmt.Errorf("grnxx_pipeline_builder_create() failed") + } + return &PipelineBuilder{builder}, nil +} + +func (this *PipelineBuilder) Close() error { + C.grnxx_pipeline_builder_close(this.handle) + return nil +} + +func (this *PipelineBuilder) Table() *Table { + return &Table{C.grnxx_pipeline_builder_table(this.handle)} +} + +func (this *PipelineBuilder) PushCursor(cursor *Cursor) error { + if !C.grnxx_pipeline_builder_push_cursor(this.handle, cursor.handle) { + return fmt.Errorf("grnxx_pipeline_builder_push_cursor() failed") + } + return nil +} + +func (this *PipelineBuilder) PushFilter( + expression *Expression, offset int, limit int) error { + if !C.grnxx_pipeline_builder_push_filter( + this.handle, expression.handle, C.size_t(offset), C.size_t(limit)) { + return fmt.Errorf("grnxx_pipeline_builder_push_filter() failed") + } + return nil +} + +func (this *PipelineBuilder) PushAdjuster(expression *Expression) error { + if !C.grnxx_pipeline_builder_push_adjuster(this.handle, expression.handle) { + return fmt.Errorf("grnxx_pipeline_builder_push_adjuster() failed") + } + return nil +} + +func (this *PipelineBuilder) PushSorter(sorter *Sorter) error { + if !C.grnxx_pipeline_builder_push_sorter(this.handle, sorter.handle) { + return fmt.Errorf("grnxx_pipeline_builder_push_sorter() failed") + } + return nil +} + +func (this *PipelineBuilder) PushMerger(options *MergerOptions) error { + internalOptions := convertMergerOptions(options) + if !C.grnxx_pipeline_builder_push_merger(this.handle, internalOptions) { + return fmt.Errorf("grnxx_pipeline_builder_push_merger() failed") + } + return nil +} + +func (this *PipelineBuilder) Clear() error { + C.grnxx_pipeline_builder_clear(this.handle) + return nil +} + +func (this *PipelineBuilder) Release(options *PipelineOptions) (*Pipeline, error) { + internalOptions := convertPipelineOptions(options) + pipeline := C.grnxx_pipeline_builder_release(this.handle, internalOptions) + if pipeline == nil { + return nil, fmt.Errorf("grnxx_pipeline_builder_release() failed") + } + return &Pipeline{pipeline}, nil +} Added: go/gnx/grnxx/grnxx.h (+327 -0) 100644 =================================================================== --- /dev/null +++ go/gnx/grnxx/grnxx.h 2015-03-20 21:42:11 +0900 (1ee051a) @@ -0,0 +1,327 @@ +#ifndef GRNXX_H +#define GRNXX_H + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + +#include <grnxx/constants.h> + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +const char *grnxx_package(void); +const char *grnxx_version(void); + +typedef struct grnxx_db grnxx_db; +typedef struct grnxx_table grnxx_table; +typedef struct grnxx_column grnxx_column; +typedef struct grnxx_index grnxx_index; +typedef struct grnxx_cursor grnxx_cursor; +typedef struct grnxx_expression grnxx_expression; +typedef struct grnxx_expression_builder grnxx_expression_builder; +typedef struct grnxx_sorter grnxx_sorter; +typedef struct grnxx_merger grnxx_merger; +typedef struct grnxx_pipeline grnxx_pipeline; +typedef struct grnxx_pipeline_builder grnxx_pipeline_builder; + +typedef uint8_t grnxx_bool; + +enum { + GRNXX_BOOL_NA = 1, + GRNXX_BOOL_TRUE = 3, + GRNXX_BOOL_FALSE = 0 +}; + +typedef struct { + int32_t latitude; + int32_t longitude; +} grnxx_geo_point; + +typedef struct { + const char *data; + int64_t size; +} grnxx_text; + +typedef struct { + int64_t row_id; + double score; +} grnxx_record; + +typedef struct { + const char *reference_table_name; +} grnxx_column_options; + +// TODO: Settings should be done by using functions? +typedef struct { + size_t offset; + size_t limit; + grnxx_order_type order_type; +} grnxx_cursor_options; + +typedef struct { + grnxx_expression *expression; + grnxx_order_type order_type; +} grnxx_sorter_order; + +// TODO: Settings should be done by using functions? +typedef struct { + size_t offset; + size_t limit; +} grnxx_sorter_options; + +// TODO: Settings should be done by using functions? +typedef struct { + grnxx_merger_operator_type logical_operator_type; + grnxx_merger_operator_type score_operator_type; + double missing_score; + size_t offset; + size_t limit; +} grnxx_merger_options; + +typedef struct { +} grnxx_pipeline_options; + +// -- DB -- + +grnxx_db *grnxx_db_create(void); +void grnxx_db_close(grnxx_db *db); + +size_t grnxx_db_num_tables(grnxx_db *db); + +grnxx_table *grnxx_db_create_table(grnxx_db *db, const char *name); +bool grnxx_db_remove_table(grnxx_db *db, const char *name); +bool grnxx_db_rename_table(grnxx_db *db, + const char *name, + const char *new_name); +bool grnxx_db_reorder_table(grnxx_db *db, + const char *name, + const char *prev_name); +grnxx_table *grnxx_db_get_table(grnxx_db *db, size_t table_id); +grnxx_table *grnxx_db_find_table(grnxx_db *db, const char *name); + +// -- Table -- + +grnxx_db *grnxx_table_db(grnxx_table *table); +const char *grnxx_table_name(grnxx_table *table, size_t *size); +size_t grnxx_table_num_columns(grnxx_table *table); +grnxx_column *grnxx_table_key_column(grnxx_table *table); +size_t grnxx_table_num_rows(grnxx_table *table); +int64_t grnxx_table_max_row_id(grnxx_table *table); +bool grnxx_table_is_empty(grnxx_table *table); +bool grnxx_table_is_full(grnxx_table *table); + +// TODO: Support references. +grnxx_column *grnxx_table_create_column(grnxx_table *table, + const char *name, + grnxx_data_type data_type, + const grnxx_column_options *options); +bool grnxx_table_remove_column(grnxx_table *table, const char *name); +bool grnxx_table_rename_column(grnxx_table *table, + const char *name, + const char *new_name); +bool grnxx_table_reorder_column(grnxx_table *table, + const char *name, + const char *prev_name); +grnxx_column *grnxx_table_get_column(grnxx_table *table, size_t column_id); +grnxx_column *grnxx_table_find_column(grnxx_table *table, const char *name); + +bool grnxx_table_set_key_column(grnxx_table *table, const char *name); +bool grnxx_table_unset_key_column(grnxx_table *table); + +int64_t grnxx_table_insert_row(grnxx_table *table, const void *key); +size_t grnxx_table_insert_rows(grnxx_table *table, + size_t num_keys, + const void *keys, + int64_t *row_ids); +bool grnxx_table_insert_row_at(grnxx_table *table, + int64_t row_id, + const void *key); +int64_t grnxx_table_find_or_insert_row(grnxx_table *table, + const void *key, + bool *inserted); +bool grnxx_table_remove_row(grnxx_table *table, int64_t row_id); +bool grnxx_table_test_row(grnxx_table *table, int64_t row_id); +int64_t grnxx_table_find_row(grnxx_table *table, const void *key); + +grnxx_cursor *grnxx_table_create_cursor(grnxx_table *table, + const grnxx_cursor_options *options); + +// -- Column -- + +grnxx_table *grnxx_column_table(grnxx_column *column); +const char *grnxx_column_name(grnxx_column *column, size_t *size); +grnxx_data_type grnxx_column_data_type(grnxx_column *column); +grnxx_table *grnxx_column_reference_table(grnxx_column *column); +bool grnxx_column_is_key(grnxx_column *column); +size_t grnxx_column_num_indexes(grnxx_column *column); + +grnxx_index *grnxx_column_create_index(grnxx_column *column, + const char *name, + grnxx_index_type index_type); +bool grnxx_column_remove_index(grnxx_column *column, const char *name); +bool grnxx_column_rename_index(grnxx_column *column, + const char *name, + const char *new_name); +bool grnxx_column_reorder_index(grnxx_column *column, + const char *name, + const char *prev_name); +grnxx_index *grnxx_column_get_index(grnxx_column *column, size_t index_id); +grnxx_index *grnxx_column_find_index(grnxx_column *column, const char *name); + +bool grnxx_column_set(grnxx_column *column, int64_t row_id, const void *value); +bool grnxx_column_get(grnxx_column *column, int64_t row_id, void *value); + +bool grnxx_column_contains(grnxx_column *column, const void *value); +int64_t grnxx_column_find_one(grnxx_column *column, const void *value); + +// -- Index -- + +grnxx_column *grnxx_index_column(grnxx_index *index); +const char *grnxx_index_name(grnxx_index *index, size_t *size); +grnxx_index_type grnxx_index_index_type(grnxx_index *index); +size_t grnxx_index_num_entries(grnxx_index *index); + +bool grnxx_index_test_uniqueness(grnxx_index *index); + +bool grnxx_index_contains(grnxx_index *index, const void *value); +int64_t grnxx_index_find_one(grnxx_index *index, const void *value); + +grnxx_cursor *grnxx_index_find(grnxx_index *index, + const void *value, + const grnxx_cursor_options *options); +grnxx_cursor *grnxx_index_find_in_range(grnxx_index *index, + const void *lower_bound_value, + bool lower_bound_is_inclusive, + const void *upper_bound_value, + bool upper_bound_is_inclusive, + const grnxx_cursor_options *options); +grnxx_cursor *grnxx_index_find_starts_with(grnxx_index *index, + const void *prefix, + bool prefix_is_inclusive, + const grnxx_cursor_options *options); +grnxx_cursor *grnxx_index_find_prefixes(grnxx_index *index, + const void *value, + const grnxx_cursor_options *options); + +// -- Cursor -- + +void grnxx_cursor_close(grnxx_cursor *cursor); + +size_t grnxx_cursor_read(grnxx_cursor *cursor, + grnxx_record *records, + size_t size); + +// -- Expression -- + +grnxx_expression *grnxx_expression_parse(grnxx_table *table, + const char *query); +void grnxx_expression_close(grnxx_expression *expression); + +grnxx_table *grnxx_expression_table(grnxx_expression *expression); +grnxx_data_type grnxx_expression_data_type(grnxx_expression *expression); +bool grnxx_expression_is_row_id(grnxx_expression *expression); +bool grnxx_expression_is_score(grnxx_expression *expression); +size_t grnxx_expression_block_size(grnxx_expression *expression); + +bool grnxx_expression_filter(grnxx_expression *expression, + grnxx_record *records, + size_t *size, + size_t offset, + size_t limit); +bool grnxx_expression_adjust(grnxx_expression *expression, + grnxx_record *records, + size_t size); +bool grnxx_expression_evaluate(grnxx_expression *expression, + const grnxx_record *records, + size_t size, + void *values); + +// -- ExpressionBuilder -- + +grnxx_expression_builder *grnxx_expression_builder_create(grnxx_table *table); +void grnxx_expression_builder_close(grnxx_expression_builder *builder); + +grnxx_table *grnxx_expression_builder_table(grnxx_expression_builder *builder); + +bool grnxx_expression_builder_push_constant(grnxx_expression_builder *builder, + grnxx_data_type data_type, + const void *value); +bool grnxx_expression_builder_push_row_id(grnxx_expression_builder *builder); +bool grnxx_expression_builder_push_score(grnxx_expression_builder *builder); +bool grnxx_expression_builder_push_column(grnxx_expression_builder *builder, + const char *column_name); +bool grnxx_expression_builder_push_operator(grnxx_expression_builder *builder, + grnxx_operator_type operator_type); + +bool grnxx_expression_builder_begin_subexpression( + grnxx_expression_builder *builder); +bool grnxx_expression_builder_end_subexpression( + grnxx_expression_builder *builder); + +void grnxx_expression_builder_clear(grnxx_expression_builder *builder); + +grnxx_expression *grnxx_expression_builder_release( + grnxx_expression_builder *builder); + +// -- Sorter -- + +// On success, the ownership of given expressions in "order" is moved to the +// created sorter object. +// On failure, given expressions are closed. +grnxx_sorter *grnxx_sorter_create(grnxx_sorter_order *orders, + size_t num_orders, + const grnxx_sorter_options *options); +void grnxx_sorter_close(grnxx_sorter *sorter); + +// -- Merger -- + +grnxx_merger *grnxx_merger_create(const grnxx_merger_options *options); +void grnxx_merger_close(grnxx_merger *merger); + +// -- Pipeline -- + +void grnxx_pipeline_close(grnxx_pipeline *pipeline); + +grnxx_table *grnxx_pipeline_table(grnxx_pipeline *pipeline); + +// On success, the starting address of records is stored into "*records". +// The caller must free the "*records". +bool grnxx_pipeline_flush(grnxx_pipeline *pipeline, + grnxx_record **records, + size_t *size); + +// -- PipelineBuilder -- + +grnxx_pipeline_builder *grnxx_pipeline_builder_create(grnxx_table *table); +void grnxx_pipeline_builder_close(grnxx_pipeline_builder *builder); + +grnxx_table *grnxx_pipeline_builder_table(grnxx_pipeline_builder *builder); + +// On success, the ownership of given objects is moved to the pipeline. +// On failure, given objects are closed. +bool grnxx_pipeline_builder_push_cursor(grnxx_pipeline_builder *builder, + grnxx_cursor *cursor); +bool grnxx_pipeline_builder_push_filter(grnxx_pipeline_builder *builder, + grnxx_expression *expression, + size_t offset, + size_t limit); +bool grnxx_pipeline_builder_push_adjuster(grnxx_pipeline_builder *builder, + grnxx_expression *expression); +bool grnxx_pipeline_builder_push_sorter(grnxx_pipeline_builder *builder, + grnxx_sorter *sorter); +bool grnxx_pipeline_builder_push_merger(grnxx_pipeline_builder *builder, + const grnxx_merger_options *options); + +void grnxx_pipeline_builder_clear(grnxx_pipeline_builder *builder); + +grnxx_pipeline *grnxx_pipeline_builder_release( + grnxx_pipeline_builder *builder, + const grnxx_pipeline_options *options); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif // GRNXX_H Added: go/gnx/grnxx/grnxx_test.go (+1845 -0) 100644 =================================================================== --- /dev/null +++ go/gnx/grnxx/grnxx_test.go 2015-03-20 21:42:11 +0900 (289226f) @@ -0,0 +1,1845 @@ +package grnxx + +import "reflect" +import "testing" + +func TestPackage(t *testing.T) { + if Package() != "grnxx" { + t.Fatalf("Package name is wrong") + } +} + +func TestVersion(t *testing.T) { + if Version() == "" { + t.Fatalf("Version is not available") + } +} + +func TestEmptyDB(t *testing.T) { + db, error := CreateDB() + if error != nil { + t.Fatalf("CreateDB() failed: error = %v", error) + } + if db == nil { + t.Fatalf("CreateDB() returned nil") + } + numTables := db.NumTables() + if numTables != 0 { + t.Fatalf("Empty DB has tables: numTables = %v", numTables) + } + error = db.Close() + if error != nil { + t.Fatalf("DB.Close() failed: error = %v", error) + } +} + +func TestEmptyTable(t *testing.T) { + db, error := CreateDB() + if error != nil { + t.Fatalf("CreateDB() failed: error = %v", error) + } + defer db.Close() + table, error := db.CreateTable("Table") + if error != nil { + t.Fatalf("DB.CreateTable() failed: error = %v", error) + } + numTables := db.NumTables() + if numTables != 1 { + t.Fatalf("DB.NumTables() returned a wrong value: numTables = %v", + numTables) + } + if table.DB().handle != db.handle { + t.Fatalf("Table.DB() returned a wrong DB") + } + name := table.Name() + if name != "Table" { + t.Fatalf("Table.Name() returned a wrong name: name = %v", name) + } + numColumns := table.NumColumns() + if numColumns != 0 { + t.Fatalf("Empty table has columns: numColumns = %v", numColumns) + } + keyColumn := table.KeyColumn() + if keyColumn != nil { + t.Fatalf("Empty column has key column") + } + numRows := table.NumRows() + if numRows != 0 { + t.Fatalf("Empty table has rows: numRows = %v", numRows) + } + maxRowID := table.MaxRowID() + if !maxRowID.IsNA() { + t.Fatalf("Empty table has valid max. row ID: maxRowID = %v", maxRowID) + } + if !table.IsEmpty() { + t.Fatalf("Empty table's IsEmpty() returned false") + } + if !table.IsFull() { + t.Fatalf("Empty table's IsFull() returned false") + } + error = db.RemoveTable("Table") + if error != nil { + t.Fatalf("DB.RemoveTable() failed: error = %v", error) + } +} + +func TestTableRename(t *testing.T) { + db, error := CreateDB() + if error != nil { + t.Fatalf("CreateDB() failed: error = %v", error) + } + defer db.Close() + table, error := db.CreateTable("Table") + if error != nil { + t.Fatalf("DB.CreateTable() failed: error = %v", error) + } + error = db.RenameTable("Table", "FirstTable") + if error != nil { + t.Fatalf("DB.RenameTable() failed: error = %v", error) + } + name := table.Name() + if name != "FirstTable" { + t.Fatalf("Table name is wrong: name = %v", name) + } + table2, error := db.CreateTable("FirstTable") + if error == nil { + t.Fatalf("DB.CreateTable() could not detect name collision") + } + table2, error = db.CreateTable("SecondTable") + if error != nil { + t.Fatalf("DB.CreateTable() failed: error = %v", error) + } + name = table2.Name() + if name != "SecondTable" { + t.Fatalf("Table name is wrong: name = %v", name) + } + error = db.RenameTable("FirstTable", "SecondTable") + if error == nil { + t.Fatalf("DB.RenameTable() could not detect name collision") + } + error = db.RenameTable("FirstTable", "Table") + if error != nil { + t.Fatalf("DB.RenameTable() failed: error = %v", error) + } +} + +func TestTableReordering(t *testing.T) { + // TODO +} + +func TestEmptyColumn(t *testing.T) { + db, error := CreateDB() + if error != nil { + t.Fatalf("CreateDB() failed: error = %v", error) + } + defer db.Close() + table, error := db.CreateTable("Table") + if error != nil { + t.Fatalf("DB.CreateTable() failed: error = %v", error) + } + column, error := table.CreateColumn("Column", INT, nil) + if error != nil { + t.Fatalf("Table.CreateColumn() failed: error = %v", error) + } + numColumns := table.NumColumns() + if numColumns != 1 { + t.Fatalf("Table.NumColumns() returned a wrong value: numColumns = %v", + numColumns) + } + if column.Table().handle != table.handle { + t.Fatalf("Column.Table() returned a wrong table") + } + name := column.Name() + if name != "Column" { + t.Fatalf("Column.Name() returned a wrong name: name = %v", name) + } + dataType := column.DataType() + if dataType != INT { + t.Fatalf("Column.DataType() returned a wrong data type: dataType = %v", + dataType) + } + referenceTable := column.ReferenceTable() + if referenceTable != nil { + t.Fatalf("Empty column has a reference table: referenceTable.Name() = %v", + referenceTable.Name()) + } + if column.IsKey() { + t.Fatalf("Empty column is key") + } + numIndexes := column.NumIndexes() + if numIndexes != 0 { + t.Fatalf("Empty column has indexes: numIndexes = %v", numIndexes) + } + table.RemoveColumn("Column") + if error != nil { + t.Fatalf("Table.RemoveColumn() failed: error = %v", error) + } +} + +func TestColumnRename(t *testing.T) { + db, error := CreateDB() + if error != nil { + t.Fatalf("CreateDB() failed: error = %v", error) + } + defer db.Close() + table, error := db.CreateTable("Table") + if error != nil { + t.Fatalf("DB.CreateTable() failed: error = %v", error) + } + column, error := table.CreateColumn("Column", INT, nil) + if error != nil { + t.Fatalf("Table.CreateColumn() failed: error = %v", error) + } + error = table.RenameColumn("Column", "FirstColumn") + if error != nil { + t.Fatalf("Table.RenameColumn() failed: error = %v", error) + } + name := column.Name() + if name != "FirstColumn" { + t.Fatalf("Column name is wrong: name = %v", name) + } + column2, error := table.CreateColumn("FirstColumn", INT, nil) + if error == nil { + t.Fatalf("Table.CreateColumn() could not detect name collision") + } + column2, error = table.CreateColumn("SecondColumn", INT, nil) + if error != nil { + t.Fatalf("Table.CreateColumn() failed: error = %v", error) + } + name = column2.Name() + if name != "SecondColumn" { + t.Fatalf("Column name is wrong: name = %v", name) + } + error = table.RenameColumn("FirstColumn", "SecondColumn") + if error == nil { + t.Fatalf("Table.RenameColumn() could not detect name collision") + } + error = table.RenameColumn("FirstColumn", "Column") + if error != nil { + t.Fatalf("Table.RenameColumn() failed: error = %v", error) + } +} + +func TestColumnReordering(t *testing.T) { + // TODO +} + +func TestEmptyIndex(t *testing.T) { + db, error := CreateDB() + if error != nil { + t.Fatalf("CreateDB() failed: error = %v", error) + } + defer db.Close() + table, error := db.CreateTable("Table") + if error != nil { + t.Fatalf("DB.CreateTable() failed: error = %v", error) + } + column, error := table.CreateColumn("Column", INT, nil) + if error != nil { + t.Fatalf("Table.CreateColumn() failed: error = %v", error) + } + index, error := column.CreateIndex("Index", TREE_INDEX) + if error != nil { + t.Fatalf("Column.CreateIndex() failed: error = %v", error) + } + numIndexes := column.NumIndexes() + if numIndexes != 1 { + t.Fatalf("Column.NumIndexes() returned a wrong value: numIndexes = %v", + numIndexes) + } + if index.Column().handle != column.handle { + t.Fatalf("Index.Column() returned a wrong column") + } + name := index.Name() + if name != "Index" { + t.Fatalf("Index.Name() returned a wrong name: name = %v", name) + } + indexType := index.IndexType() + if indexType != TREE_INDEX { + t.Fatalf("Index.IndexType() returned a wrong index type: indexType = %v", + indexType) + } + numEntries := index.NumEntries() + if numEntries != 0 { + t.Fatalf("Empty index has entries: numEntries = %v", numEntries) + } + if !index.TestUniqueness() { + t.Fatalf("Empty index's TestUniqueness() returned true") + } + column.RemoveIndex("Index") + if error != nil { + t.Fatalf("Column.RemoveIndex() failed: error = %v", error) + } +} + +func TestIndexRename(t *testing.T) { + db, error := CreateDB() + if error != nil { + t.Fatalf("CreateDB() failed: error = %v", error) + } + defer db.Close() + table, error := db.CreateTable("Table") + if error != nil { + t.Fatalf("DB.CreateTable() failed: error = %v", error) + } + column, error := table.CreateColumn("Column", INT, nil) + if error != nil { + t.Fatalf("Table.CreateColumn() failed: error = %v", error) + } + index, error := column.CreateIndex("Index", TREE_INDEX) + if error != nil { + t.Fatalf("Column.CreateIndex() failed: error = %v", error) + } + + error = column.RenameIndex("Index", "FirstIndex") + if error != nil { + t.Fatalf("Table.RenameIndex() failed: error = %v", error) + } + name := index.Name() + if name != "FirstIndex" { + t.Fatalf("Index name is wrong: name = %v", name) + } + index2, error := column.CreateIndex("FirstIndex", TREE_INDEX) + if error == nil { + t.Fatalf("Table.CreateIndex() could not detect name collision") + } + index2, error = column.CreateIndex("SecondIndex", TREE_INDEX) + if error != nil { + t.Fatalf("Table.CreateIndex() failed: error = %v", error) + } + name = index2.Name() + if name != "SecondIndex" { + t.Fatalf("Index name is wrong: name = %v", name) + } + error = column.RenameIndex("FirstIndex", "SecondIndex") + if error == nil { + t.Fatalf("Table.RenameIndex() could not detect name collision") + } + error = column.RenameIndex("FirstIndex", "Index") + if error != nil { + t.Fatalf("Table.RenameIndex() failed: error = %v", error) + } +} + +func TestIndexReordering(t *testing.T) { + // TODO +} + +func TestInsertRow(t *testing.T) { + db, error := CreateDB() + if error != nil { + t.Fatalf("CreateDB() failed: error = %v", error) + } + defer db.Close() + table, error := db.CreateTable("Table") + if error != nil { + t.Fatalf("DB.CreateTable() failed: error = %v", error) + } + rowID, error := table.InsertRow(nil) + if error != nil { + t.Fatalf("Table.InsertRow() failed: error = %v", error) + } + if rowID != 0 { + t.Fatalf("Table.InsertRow() returned a wrong value: rowID = %v", rowID) + } + numRows := table.NumRows() + if numRows != 1 { + t.Fatalf("Table.NumRows() returned a wrong value: numRows = %v", numRows) + } + maxRowID := table.MaxRowID() + if maxRowID != rowID { + t.Fatalf("Table.MaxRowID() returned a wrong value: maxRowID = %v", maxRowID) + } + if table.IsEmpty() { + t.Fatalf("Non-empty table's IsEmpty() returned true") + } + if !table.IsFull() { + t.Fatalf("Full table's IsFull() returned false") + } + rowID, error = table.InsertRow(nil) + if error != nil { + t.Fatalf("Table.InsertRow() failed: error = %v", error) + } + if rowID != 1 { + t.Fatalf("Table.InsertRow() returned a wrong value: rowID = %v", rowID) + } + error = db.RemoveTable("Table") + if error != nil { + t.Fatalf("DB.RemoveTable() failed: error = %v", error) + } + + table, error = db.CreateTable("Table") + if error != nil { + t.Fatalf("DB.CreateTable() failed: error = %v", error) + } + column, error := table.CreateColumn("Column", TEXT, nil) + if error != nil { + t.Fatalf("Table.CreateColumn() failed: error = %v", error) + } + error = table.SetKeyColumn("Column") + if error != nil { + t.Fatalf("Table.SetKeyColumn() failed: error = %v", error) + } + rowID, error = table.InsertRow(Text("Hello")) + if error != nil { + t.Fatalf("Table.InsertRow() failed: error = %v", error) + } + if rowID != 0 { + t.Fatalf("Table.InsertRow() returned a wrong value: rowID = %v", rowID) + } + var value Text + error = column.Get(rowID, &value) + if error != nil { + t.Fatalf("Column.Get() failed: error = %v", error) + } + if !reflect.DeepEqual(value, Text("Hello")) { + t.Fatalf("Column.Get() returned a wrong value: value = %v", value) + } + rowID = table.FindRow(Text("Hello")) + if rowID != 0 { + t.Fatalf("Table.FindRow() returned a wrong value: rowID = %v", rowID) + } +} + +func TestTableInsertRows(t *testing.T) { + db, error := CreateDB() + if error != nil { + t.Fatalf("CreateDB() failed: error = %v", error) + } + defer db.Close() + + table, error := db.CreateTable("Table") + if error != nil { + t.Fatalf("DB.CreateTable() failed: error = %v", error) + } + rowIDs, error := table.InsertRows(make([]Int, 3)) + if error != nil { + t.Fatalf("Table.InsertRows() failed: error = %v", error) + } + if len(rowIDs) != 3 { + t.Fatalf("Table.InsertRows() failed: len(rowIDs) = %v", len(rowIDs)) + } + for i := 0; i < 3; i++ { + if rowIDs[i] != Int(i) { + t.Fatalf("Table.InsertRows() failed: i = %v, rowIDs[i] = %v", + i, rowIDs[i]) + } + } + error = db.RemoveTable("Table") + if error != nil { + t.Fatalf("DB.RemoveTable() failed: error = %v", error) + } + + table, error = db.CreateTable("Table") + if error != nil { + t.Fatalf("DB.CreateTable() failed: error = %v", error) + } + column, error := table.CreateColumn("Column", TEXT, nil) + if error != nil { + t.Fatalf("Table.CreateColumn() failed: error = %v", error) + } + error = table.SetKeyColumn("Column") + if error != nil { + t.Fatalf("Table.SetKeyColumn() failed: error = %v", error) + } + keys := []Text{Text("Hello"), Text("World"), Text("!")} + rowIDs, error = table.InsertRows(keys) + if error != nil { + t.Fatalf("Table.InsertRows() failed: error = %v", error) + } + if len(rowIDs) != 3 { + t.Fatalf("Table.InsertRows() failed: len(rowIDs) = %v", len(rowIDs)) + } + for i := 0; i < 3; i++ { + if rowIDs[i] != Int(i) { + t.Fatalf("Table.InsertRows() failed: i = %v, rowIDs[i] = %v", + i, rowIDs[i]) + } + var value Text + error = column.Get(rowIDs[i], &value) + if error != nil { + t.Fatalf("Column.Get() failed: error = %v", error) + } + if !reflect.DeepEqual(value, keys[i]) { + t.Fatalf("Column.Get() returned a wrong value: "+ + "i = %v, key = %v, value = %v", i, keys[i], value) + } + } +} + +func TestTableInsertRowAt(t *testing.T) { + db, error := CreateDB() + if error != nil { + t.Fatalf("CreateDB() failed: error = %v", error) + } + defer db.Close() + table, error := db.CreateTable("Table") + if error != nil { + t.Fatalf("DB.CreateTable() failed: error = %v", error) + } + error = table.InsertRowAt(Int(100), nil) + if error != nil { + t.Fatalf("Table.InsertRow() failed: error = %v", error) + } + numRows := table.NumRows() + if numRows != 1 { + t.Fatalf("Table.NumRows() returned a wrong value: numRows = %v", numRows) + } + maxRowID := table.MaxRowID() + if maxRowID != 100 { + t.Fatalf("Table.MaxRowID() returned a wrong value: maxRowID = %v", maxRowID) + } + if table.IsEmpty() { + t.Fatalf("Non-empty table's IsEmpty() returned true") + } + if table.IsFull() { + t.Fatalf("Non-full table's IsFull() returned true") + } + if table.TestRow(0) { + t.Fatalf("Table.TestRow() returned true for an invalid row ID") + } + if !table.TestRow(100) { + t.Fatalf("Table.TestRow() returned false for a valid row ID") + } +} + +func TestTableFindOrInsertRow(t *testing.T) { + db, error := CreateDB() + if error != nil { + t.Fatalf("CreateDB() failed: error = %v", error) + } + defer db.Close() + table, error := db.CreateTable("Table") + if error != nil { + t.Fatalf("DB.CreateTable() failed: error = %v", error) + } + _, error = table.CreateColumn("Column", TEXT, nil) + if error != nil { + t.Fatalf("Table.CreateColumn() failed: error = %v", error) + } + error = table.SetKeyColumn("Column") + if error != nil { + t.Fatalf("Table.SetKeyColumn() failed: error = %v", error) + } + rowID, inserted, error := table.FindOrInsertRow(Text("Hello")) + if error != nil { + t.Fatalf("Table.FindOrInsertRow() failed: error = %v", error) + } + if (rowID != 0) || !inserted { + t.Fatalf("Table.FindOrInsertRow() returned a wrong value: "+ + "rowID = %v, inserted = %v", rowID, inserted) + } + rowID, inserted, error = table.FindOrInsertRow(Text("Hello")) + if error != nil { + t.Fatalf("Table.FindOrInsertRow() failed: error = %v", error) + } + if (rowID != 0) || inserted { + t.Fatalf("Table.FindOrInsertRow() returned a wrong value: "+ + "rowID = %v, inserted = %v", rowID, inserted) + } + rowID, inserted, error = table.FindOrInsertRow(Text("World")) + if error != nil { + t.Fatalf("Table.FindOrInsertRow() failed: error = %v", error) + } + if (rowID != 1) || !inserted { + t.Fatalf("Table.FindOrInsertRow() returned a wrong value: "+ + "rowID = %v, inserted = %v", rowID, inserted) + } +} + +func TestTableRemoveRow(t *testing.T) { + db, error := CreateDB() + if error != nil { + t.Fatalf("CreateDB() failed: error = %v", error) + } + defer db.Close() + table, error := db.CreateTable("Table") + if error != nil { + t.Fatalf("DB.CreateTable() failed: error = %v", error) + } + rowID, error := table.InsertRow(nil) + if error != nil { + t.Fatalf("Table.InsertRow() failed: error = %v", error) + } + error = table.RemoveRow(rowID) + if error != nil { + t.Fatalf("Table.RemoveRow() failed: error = %v", error) + } + numRows := table.NumRows() + if numRows != 0 { + t.Fatalf("Table.NumRows() returned a wrong value: numRows = %v", numRows) + } + maxRowID := table.MaxRowID() + if !maxRowID.IsNA() { + t.Fatalf("Table.MaxRowID() returned a wrong value: maxRowID = %v", maxRowID) + } + if !table.IsEmpty() { + t.Fatalf("Empty table's IsEmpty() returned false") + } + if !table.IsFull() { + t.Fatalf("Empty table's IsFull() returned false") + } +} + +func TestTableCreateCursor(t *testing.T) { + db, error := CreateDB() + if error != nil { + t.Fatalf("CreateDB() failed: error = %v", error) + } + defer db.Close() + table, error := db.CreateTable("Table") + if error != nil { + t.Fatalf("DB.CreateTable() failed: error = %v", error) + } + rowIDs := make([]Int, 100) + for i := 0; i < 100; i++ { + rowIDs[i], error = table.InsertRow(nil) + if error != nil { + t.Fatalf("Table.InsertRow() failed: error = %v", error) + } + } + cursor, error := table.CreateCursor(nil) + if error != nil { + t.Fatalf("Table.CreateCursor() failed: error = %v", error) + } + defer cursor.Close() + records := make([]Record, 200) + count := cursor.Read(records) + if count != 100 { + t.Fatalf("Cursor.Read() could not read all records: count = %v", count) + } + for i := 0; i < 100; i++ { + if (rowIDs[i] != records[i].RowID) || (records[i].Score != 0.0) { + t.Fatalf("Cursor.Read() returned a wrong record: RowID = %v, Score = %v", + records[i].RowID, records[i].Score) + } + } + cursorOptions := CursorOptions{25, 50, REVERSE_ORDER} + cursor, error = table.CreateCursor(&cursorOptions) + if error != nil { + t.Fatalf("Table.CreateCursor() failed: error = %v", error) + } + defer cursor.Close() + count = cursor.Read(records) + if count != 50 { + t.Fatalf("Cursor.Read() could not read all records: count = %v", count) + } + for i := 0; i < 50; i++ { + j := 100 - 25 - i - 1 + if (rowIDs[j] != records[i].RowID) || (records[i].Score != 0.0) { + t.Fatalf("Cursor.Read() returned a wrong record: RowID = %v, Score = %v", + records[i].RowID, records[i].Score) + } + } +} + +func TestBoolColumn(t *testing.T) { + db, error := CreateDB() + if error != nil { + t.Fatalf("CreateDB() failed: error = %v", error) + } + defer db.Close() + table, error := db.CreateTable("Table") + if error != nil { + t.Fatalf("DB.CreateTable() failed: error = %v", error) + } + column, error := table.CreateColumn("Column", BOOL, nil) + if error != nil { + t.Fatalf("Table.CreateColumn() failed: error = %v", error) + } + rowID, error := table.InsertRow(nil) + if error != nil { + t.Fatalf("Table.InsertRow() failed: error = %v", error) + } + var value Bool + error = column.Get(rowID, &value) + if error != nil { + t.Fatalf("Column.Get() failed: error = %v", error) + } + if !value.IsNA() { + t.Fatalf("Column.Get() returned a wrong value: value = %v", value) + } + + VALUE := TRUE + error = column.Set(rowID, VALUE) + if error != nil { + t.Fatalf("Column.Set() failed: error = %v", error) + } + error = column.Get(rowID, &value) + if error != nil { + t.Fatalf("Column.Get() failed: error = %v", error) + } + if value != VALUE { + t.Fatalf("Column.Get() returned a wrong value: value = %v", value) + } + if !column.Contains(VALUE) { + t.Fatalf("Column.Contains() failed") + } + foundRowID := column.FindOne(VALUE) + if foundRowID != rowID { + t.Fatalf("Column.FindOne() failed: foundRowID = %v", foundRowID) + } + + error = column.Set(rowID, nil) + if error != nil { + t.Fatalf("Column.Set() failed: error = %v", error) + } + error = column.Get(rowID, &value) + if error != nil { + t.Fatalf("Column.Get() failed: error = %v", error) + } + if !value.IsNA() { + t.Fatalf("Column.Get() returned a wrong value: value = %v", value) + } + if !column.Contains(nil) { + t.Fatalf("Column.Contains() failed") + } + foundRowID = column.FindOne(nil) + if foundRowID != rowID { + t.Fatalf("Column.FindOne() failed: foundRowID = %v", foundRowID) + } +} + +func TestIntColumn(t *testing.T) { + db, error := CreateDB() + if error != nil { + t.Fatalf("CreateDB() failed: error = %v", error) + } + defer db.Close() + table, error := db.CreateTable("Table") + if error != nil { + t.Fatalf("DB.CreateTable() failed: error = %v", error) + } + column, error := table.CreateColumn("Column", INT, nil) + if error != nil { + t.Fatalf("Table.CreateColumn() failed: error = %v", error) + } + rowID, error := table.InsertRow(nil) + if error != nil { + t.Fatalf("Table.InsertRow() failed: error = %v", error) + } + var value Int + error = column.Get(rowID, &value) + if error != nil { + t.Fatalf("Column.Get() failed: error = %v", error) + } + if !value.IsNA() { + t.Fatalf("Column.Get() returned a wrong value: value = %v", value) + } + + VALUE := Int(123) + error = column.Set(rowID, VALUE) + if error != nil { + t.Fatalf("Column.Set() failed: error = %v", error) + } + error = column.Get(rowID, &value) + if error != nil { + t.Fatalf("Column.Get() failed: error = %v", error) + } + if value != VALUE { + t.Fatalf("Column.Get() returned a wrong value: value = %v", value) + } + if !column.Contains(VALUE) { + t.Fatalf("Column.Contains() failed") + } + foundRowID := column.FindOne(VALUE) + if foundRowID != rowID { + t.Fatalf("Column.FindOne() failed: foundRowID = %v", foundRowID) + } + + error = column.Set(rowID, nil) + if error != nil { + t.Fatalf("Column.Set() failed: error = %v", error) + } + error = column.Get(rowID, &value) + if error != nil { + t.Fatalf("Column.Get() failed: error = %v", error) + } + if !value.IsNA() { + t.Fatalf("Column.Get() returned a wrong value: value = %v", value) + } + if !column.Contains(nil) { + t.Fatalf("Column.Contains() failed") + } + foundRowID = column.FindOne(nil) + if foundRowID != rowID { + t.Fatalf("Column.FindOne() failed: foundRowID = %v", foundRowID) + } +} + +func TestFloatColumn(t *testing.T) { + db, error := CreateDB() + if error != nil { + t.Fatalf("CreateDB() failed: error = %v", error) + } + defer db.Close() + table, error := db.CreateTable("Table") + if error != nil { + t.Fatalf("DB.CreateTable() failed: error = %v", error) + } + column, error := table.CreateColumn("Column", FLOAT, nil) + if error != nil { + t.Fatalf("Table.CreateColumn() failed: error = %v", error) + } + rowID, error := table.InsertRow(nil) + if error != nil { + t.Fatalf("Table.InsertRow() failed: error = %v", error) + } + var value Float + error = column.Get(rowID, &value) + if error != nil { + t.Fatalf("Column.Get() failed: error = %v", error) + } + if !value.IsNA() { + t.Fatalf("Column.Get() returned a wrong value: value = %v", value) + } + + VALUE := Float(1.25) + error = column.Set(rowID, VALUE) + if error != nil { + t.Fatalf("Column.Set() failed: error = %v", error) + } + error = column.Get(rowID, &value) + if error != nil { + t.Fatalf("Column.Get() failed: error = %v", error) + } + if value != VALUE { + t.Fatalf("Column.Get() returned a wrong value: value = %v", value) + } + if !column.Contains(VALUE) { + t.Fatalf("Column.Contains() failed") + } + foundRowID := column.FindOne(VALUE) + if foundRowID != rowID { + t.Fatalf("Column.FindOne() failed: foundRowID = %v", foundRowID) + } + + error = column.Set(rowID, nil) + if error != nil { + t.Fatalf("Column.Set() failed: error = %v", error) + } + error = column.Get(rowID, &value) + if error != nil { + t.Fatalf("Column.Get() failed: error = %v", error) + } + if !value.IsNA() { + t.Fatalf("Column.Get() returned a wrong value: value = %v", value) + } + if !column.Contains(nil) { + t.Fatalf("Column.Contains() failed") + } + foundRowID = column.FindOne(nil) + if foundRowID != rowID { + t.Fatalf("Column.FindOne() failed: foundRowID = %v", foundRowID) + } +} + +func TestGeoPointColumn(t *testing.T) { + db, error := CreateDB() + if error != nil { + t.Fatalf("CreateDB() failed: error = %v", error) + } + defer db.Close() + table, error := db.CreateTable("Table") + if error != nil { + t.Fatalf("DB.CreateTable() failed: error = %v", error) + } + column, error := table.CreateColumn("Column", GEO_POINT, nil) + if error != nil { + t.Fatalf("Table.CreateColumn() failed: error = %v", error) + } + rowID, error := table.InsertRow(nil) + if error != nil { + t.Fatalf("Table.InsertRow() failed: error = %v", error) + } + var value GeoPoint + error = column.Get(rowID, &value) + if error != nil { + t.Fatalf("Column.Get() failed: error = %v", error) + } + if !value.IsNA() { + t.Fatalf("Column.Get() returned a wrong value: value = %v", value) + } + + VALUE := GeoPoint{123, 456} + error = column.Set(rowID, VALUE) + if error != nil { + t.Fatalf("Column.Set() failed: error = %v", error) + } + error = column.Get(rowID, &value) + if error != nil { + t.Fatalf("Column.Get() failed: error = %v", error) + } + if value != VALUE { + t.Fatalf("Column.Get() returned a wrong value: value = %v", value) + } + if !column.Contains(VALUE) { + t.Fatalf("Column.Contains() failed") + } + foundRowID := column.FindOne(VALUE) + if foundRowID != rowID { + t.Fatalf("Column.FindOne() failed: foundRowID = %v", foundRowID) + } + + error = column.Set(rowID, nil) + if error != nil { + t.Fatalf("Column.Set() failed: error = %v", error) + } + error = column.Get(rowID, &value) + if error != nil { + t.Fatalf("Column.Get() failed: error = %v", error) + } + if !value.IsNA() { + t.Fatalf("Column.Get() returned a wrong value: value = %v", value) + } + if !column.Contains(nil) { + t.Fatalf("Column.Contains() failed") + } + foundRowID = column.FindOne(nil) + if foundRowID != rowID { + t.Fatalf("Column.FindOne() failed: foundRowID = %v", foundRowID) + } +} + +func TestTextColumn(t *testing.T) { + db, error := CreateDB() + if error != nil { + t.Fatalf("CreateDB() failed: error = %v", error) + } + defer db.Close() + table, error := db.CreateTable("Table") + if error != nil { + t.Fatalf("DB.CreateTable() failed: error = %v", error) + } + column, error := table.CreateColumn("Column", TEXT, nil) + if error != nil { + t.Fatalf("Table.CreateColumn() failed: error = %v", error) + } + rowID, error := table.InsertRow(nil) + if error != nil { + t.Fatalf("Table.InsertRow() failed: error = %v", error) + } + var value Text + error = column.Get(rowID, &value) + if error != nil { + t.Fatalf("Column.Get() failed: error = %v", error) + } + if !value.IsNA() { + t.Fatalf("Column.Get() returned a wrong value: value = %v", value) + } + + VALUE := Text("Hello") + error = column.Set(rowID, VALUE) + if error != nil { + t.Fatalf("Column.Set() failed: error = %v", error) + } + error = column.Get(rowID, &value) + if error != nil { + t.Fatalf("Column.Get() failed: error = %v", error) + } + if !reflect.DeepEqual(value, VALUE) { + t.Fatalf("Column.Get() returned a wrong value: value = %v", value) + } + if !column.Contains(VALUE) { + t.Fatalf("Column.Contains() failed") + } + foundRowID := column.FindOne(VALUE) + if foundRowID != rowID { + t.Fatalf("Column.FindOne() failed: foundRowID = %v", foundRowID) + } + + error = column.Set(rowID, nil) + if error != nil { + t.Fatalf("Column.Set() failed: error = %v", error) + } + error = column.Get(rowID, &value) + if error != nil { + t.Fatalf("Column.Get() failed: error = %v", error) + } + if !value.IsNA() { + t.Fatalf("Column.Get() returned a wrong value: value = %v", value) + } + if !column.Contains(nil) { + t.Fatalf("Column.Contains() failed") + } + foundRowID = column.FindOne(nil) + if foundRowID != rowID { + t.Fatalf("Column.FindOne() failed: foundRowID = %v, rowID = %v", + foundRowID, rowID) + } +} + +func TestReferenceColumn(t *testing.T) { + db, error := CreateDB() + if error != nil { + t.Fatalf("CreateDB() failed: error = %v", error) + } + defer db.Close() + table, error := db.CreateTable("Table") + if error != nil { + t.Fatalf("DB.CreateTable() failed: error = %v", error) + } + column, error := table.CreateColumn("Column", INT, &ColumnOptions{"Table"}) + if error != nil { + t.Fatalf("Table.CreateColumn() failed: error = %v", error) + } + rowID, error := table.InsertRow(nil) + if error != nil { + t.Fatalf("Table.InsertRow() failed: error = %v", error) + } + var value Int + error = column.Get(rowID, &value) + if error != nil { + t.Fatalf("Column.Get() failed: error = %v", error) + } + if !value.IsNA() { + t.Fatalf("Column.Get() returned a wrong value: value = %v", value) + } + + VALUE := Int(123) + error = column.Set(rowID, VALUE) + if error == nil { + t.Fatalf("Column.Set() succeeded") + } + VALUE = Int(0) + error = column.Set(rowID, VALUE) + if error != nil { + t.Fatalf("Column.Set() failed: error = %v", error) + } + error = column.Get(rowID, &value) + if error != nil { + t.Fatalf("Column.Get() failed: error = %v", error) + } + if value != VALUE { + t.Fatalf("Column.Get() returned a wrong value: value = %v", value) + } + if !column.Contains(VALUE) { + t.Fatalf("Column.Contains() failed") + } + foundRowID := column.FindOne(VALUE) + if foundRowID != rowID { + t.Fatalf("Column.FindOne() failed: foundRowID = %v", foundRowID) + } + + error = column.Set(rowID, nil) + if error != nil { + t.Fatalf("Column.Set() failed: error = %v", error) + } + error = column.Get(rowID, &value) + if error != nil { + t.Fatalf("Column.Get() failed: error = %v", error) + } + if !value.IsNA() { + t.Fatalf("Column.Get() returned a wrong value: value = %v", value) + } + if !column.Contains(nil) { + t.Fatalf("Column.Contains() failed") + } + foundRowID = column.FindOne(nil) + if foundRowID != rowID { + t.Fatalf("Column.FindOne() failed: foundRowID = %v", foundRowID) + } +} + +func TestIntIndex(t *testing.T) { + db, error := CreateDB() + if error != nil { + t.Fatalf("CreateDB() failed: error = %v", error) + } + defer db.Close() + table, error := db.CreateTable("Table") + if error != nil { + t.Fatalf("DB.CreateTable() failed: error = %v", error) + } + column, error := table.CreateColumn("Column", INT, nil) + if error != nil { + t.Fatalf("Table.CreateColumn() failed: error = %v", error) + } + index, error := column.CreateIndex("Index", TREE_INDEX) + if error != nil { + t.Fatalf("Column.CreateIndex() failed: error = %v", error) + } + rowID, error := table.InsertRow(nil) + if error != nil { + t.Fatalf("Table.InsertRow() failed: error = %v", error) + } + error = column.Set(rowID, Int(123)) + if error != nil { + t.Fatalf("Column.Set() failed: error = %v", error) + } + error = table.SetKeyColumn("Column") + if error != nil { + t.Fatalf("Table.SetKeyColumn() failed: error = %v", error) + } + rowID, error = table.InsertRow(Int(456)) + if error != nil { + t.Fatalf("Table.InsertRow() failed: error = %v", error) + } + if !index.Contains(Int(456)) { + t.Fatalf("Index.Contains() failed") + } + foundRowID := index.FindOne(Int(456)) + if foundRowID != rowID { + t.Fatalf("Index.FindOne() failed: foundRowID = %v, rowID = %v", + foundRowID, rowID) + } + cursor, error := index.Find(Int(456), nil) + if error != nil { + t.Fatalf("Index.Find() failed: error = %v", error) + } + defer cursor.Close() + records := make([]Record, 10) + count := cursor.Read(records) + if count != 1 { + t.Fatalf("Cursor.Read() could not read a record: count = %v", count) + } + if records[0].RowID != rowID { + t.Fatalf("Cursor.Read() returned a wrong record: RowID = %v, Score = %v", + records[0].RowID, records[0].Score) + } + cursorOptions := CursorOptions{0, 2, REVERSE_ORDER} + cursor, error = index.FindInRange(Int(123), true, Int(456), true, + &cursorOptions) + if error != nil { + t.Fatalf("Index.FindInRange() failed: error = %v", error) + } + defer cursor.Close() + count = cursor.Read(records) + if count != 2 { + t.Fatalf("Cursor.Read() could not read all records: count = %v", count) + } + if records[0].RowID != 1 { + t.Fatalf("Cursor.Read() returned a wrong record: RowID = %v, Score = %v", + records[0].RowID, records[0].Score) + } + if records[1].RowID != 0 { + t.Fatalf("Cursor.Read() returned a wrong record: RowID = %v, Score = %v", + records[1].RowID, records[1].Score) + } +} + +func TestFloatIndex(t *testing.T) { + db, error := CreateDB() + if error != nil { + t.Fatalf("CreateDB() failed: error = %v", error) + } + defer db.Close() + table, error := db.CreateTable("Table") + if error != nil { + t.Fatalf("DB.CreateTable() failed: error = %v", error) + } + column, error := table.CreateColumn("Column", FLOAT, nil) + if error != nil { + t.Fatalf("Table.CreateColumn() failed: error = %v", error) + } + index, error := column.CreateIndex("Index", HASH_INDEX) + if error != nil { + t.Fatalf("Column.CreateIndex() failed: error = %v", error) + } + rowID, error := table.InsertRow(nil) + if error != nil { + t.Fatalf("Table.InsertRow() failed: error = %v", error) + } + error = column.Set(rowID, Float(1.25)) + if error != nil { + t.Fatalf("Column.Set() failed: error = %v", error) + } + rowID, error = table.InsertRow(nil) + if error != nil { + t.Fatalf("Table.InsertRow() failed: error = %v", error) + } + error = column.Set(rowID, Float(4.75)) + if error != nil { + t.Fatalf("Column.Set() failed: error = %v", error) + } + if !index.Contains(Float(4.75)) { + t.Fatalf("Index.Contains() failed") + } + foundRowID := index.FindOne(Float(4.75)) + if foundRowID != rowID { + t.Fatalf("Index.FindOne() failed: foundRowID = %v, rowID = %v", + foundRowID, rowID) + } + cursor, error := index.Find(Float(4.75), nil) + if error != nil { + t.Fatalf("Index.Find() failed: error = %v", error) + } + defer cursor.Close() + records := make([]Record, 10) + count := cursor.Read(records) + if count != 1 { + t.Fatalf("Cursor.Read() could not read a record: count = %v", count) + } + if records[0].RowID != rowID { + t.Fatalf("Cursor.Read() returned a wrong record: RowID = %v, Score = %v", + records[0].RowID, records[0].Score) + } + cursorOptions := CursorOptions{0, 2, REVERSE_ORDER} + cursor, error = index.FindInRange(Float(1.25), true, Float(4.75), true, + &cursorOptions) + if error == nil { + t.Fatalf("Index.FindInRange() Succeeded for hash index") + } +} + +func TestTextIndex(t *testing.T) { + db, error := CreateDB() + if error != nil { + t.Fatalf("CreateDB() failed: error = %v", error) + } + defer db.Close() + table, error := db.CreateTable("Table") + if error != nil { + t.Fatalf("DB.CreateTable() failed: error = %v", error) + } + column, error := table.CreateColumn("Column", TEXT, nil) + if error != nil { + t.Fatalf("Table.CreateColumn() failed: error = %v", error) + } + index, error := column.CreateIndex("Index", TREE_INDEX) + if error != nil { + t.Fatalf("Column.CreateIndex() failed: error = %v", error) + } + VALUES := []Text{Text("He"), Text("Hello"), Text("World")} + for i := 0; i < len(VALUES); i++ { + rowID, error := table.InsertRow(nil) + if error != nil { + t.Fatalf("Table.InsertRow() failed: error = %v", error) + } + if rowID != Int(i) { + t.Fatalf("Table.InsertRow() returned a wrong row ID: rowID = %v", rowID) + } + error = column.Set(rowID, VALUES[i]) + if error != nil { + t.Fatalf("Column.Set() failed: error = %v", error) + } + } + for i := 0; i < len(VALUES); i++ { + if !index.Contains(VALUES[i]) { + t.Fatalf("Index.Contains() failed: i = %v", i) + } + rowID := index.FindOne(VALUES[i]) + if rowID != Int(i) { + t.Fatalf("Index.FindOne() failed: rowID = %v, i = %v", rowID, i) + } + } + cursor, error := index.Find(VALUES[0], nil) + if error != nil { + t.Fatalf("Index.Find() failed: error = %v", error) + } + defer cursor.Close() + records := make([]Record, 10) + count := cursor.Read(records) + if count != 1 { + t.Fatalf("Cursor.Read() failed: count = %v", count) + } + cursor, error = index.FindInRange(nil, false, nil, false, nil) + if error != nil { + t.Fatalf("Index.FindInRange() failed: error = %v", error) + } + defer cursor.Close() + count = cursor.Read(records) + if count != 3 { + t.Fatalf("Cursor.Read() failed: count = %v", count) + } + cursor, error = index.FindStartsWith(Text("He"), true, nil) + if error != nil { + t.Fatalf("Index.FindInRange() failed: error = %v", error) + } + defer cursor.Close() + count = cursor.Read(records) + if count != 2 { + t.Fatalf("Cursor.Read() failed: count = %v", count) + } + cursor, error = index.FindPrefixes(Text("Hello"), nil) + if error != nil { + t.Fatalf("Index.FindPrefixes() failed: error = %v", error) + } + defer cursor.Close() + count = cursor.Read(records) + if count != 2 { + t.Fatalf("Cursor.Read() failed: count = %v", count) + } +} + +func TestRowIDExpression(t *testing.T) { + db, error := CreateDB() + if error != nil { + t.Fatalf("CreateDB() failed: error = %v", error) + } + defer db.Close() + table, error := db.CreateTable("Table") + if error != nil { + t.Fatalf("DB.CreateTable() failed: error = %v", error) + } + builder, error := CreateExpressionBuilder(table) + if error != nil { + t.Fatalf("CreateExpressionBuilder() failed: error = %v", error) + } + defer builder.Close() + error = builder.PushRowID() + if error != nil { + t.Fatalf("ExpressionBuilder.PushRowID() failed: error = %v", error) + } + expression, error := builder.Release() + if error != nil { + t.Fatalf("ExpressionBuilder.Release() failed: error = %v", error) + } + defer expression.Close() + if expression.Table().handle != table.handle { + t.Fatalf("Expression.Table() returned a wrong table") + } + if expression.DataType() != INT { + t.Fatalf("Expression.DataType() returned a wrong data type: type = %v", + expression.DataType()) + } + if !expression.IsRowID() { + t.Fatalf("Expression.IsRowID() failed") + } + if expression.IsScore() { + t.Fatalf("Expression.IsScore() failed") + } +} + +func TestScoreExpression(t *testing.T) { + db, error := CreateDB() + if error != nil { + t.Fatalf("CreateDB() failed: error = %v", error) + } + defer db.Close() + table, error := db.CreateTable("Table") + if error != nil { + t.Fatalf("DB.CreateTable() failed: error = %v", error) + } + builder, error := CreateExpressionBuilder(table) + if error != nil { + t.Fatalf("CreateExpressionBuilder() failed: error = %v", error) + } + defer builder.Close() + error = builder.PushScore() + if error != nil { + t.Fatalf("ExpressionBuilder.PushScore() failed: error = %v", error) + } + expression, error := builder.Release() + if error != nil { + t.Fatalf("ExpressionBuilder.Release() failed: error = %v", error) + } + defer expression.Close() + if expression.Table().handle != table.handle { + t.Fatalf("Expression.Table() returned a wrong table") + } + if expression.DataType() != FLOAT { + t.Fatalf("Expression.DataType() returned a wrong data type: type = %v", + expression.DataType()) + } + if expression.IsRowID() { + t.Fatalf("Expression.IsRowID() failed") + } + if !expression.IsScore() { + t.Fatalf("Expression.IsScore() failed") + } +} + +func TestExpressionFilter(t *testing.T) { + db, error := CreateDB() + if error != nil { + t.Fatalf("CreateDB() failed: error = %v", error) + } + defer db.Close() + table, error := db.CreateTable("Table") + if error != nil { + t.Fatalf("DB.CreateTable() failed: error = %v", error) + } + column, error := table.CreateColumn("Column", TEXT, nil) + if error != nil { + t.Fatalf("Table.CreateColumn() failed: error = %v", error) + } + VALUES := []Text{Text("He"), Text("Hello"), Text("World")} + for i := 0; i < len(VALUES); i++ { + rowID, error := table.InsertRow(nil) + if error != nil { + t.Fatalf("Table.InsertRow() failed: error = %v", error) + } + error = column.Set(rowID, VALUES[i]) + if error != nil { + t.Fatalf("Column.Set() failed: error = %v", error) + } + } + builder, error := CreateExpressionBuilder(table) + if error != nil { + t.Fatalf("CreateExpressionBuilder() failed: error = %v", error) + } + defer builder.Close() + error = builder.PushColumn("Column") + if error != nil { + t.Fatalf("ExpressionBuilder.PushColumn() failed: error = %v", error) + } + error = builder.PushConstant(Text("Hello")) + if error != nil { + t.Fatalf("ExpressionBuilder.PushConstant() failed: error = %v", error) + } + error = builder.PushOperator(LESS_EQUAL) + if error != nil { + t.Fatalf("ExpressionBuilder.PushOperator() failed: error = %v", error) + } + expression, error := builder.Release() + if error != nil { + t.Fatalf("ExpressionBuilder.Release() failed: error = %v", error) + } + defer expression.Close() + cursor, error := table.CreateCursor(nil) + if error != nil { + t.Fatalf("Table.CreateCursor() failed: error = %v", error) + } + defer cursor.Close() + records := make([]Record, 10) + count := cursor.Read(records) + records = records[:count] + error = expression.Filter(&records) + if error != nil { + t.Fatalf("Expression.Filter() failed: error = %v", error) + } + if len(records) != 2 { + t.Fatalf("Expression.Filter() failed: len(records) = %v", len(records)) + } +} + +func TestExpressionAdjust(t *testing.T) { + db, error := CreateDB() + if error != nil { + t.Fatalf("CreateDB() failed: error = %v", error) + } + defer db.Close() + table, error := db.CreateTable("Table") + if error != nil { + t.Fatalf("DB.CreateTable() failed: error = %v", error) + } + column, error := table.CreateColumn("Column", FLOAT, nil) + if error != nil { + t.Fatalf("Table.CreateColumn() failed: error = %v", error) + } + VALUES := []Float{1.5, 2.0, 2.5} + for i := 0; i < len(VALUES); i++ { + rowID, error := table.InsertRow(nil) + if error != nil { + t.Fatalf("Table.InsertRow() failed: error = %v", error) + } + error = column.Set(rowID, VALUES[i]) + if error != nil { + t.Fatalf("Column.Set() failed: error = %v", error) + } + } + builder, error := CreateExpressionBuilder(table) + if error != nil { + t.Fatalf("CreateExpressionBuilder() failed: error = %v", error) + } + defer builder.Close() + error = builder.PushColumn("Column") + if error != nil { + t.Fatalf("ExpressionBuilder.PushColumn() failed: error = %v", error) + } + error = builder.PushColumn("Column") + if error != nil { + t.Fatalf("ExpressionBuilder.PushColumn() failed: error = %v", error) + } + error = builder.PushOperator(MULTIPLICATION) + if error != nil { + t.Fatalf("ExpressionBuilder.PushOperator() failed: error = %v", error) + } + expression, error := builder.Release() + if error != nil { + t.Fatalf("ExpressionBuilder.Release() failed: error = %v", error) + } + defer expression.Close() + cursor, error := table.CreateCursor(nil) + if error != nil { + t.Fatalf("Table.CreateCursor() failed: error = %v", error) + } + defer cursor.Close() + records := make([]Record, 10) + count := cursor.Read(records) + if count != 3 { + t.Fatalf("Cursor.Read() failed: count = %v", count) + } + records = records[:count] + error = expression.Adjust(records) + if error != nil { + t.Fatalf("Expression.Adjust() failed: error = %v", error) + } + for i := 0; i < count; i++ { + if records[i].Score != (VALUES[i] * VALUES[i]) { + t.Fatalf("Expression.Adjust() failed: "+ + "i = %v, RowID = %v, Score = %v, VALUE = %v", + i, records[i].RowID, records[i].Score, VALUES[i]) + } + } +} + +func TestExpressionEvaluate(t *testing.T) { + db, error := CreateDB() + if error != nil { + t.Fatalf("CreateDB() failed: error = %v", error) + } + defer db.Close() + table, error := db.CreateTable("Table") + if error != nil { + t.Fatalf("DB.CreateTable() failed: error = %v", error) + } + column, error := table.CreateColumn("Column", INT, nil) + if error != nil { + t.Fatalf("Table.CreateColumn() failed: error = %v", error) + } + VALUES := []Int{123, 456, 789} + for i := 0; i < len(VALUES); i++ { + rowID, error := table.InsertRow(nil) + if error != nil { + t.Fatalf("Table.InsertRow() failed: error = %v", error) + } + error = column.Set(rowID, VALUES[i]) + if error != nil { + t.Fatalf("Column.Set() failed: error = %v", error) + } + } + builder, error := CreateExpressionBuilder(table) + if error != nil { + t.Fatalf("CreateExpressionBuilder() failed: error = %v", error) + } + defer builder.Close() + error = builder.PushColumn("Column") + if error != nil { + t.Fatalf("ExpressionBuilder.PushColumn() failed: error = %v", error) + } + error = builder.PushConstant(Int(100)) + if error != nil { + t.Fatalf("ExpressionBuilder.PushConstant() failed: error = %v", error) + } + error = builder.PushOperator(PLUS) + if error != nil { + t.Fatalf("ExpressionBuilder.PushOperator() failed: error = %v", error) + } + expression, error := builder.Release() + if error != nil { + t.Fatalf("ExpressionBuilder.Release() failed: error = %v", error) + } + defer expression.Close() + cursor, error := table.CreateCursor(nil) + if error != nil { + t.Fatalf("Table.CreateCursor() failed: error = %v", error) + } + defer cursor.Close() + records := make([]Record, 10) + count := cursor.Read(records) + if count != 3 { + t.Fatalf("Cursor.Read() failed: count = %v", count) + } + records = records[:count] + values := make([]Int, count) + error = expression.Evaluate(records, values) + if error != nil { + t.Fatalf("Expression.Evaluate() failed: error = %v", error) + } + for i := 0; i < count; i++ { + if values[i] != (VALUES[i] + 100) { + t.Fatalf("Expression.Evaluate() failed: "+ + "i = %v, VALUE = %v, value = %v", i, VALUES[i], values[i]) + } + } +} + +func TestExpressionParser(t *testing.T) { + db, error := CreateDB() + if error != nil { + t.Fatalf("CreateDB() failed: error = %v", error) + } + defer db.Close() + table, error := db.CreateTable("Table") + if error != nil { + t.Fatalf("DB.CreateTable() failed: error = %v", error) + } + column, error := table.CreateColumn("Column", INT, nil) + if error != nil { + t.Fatalf("Table.CreateColumn() failed: error = %v", error) + } + VALUES := []Int{123, 456, 789} + for i := 0; i < len(VALUES); i++ { + rowID, error := table.InsertRow(nil) + if error != nil { + t.Fatalf("Table.InsertRow() failed: error = %v", error) + } + error = column.Set(rowID, VALUES[i]) + if error != nil { + t.Fatalf("Column.Set() failed: error = %v", error) + } + } + expression, error := ParseExpression(table, "Column + 100") + defer expression.Close() + cursor, error := table.CreateCursor(nil) + if error != nil { + t.Fatalf("Table.CreateCursor() failed: error = %v", error) + } + defer cursor.Close() + records := make([]Record, 10) + count := cursor.Read(records) + if count != 3 { + t.Fatalf("Cursor.Read() failed: count = %v", count) + } + records = records[:count] + values := make([]Int, count) + error = expression.Evaluate(records, values) + if error != nil { + t.Fatalf("Expression.Evaluate() failed: error = %v", error) + } + for i := 0; i < count; i++ { + if values[i] != (VALUES[i] + 100) { + t.Fatalf("Expression.Evaluate() failed: "+ + "i = %v, VALUE = %v, value = %v", i, VALUES[i], values[i]) + } + } +} + +func TestPipelineFilterAndAdjuster(t *testing.T) { + db, error := CreateDB() + if error != nil { + t.Fatalf("CreateDB() failed: error = %v", error) + } + defer db.Close() + table, error := db.CreateTable("Table") + if error != nil { + t.Fatalf("DB.CreateTable() failed: error = %v", error) + } + column, error := table.CreateColumn("Column", FLOAT, nil) + if error != nil { + t.Fatalf("Table.CreateColumn() failed: error = %v", error) + } + VALUES := []Float{1.5, 2.0, 2.5} + for i := 0; i < len(VALUES); i++ { + rowID, error := table.InsertRow(nil) + if error != nil { + t.Fatalf("Table.InsertRow() failed: error = %v", error) + } + error = column.Set(rowID, VALUES[i]) + if error != nil { + t.Fatalf("Column.Set() failed: error = %v", error) + } + } + + pipelineBuilder, error := CreatePipelineBuilder(table) + if error != nil { + t.Fatalf("CreatePipelineBuilder() failed: error = %v", error) + } + defer pipelineBuilder.Close() + cursor, error := table.CreateCursor(nil) + if error != nil { + t.Fatalf("Table.CreateCursor() failed: error = %v", error) + } + error = pipelineBuilder.PushCursor(cursor) + if error != nil { + t.Fatalf("PipelineBuilder.PushCursor() failed: error = %v", error) + } + + expressionBuilder, error := CreateExpressionBuilder(table) + if error != nil { + t.Fatalf("CreateExpressionBuilder() failed: error = %v", error) + } + defer expressionBuilder.Close() + error = expressionBuilder.PushColumn("Column") + if error != nil { + t.Fatalf("ExpressionBuilder.PushColumn() failed: error = %v", error) + } + error = expressionBuilder.PushConstant(Float(2.0)) + if error != nil { + t.Fatalf("ExpressionBuilder.PushConstant() failed: error = %v", error) + } + error = expressionBuilder.PushOperator(GREATER_EQUAL) + if error != nil { + t.Fatalf("ExpressionBuilder.PushOperator() failed: error = %v", error) + } + expression, error := expressionBuilder.Release() + if error != nil { + t.Fatalf("ExpressionBuilder.Release() failed: error = %v", error) + } + error = pipelineBuilder.PushFilter(expression, 0, 100) + if error != nil { + t.Fatalf("PipelineBuilder.PushFilter() failed: error = %v", error) + } + + error = expressionBuilder.PushColumn("Column") + if error != nil { + t.Fatalf("ExpressionBuilder.PushColumn() failed: error = %v", error) + } + error = expressionBuilder.PushColumn("Column") + if error != nil { + t.Fatalf("ExpressionBuilder.PushColumn() failed: error = %v", error) + } + error = expressionBuilder.PushOperator(MULTIPLICATION) + if error != nil { + t.Fatalf("ExpressionBuilder.PushOperator() failed: error = %v", error) + } + expression, error = expressionBuilder.Release() + if error != nil { + t.Fatalf("ExpressionBuilder.Release() failed: error = %v", error) + } + error = pipelineBuilder.PushAdjuster(expression) + if error != nil { + t.Fatalf("PipelineBuilder.PushAdjuster() failed: error = %v", error) + } + + pipeline, error := pipelineBuilder.Release(nil) + if error != nil { + t.Fatalf("PipelineBuilder.Release() failed: error = %v", error) + } + defer pipeline.Close() + records, error := pipeline.Flush() + if error != nil { + t.Fatalf("Pipeline.Flush() failed: error = %v", error) + } + if len(records) != 2 { + t.Fatalf("Pipeline.Flush() failed: len(records) = %v", len(records)) + } + if (records[0].RowID != 1) || (records[0].Score != (VALUES[1] * VALUES[1])) { + t.Fatalf("Pipeline.Flush() failed: i = %v, RowID = %v, Score = %v", + 0, records[0].RowID, records[0].Score) + } + if (records[1].RowID != 2) || (records[1].Score != (VALUES[2] * VALUES[2])) { + t.Fatalf("Pipeline.Flush() failed: i = %v, RowID = %v, Score = %v", + 1, records[1].RowID, records[1].Score) + } +} + +func TestPipelineSorterAndMerger(t *testing.T) { + db, error := CreateDB() + if error != nil { + t.Fatalf("CreateDB() failed: error = %v", error) + } + defer db.Close() + table, error := db.CreateTable("Table") + if error != nil { + t.Fatalf("DB.CreateTable() failed: error = %v", error) + } + column, error := table.CreateColumn("Column", FLOAT, nil) + if error != nil { + t.Fatalf("Table.CreateColumn() failed: error = %v", error) + } + index, error := column.CreateIndex("Index", TREE_INDEX) + if error != nil { + t.Fatalf("Table.CreateIndex() failed: error = %v", error) + } + VALUES := []Float{4.5, 1.5, 6.0, 0.0, 3.0} + for i := 0; i < len(VALUES); i++ { + rowID, error := table.InsertRow(nil) + if error != nil { + t.Fatalf("Table.InsertRow() failed: error = %v", error) + } + error = column.Set(rowID, VALUES[i]) + if error != nil { + t.Fatalf("Column.Set() failed: error = %v", error) + } + } + + pipelineBuilder, error := CreatePipelineBuilder(table) + if error != nil { + t.Fatalf("CreatePipelineBuilder() failed: error = %v", error) + } + defer pipelineBuilder.Close() + expressionBuilder, error := CreateExpressionBuilder(table) + if error != nil { + t.Fatalf("CreateExpressionBuilder() failed: error = %v", error) + } + defer expressionBuilder.Close() + + cursor, error := index.FindInRange(nil, false, Float(4.5), true, nil) + if error != nil { + t.Fatalf("index.FindInRange() failed: error = %v", error) + } + error = pipelineBuilder.PushCursor(cursor) + if error != nil { + t.Fatalf("PipelineBuilder.PushCursor() failed: error = %v", error) + } + error = expressionBuilder.PushColumn("Column") + if error != nil { + t.Fatalf("ExpressionBuilder.PushColumn() failed: error = %v", error) + } + expression, error := expressionBuilder.Release() + if error != nil { + t.Fatalf("ExpressionBuilder.Release() failed: error = %v", error) + } + error = pipelineBuilder.PushAdjuster(expression) + if error != nil { + t.Fatalf("PipelineBuilder.PushAdjuster() failed: error = %v", error) + } + + cursor, error = index.FindInRange(Float(1.5), true, nil, false, nil) + if error != nil { + t.Fatalf("index.FindInRange() failed: error = %v", error) + } + error = pipelineBuilder.PushCursor(cursor) + if error != nil { + t.Fatalf("PipelineBuilder.PushCursor() failed: error = %v", error) + } + error = expressionBuilder.PushColumn("Column") + if error != nil { + t.Fatalf("ExpressionBuilder.PushColumn() failed: error = %v", error) + } + expression, error = expressionBuilder.Release() + if error != nil { + t.Fatalf("ExpressionBuilder.Release() failed: error = %v", error) + } + error = pipelineBuilder.PushAdjuster(expression) + if error != nil { + t.Fatalf("PipelineBuilder.PushAdjuster() failed: error = %v", error) + } + + var mergerOptions MergerOptions + mergerOptions.LogicalOperatorType = MERGER_AND + mergerOptions.ScoreOperatorType = MERGER_PLUS + mergerOptions.Limit = 10 + error = pipelineBuilder.PushMerger(&mergerOptions) + if error != nil { + t.Fatalf("PipelineBuilder.PushMerger() failed: error = %v", error) + } + + error = expressionBuilder.PushRowID() + if error != nil { + t.Fatalf("ExpressionBuilder.PushRowID() failed: error = %v", error) + } + expression, error = expressionBuilder.Release() + if error != nil { + t.Fatalf("ExpressionBuilder.Release() failed: error = %v", error) + } + orders := make([]SorterOrder, 1) + orders[0].Expression = expression + orders[0].OrderType = REGULAR_ORDER + sorter, error := CreateSorter(orders, nil) + if error != nil { + t.Fatalf("CreateSorter() failed: error = %v", error) + } + error = pipelineBuilder.PushSorter(sorter) + if error != nil { + t.Fatalf("PipelineBuilder.PushSorter() failed: error = %v", error) + } + + pipeline, error := pipelineBuilder.Release(nil) + if error != nil { + t.Fatalf("PipelineBuilder.Release() failed: error = %v", error) + } + defer pipeline.Close() + records, error := pipeline.Flush() + if error != nil { + t.Fatalf("Pipeline.Flush() failed: error = %v", error) + } + if len(records) != 3 { + t.Fatalf("Pipeline.Flush() failed: len(records) = %v", len(records)) + } + if (records[0].RowID != 0) || (records[0].Score != (VALUES[0] + VALUES[0])) { + t.Fatalf("Pipeline.Flush() failed: i = %v, RowID = %v, Score = %v", + 0, records[0].RowID, records[0].Score) + } + if (records[1].RowID != 1) || (records[1].Score != (VALUES[1] + VALUES[1])) { + t.Fatalf("Pipeline.Flush() failed: i = %v, RowID = %v, Score = %v", + 1, records[1].RowID, records[1].Score) + } + if (records[2].RowID != 4) || (records[2].Score != (VALUES[4] + VALUES[4])) { + t.Fatalf("Pipeline.Flush() failed: i = %v, RowID = %v, Score = %v", + 2, records[2].RowID, records[2].Score) + } +} Added: go/gnx/groonga/groonga.go (+862 -0) 100644 =================================================================== --- /dev/null +++ go/gnx/groonga/groonga.go 2015-03-20 21:42:11 +0900 (ffe3abb) @@ -0,0 +1,862 @@ +package groonga + +/* +#cgo pkg-config: groonga +#include <groonga.h> +#include <stdlib.h> +*/ +import "C" + +import "encoding/json" +import "fmt" +import "io/ioutil" +import "os" +import "strconv" +import "strings" +import "time" +import "unicode" +import "unsafe" + +var initCount = 0 + +func DisableInitCount() { + initCount = -1 +} + +func Init() error { + switch initCount { + 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) + } + } + initCount++ + return nil +} + +func Fin() error { + switch initCount { + 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) + } + } + initCount-- + return nil +} + +type DB struct { + ctx *C.grn_ctx +} + +func CreateDB(path string) (*DB, error) { + if err := Init(); err != nil { + return nil, err + } + ctx := C.grn_ctx_open(0) + if ctx == nil { + Fin() + return nil, fmt.Errorf("grn_ctx_open() failed") + } + cPath := C.CString(path) + defer C.free(unsafe.Pointer(cPath)) + if db := C.grn_db_create(ctx, cPath, nil); db == nil { + C.grn_ctx_close(ctx) + Fin() + message := C.GoString(&ctx.errbuf[0]) + return nil, fmt.Errorf("grn_db_create() failed: err = %s", message) + } + return &DB{ctx}, nil +} + +// Create a DB in a temporary directory. +// +// Example: +// db, dir, err := CreateTempDB("", "gnx") +// if err != nil { +// log.Fatalln(err) +// } +// defer os.RemoveAll(dir) +// ... +func CreateTempDB(dir, prefix string) (*DB, string, error) { + tempDir, err := ioutil.TempDir(dir, prefix) + if err != nil { + return nil, "", err + } + db, err := CreateDB(tempDir + "/db") + if err != nil { + os.RemoveAll(tempDir) + return nil, "", err + } + return db, tempDir, nil +} + +func OpenDB(path string) (*DB, error) { + if err := Init(); err != nil { + return nil, err + } + ctx := C.grn_ctx_open(0) + if ctx == nil { + Fin() + return nil, fmt.Errorf("grn_ctx_open() failed") + } + cPath := C.CString(path) + defer C.free(unsafe.Pointer(cPath)) + if db := C.grn_db_open(ctx, cPath); db == nil { + C.grn_ctx_close(ctx) + Fin() + message := C.GoString(&ctx.errbuf[0]) + return nil, fmt.Errorf("grn_db_create() failed: err = %s", message) + } + return &DB{ctx}, nil +} + +func (db *DB) Close() error { + if db.ctx == nil { + return nil + } + if rc := C.grn_ctx_close(db.ctx); rc != C.GRN_SUCCESS { + return fmt.Errorf("grn_ctx_close() failed: rc = %d", rc) + } + db.ctx = nil + Fin() // may fail + return nil +} + +func (db *DB) Send(command string) error { + cCommand := C.CString(command) + defer C.free(unsafe.Pointer(cCommand)) + rc := C.grn_ctx_send(db.ctx, cCommand, C.uint(len(command)), 0) + if (rc != C.GRN_SUCCESS) || (db.ctx.rc != C.GRN_SUCCESS) { + message := C.GoString(&db.ctx.errbuf[0]) + return fmt.Errorf( + "grn_ctx_send() failed: rc = %d, ctx.rc = %d, err = %s", + rc, db.ctx.rc, message) + } + return nil +} + +func (db *DB) 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) + if (rc != C.GRN_SUCCESS) || (db.ctx.rc != C.GRN_SUCCESS) { + message := C.GoString(&db.ctx.errbuf[0]) + return nil, fmt.Errorf( + "grn_ctx_recv() failed: rc = %d, ctx.rc = %d, err = %s", + rc, db.ctx.rc, message) + } + result := C.GoBytes(unsafe.Pointer(resultBuffer), C.int(resultLength)) + return result, nil +} + +func (db *DB) Query(command string) ([]byte, error) { + if err := db.Send(command); err != nil { + return nil, err + } + return db.Recv() +} + +func (db *DB) SendEx(name string, options map[string]string) error { + if (len(name) == 0) || (strings.IndexFunc(name, unicode.IsSpace) != -1) { + return fmt.Errorf("invalid command name") + } + command := name + for key, value := range options { + if (len(key) == 0) || (strings.IndexFunc(key, unicode.IsSpace) != -1) { + return fmt.Errorf("invalid option key") + } + value = strings.Replace(value, "\\", "\\\\", -1) + value = strings.Replace(value, "'", "\\'", -1) + command += fmt.Sprintf(" --%s '%s'", key, value) + } + return db.Send(command) +} + +func (db *DB) QueryEx(name string, options map[string]string) ( + []byte, error) { + if err := db.SendEx(name, options); err != nil { + return nil, err + } + return db.Recv() +} + +func (db *DB) boolQueryEx(name string, options map[string]string) ( + bool, error) { + bytes, err := db.QueryEx(name, options) + if err != nil { + return false, err + } + return string(bytes) == "true", nil +} + +func (db *DB) intQueryEx(name string, options map[string]string) ( + int, error) { + bytes, err := db.QueryEx(name, options) + if err != nil { + return 0, err + } + value, err := strconv.Atoi(string(bytes)) + if err != nil { + return 0, err + } + return value, nil +} + +func (db *DB) CacheLimit(max int) (int, error) { + if max < 0 { + return db.intQueryEx("cache_limit", nil) + } + return db.intQueryEx("cache_limit", map[string]string{ + "max": strconv.Itoa(max)}) +} + +func (db *DB) Check(objName string) ([]byte, error) { + return db.QueryEx("check", map[string]string{"obj": objName}) +} + +func (db *DB) CreateColumn(options map[string]string) (bool, error) { + return db.boolQueryEx("column_create", options) +} + +func parseStringOrNil(value interface{}) string { + if result, ok := value.(string); ok { + return result + } + return "" +} + +type Column struct { + ID int + Name string + Path string + Type string + Flags string + Domain string + Range string + Source string +} + +func (db *DB) ColumnList(tableName string) ([]Column, error) { + bytes, err := db.QueryEx("column_list", map[string]string{ + "table": tableName}) + if err != nil { + return nil, err + } + var result [][]interface{} + if err = json.Unmarshal(bytes, &result); err != nil { + return nil, err + } + headers := make([]string, len(result[0])) + for i := 0; i < len(headers); i++ { + headers[i] = result[0][i].([]interface{})[0].(string) + } + columns := make([]Column, len(result)-1) + for i := 0; i < len(columns); i++ { + values := result[i+1] + for j := 0; j < len(values); j++ { + switch headers[j] { + case "id": + columns[i].ID = int(values[j].(float64)) + case "name": + columns[i].Name = parseStringOrNil(values[j]) + case "path": + columns[i].Path = parseStringOrNil(values[j]) + case "type": + columns[i].Type = parseStringOrNil(values[j]) + case "flags": + columns[i].Flags = parseStringOrNil(values[j]) + case "domain": + columns[i].Domain = parseStringOrNil(values[j]) + case "range": + columns[i].Range = parseStringOrNil(values[j]) + case "source": + columns[i].Source = parseStringOrNil(values[j]) + } + } + } + return columns, nil +} + +func (db *DB) RemoveColumn(tableName, columnName string) (bool, error) { + return db.boolQueryEx("column_remove", + map[string]string{"table": tableName, "name": columnName}) +} + +func (db *DB) RenameColumn(tableName, columnName, newColumnName string) ( + bool, error) { + return db.boolQueryEx("column_rename", map[string]string{ + "table": tableName, "name": columnName, "new_name": newColumnName}) +} + +func (db *DB) DefineSelector(options map[string]string) (bool, error) { + return db.boolQueryEx("define_selector", options) +} + +func (db *DB) Defrag(objName string, threshold int) (int, error) { + return db.intQueryEx("defrag", map[string]string{ + "objname": objName, "threshold": strconv.Itoa(threshold)}) +} + +func (db *DB) Delete(options map[string]string) (bool, error) { + return db.boolQueryEx("delete", options) +} + +func (db *DB) Dump(tableNames []string) ([]byte, error) { + if len(tableNames) == 0 { + return db.QueryEx("dump", nil) + } + joinedTableNames := strings.Join(tableNames, ",") + return db.QueryEx("dump", map[string]string{"tables": joinedTableNames}) +} + +func (db *DB) Load(options map[string]string) (int, error) { + return db.intQueryEx("load", options) +} + +func (db *DB) ClearLock(options map[string]string) (bool, error) { + return db.boolQueryEx("lock_clear", options) +} + +func (db *DB) SetLogLevel(level string) (bool, error) { + return db.boolQueryEx("log_level", map[string]string{"level": level}) +} + +func (db *DB) PutLog(level, message string) (bool, error) { + return db.boolQueryEx("log_put", map[string]string{ + "level": level, "message": message}) +} + +func (db *DB) ReopenLog() (bool, error) { + return db.boolQueryEx("log_reopen", nil) +} + +func (db *DB) LogicalCount(options map[string]string) (int, error) { + return db.intQueryEx("logical_count", options) +} + +func (db *DB) Normalize(normalizer, target string, flags []string) ( + string, error) { + options := map[string]string{"normalizer": normalizer, "string": target} + if len(flags) != 0 { + options["flags"] = strings.Join(flags, "|") + } + bytes, err := db.QueryEx("normalize", options) + if err != nil { + return "", err + } + var result map[string]interface{} + if err = json.Unmarshal(bytes, &result); err != nil { + return "", err + } + return result["normalized"].(string), nil +} + +func (db *DB) NormalizerList() ([]string, error) { + bytes, err := db.QueryEx("normalizer_list", nil) + if err != nil { + return nil, err + } + var result []map[string]interface{} + if err = json.Unmarshal(bytes, &result); err != nil { + return nil, err + } + normalizers := make([]string, len(result)) + for i, normalizer := range result { + normalizers[i] = normalizer["name"].(string) + } + return normalizers, nil +} + +func (db *DB) Quit() error { + _, err := db.QueryEx("log_reopen", nil) + return err +} + +// TODO: range_filter + +func (db *DB) Register(path string) (bool, error) { + return db.boolQueryEx("register", map[string]string{"path": path}) +} + +// TODO +func (db *DB) CalcelRequest(id string) ([]byte, error) { + return db.QueryEx("request_cancel", map[string]string{"id": id}) +} + +// TODO +func (db *DB) EvalRuby(script string) ([]byte, error) { + return db.QueryEx("ruby_eval", map[string]string{"script": script}) +} + +// TODO +func (db *DB) LoadRuby(path string) ([]byte, error) { + return db.QueryEx("ruby_load", map[string]string{"path": path}) +} + +type OutputColumn struct { + Name string + Type string + // []bool, [](u)int8/16/32/64, []float64, []time.Time, []string, or ???. + // TODO: Support GeoPoint. + Values interface{} +} + +func (output *OutputColumn) Count() int { + switch values := output.Values.(type) { + case []bool: + return len(values) + case []int8: + return len(values) + case []int32: + return len(values) + case []int64: + return len(values) + case []uint8: + return len(values) + case []uint16: + return len(values) + case []uint32: + return len(values) + case []uint64: + return len(values) + case []float64: + return len(values) + case []time.Time: + return len(values) + case []string: + return len(values) + default: + return 0 + } +} + +func (db *DB) Select(options map[string]string) ([]OutputColumn, error) { + bytes, err := db.QueryEx("select", options) + if err != nil { + return nil, err + } + var result [][][]interface{} + if err = json.Unmarshal(bytes, &result); err != nil { + return nil, err + } + headers := result[0][1] + outputColumns := make([]OutputColumn, len(headers)) + for i := 0; i < len(headers); i++ { + header := headers[i].([]interface{}) + outputColumns[i].Name = header[0].(string) + outputColumns[i].Type = header[1].(string) + } + records := result[0][2:] + for i := 0; i < len(outputColumns); i++ { + switch outputColumns[i].Type { + case "Bool": + values := make([]bool, len(records)) + for j := 0; j < len(records); j++ { + values[j] = records[j][i].(bool) + } + outputColumns[i].Values = values + case "Int8": + values := make([]int8, len(records)) + for j := 0; j < len(records); j++ { + values[j] = int8(records[j][i].(float64)) + } + outputColumns[i].Values = values + case "Int16": + values := make([]int16, len(records)) + for j := 0; j < len(records); j++ { + values[j] = int16(records[j][i].(float64)) + } + outputColumns[i].Values = values + case "Int32": + values := make([]int32, len(records)) + for j := 0; j < len(records); j++ { + values[j] = int32(records[j][i].(float64)) + } + outputColumns[i].Values = values + case "Int64": + values := make([]int64, len(records)) + for j := 0; j < len(records); j++ { + values[j] = int64(records[j][i].(float64)) + } + outputColumns[i].Values = values + case "UInt8": + values := make([]uint8, len(records)) + for j := 0; j < len(records); j++ { + values[j] = uint8(records[j][i].(float64)) + } + outputColumns[i].Values = values + case "UInt16": + values := make([]uint16, len(records)) + for j := 0; j < len(records); j++ { + values[j] = uint16(records[j][i].(float64)) + } + outputColumns[i].Values = values + case "UInt32": + values := make([]uint32, len(records)) + for j := 0; j < len(records); j++ { + values[j] = uint32(records[j][i].(float64)) + } + outputColumns[i].Values = values + case "UInt64": + values := make([]uint64, len(records)) + for j := 0; j < len(records); j++ { + values[j] = uint64(records[j][i].(float64)) + } + outputColumns[i].Values = values + case "Float": + values := make([]float64, len(records)) + for j := 0; j < len(records); j++ { + values[j] = records[j][i].(float64) + } + outputColumns[i].Values = values + case "Time": + values := make([]time.Time, len(records)) + for j := 0; j < len(records); j++ { + microSecondsSinceEpoch := int64(records[j][i].(float64) * 1000000.0) + values[j] = time.Unix(microSecondsSinceEpoch/1000000, + microSecondsSinceEpoch%1000000) + } + outputColumns[i].Values = values + case "ShortText", "Text", "LongText": + values := make([]string, len(records)) + for j := 0; j < len(records); j++ { + values[j] = records[j][i].(string) + } + outputColumns[i].Values = values + case "TokyoGeoPoint": // TODO + return nil, fmt.Errorf("not supported type") + case "WGS84GeoPoint": // TODO + return nil, fmt.Errorf("not supported type") + } + } + return outputColumns, nil +} + +func (db *DB) Shutdown() (bool, error) { + return db.boolQueryEx("shutdown", nil) +} + +func (db *DB) Status() (map[string]interface{}, error) { + bytes, err := db.QueryEx("status", nil) + if err != nil { + return nil, err + } + var result map[string]interface{} + if err = json.Unmarshal(bytes, &result); err != nil { + return nil, err + } + return result, nil +} + +type Candidate struct { + Query string + Score float64 +} + +func (db *DB) Suggest(options map[string]string) ( + map[string][]Candidate, error) { + bytes, err := db.QueryEx("suggest", options) + if err != nil { + return nil, err + } + var result map[string][][]interface{} + if err = json.Unmarshal(bytes, &result); err != nil { + return nil, err + } + suggestResult := make(map[string][]Candidate, len(result)) + for key, value := range result { + var candidates []Candidate + if len(value) > 2 { + value = value[2:] + candidates = make([]Candidate, len(value)) + for i := 0; i < len(value); i++ { + candidates[i].Query = value[i][0].(string) + candidates[i].Score = value[i][1].(float64) + } + } + suggestResult[key] = candidates + } + return suggestResult, nil +} + +func (db *DB) CreateTable(options map[string]string) (bool, error) { + bytes, err := db.QueryEx("table_create", options) + if err != nil { + return false, err + } + return string(bytes) == "true", nil +} + +type Table struct { + ID int + Name string + Path string + Flags string + Domain string + Range string + DefaultTokenizer string + Normalizer string +} + +func (db *DB) TableList() ([]Table, error) { + bytes, err := db.QueryEx("table_list", nil) + if err != nil { + return nil, err + } + var result [][]interface{} + if err = json.Unmarshal(bytes, &result); err != nil { + return nil, err + } + headers := make([]string, len(result[0])) + for i := 0; i < len(headers); i++ { + headers[i] = result[0][i].([]interface{})[0].(string) + } + tables := make([]Table, len(result)-1) + for i := 0; i < len(tables); i++ { + values := result[i+1] + for j := 0; j < len(values); j++ { + switch headers[j] { + case "id": + tables[i].ID = int(values[j].(float64)) + case "name": + tables[i].Name = parseStringOrNil(values[j]) + case "path": + tables[i].Path = parseStringOrNil(values[j]) + case "flags": + tables[i].Flags = parseStringOrNil(values[j]) + case "domain": + tables[i].Domain = parseStringOrNil(values[j]) + case "range": + tables[i].Range = parseStringOrNil(values[j]) + case "default_tokenizer": + tables[i].DefaultTokenizer = parseStringOrNil(values[j]) + case "normalizer": + tables[i].Normalizer = parseStringOrNil(values[j]) + } + } + } + return tables, nil +} + +func (db *DB) RemoveTable(tableName string) (bool, error) { + return db.boolQueryEx("table_remove", map[string]string{"name": tableName}) +} + +type Token struct { + Value string + Position int +} + +func (db *DB) TableTokenize(options map[string]string) ([]Token, error) { + bytes, err := db.QueryEx("table_tokenize", options) + if err != nil { + return nil, err + } + var result []Token + if err = json.Unmarshal(bytes, &result); err != nil { + return nil, err + } + return result, nil +} + +func (db *DB) Tokenize(options map[string]string) ([]Token, error) { + bytes, err := db.QueryEx("tokenize", options) + if err != nil { + return nil, err + } + var result []Token + if err = json.Unmarshal(bytes, &result); err != nil { + return nil, err + } + return result, nil +} + +func (db *DB) TokenizerList() ([]string, error) { + bytes, err := db.QueryEx("tokenizer_list", nil) + if err != nil { + return nil, err + } + var result []map[string]string + if err = json.Unmarshal(bytes, &result); err != nil { + return nil, err + } + tokenizers := make([]string, len(result)) + for i := 0; i < len(result); i++ { + tokenizers[i] = result[i]["name"] + } + return tokenizers, nil +} + +func (db *DB) Truncate(targetName string) (bool, error) { + return db.boolQueryEx("truncate", map[string]string{ + "target_name": targetName}) +} + +func (db *DB) tokenizeCommand(s string) (string, string, bool, error) { + s = strings.TrimLeftFunc(s, unicode.IsSpace) + if len(s) == 0 { + return "", "", false, nil + } + switch s[0] { + case '#': + return "", "", false, nil + case '\'', '"': + quote := s[0] + pos := 1 + hasBackslash := false + for pos < len(s) { + if s[pos] == quote { + break + } else if s[pos] == '\\' { + hasBackslash = true + pos++ + } + pos++ + } + if pos >= len(s) { + return "", "", false, fmt.Errorf("quote missing") + } + token := s[1:pos] + if hasBackslash { + bytes := make([]byte, len(token)) + count := 0 + for i := 0; i < len(token); i++ { + if token[i] == '\\' { + i++ + } + bytes[count] = token[i] + count++ + } + token = string(bytes[:count]) + } + return token, s[pos+1:], true, nil + default: + pos := strings.IndexFunc(s, unicode.IsSpace) + if pos == -1 { + pos = len(s) + } + return s[0:pos], s[pos:], true, nil + } +} + +func (db *DB) tokenizeCommandAll(command string) ([]string, error) { + var tokens []string + s := command + for { + token, rest, ok, err := db.tokenizeCommand(s) + if err != nil { + return nil, err + } + if !ok { + break + } + tokens = append(tokens, token) + s = rest + } + return tokens, nil +} + +var commandOptionKeys = map[string][]string{ + "cache_limit": {"max"}, + "check": {"obj"}, + "column_create": {"table", "name", "flags", "type", "source"}, + "column_list": {"table"}, + "column_remove": {"table", "name"}, + "column_rename": {"table", "name", "new_name"}, + "define_selector": { + "name", "table", "match_columns", "query", "filter", "scorer", "sortby", + "output_columns", "offset", "limit", "drilldown", "drilldown_sortby", + "drilldown_output_columns", "drilldown_offset", "drilldown_limit"}, + "defrag": {"objname", "threshold"}, + "delete": {"table", "key", "id", "filter"}, + "dump": {"tables"}, + "load": {"values", "table", "columns", "ifexists", "input_type"}, + "lock_clear": {"target_name"}, + "log_level": {"level"}, + "log_put": {"level", "message"}, + "log_reopen": {}, + "logical_count": { + "logical_table", "shared_key", "min", "min_border", "max", "max_border", + "filter"}, + "normalize": {"normalizer", "string", "flags"}, + "normalizer_list": {}, + "quit": {}, + // "range_filter": {}, + "register": {"path"}, + "request_cancel": {"id"}, + "ruby_eval": {"script"}, + "ruby_load": {"path"}, + "select": { + "table", "match_columns", "query", "filter", "scorer", "sortby", + "output_columns", "offset", "limit", "drilldown", "drilldown_sortby", + "drilldown_output_columns", "drilldown_offset", "drilldown_limit", + "cache", "match_escalation_threshold", "query_expansion", "query_flags", + "query_expander", "adjuster", "drilldown_calc_types", + "drilldown_calc_target"}, + "shutdown": {}, + "status": {}, + "suggest": {"types", "table", "column", "query", "sortby", "output_columns", + "offset", "limit", "frequency_threshold", + "conditional_probability_threshold", "prefix_search"}, + "table_create": { + "name", "flag", "key_type", "value_type", "default_tokenizer", + "normalizer", "token_filters"}, + "table_list": {}, + "table_remove": {"name"}, + "table_tokenize": {"table", "string", "flags", "mode"}, + "tokenize": { + "tokenizer", "string", "normalizer", "flags", "mode", "token_filters"}, + "tokenizer_list": {}, + "truncate": {"target_name"}} + +func (db *DB) parseCommandOptions(name string, tokens []string) ( + map[string]string, error) { + args := make([]string, 0) + options := make(map[string]string) + for i := 0; i < len(tokens); i++ { + if strings.HasPrefix(tokens[i], "--") { + key := tokens[i][2:] + i++ + if i >= len(tokens) { + return nil, fmt.Errorf("option argument missing") + } + options[key] = tokens[i] + } else { + args = append(args, tokens[i]) + } + } + keys := commandOptionKeys[name] + end := len(keys) + if end > len(args) { + end = len(args) + } + for i := 0; i < end; i++ { + options[keys[i]] = args[i] + } + return options, nil +} + +func (db *DB) ParseCommand(command string) (string, map[string]string, error) { + tokens, err := db.tokenizeCommandAll(command) + if err != nil { + return "", nil, err + } + if len(tokens) == 0 { + return "", nil, nil + } + // Parse tokens. + name := tokens[0] + options, err := db.parseCommandOptions(name, tokens[1:]) + if err != nil { + return "", nil, err + } + return name, options, nil +} Added: go/gnx/groonga/groonga_test.go (+1 -0) 100644 =================================================================== --- /dev/null +++ go/gnx/groonga/groonga_test.go 2015-03-20 21:42:11 +0900 (89681a9) @@ -0,0 +1 @@ +package groonga Added: go/gnxConsole.go (+98 -0) 100644 =================================================================== --- /dev/null +++ go/gnxConsole.go 2015-03-20 21:42:11 +0900 (2393d26) @@ -0,0 +1,98 @@ +package main + +import "./gnx" +import "bufio" +import "flag" +import "fmt" +import "io" +import "log" +import "os" +import "runtime/pprof" +import "strings" + +// Command-line flags. +var flagNew = flag.Bool("new", false, "create new database") +var flagTemp = flag.Bool("temp", false, "create temporary database") +var flagPartition = flag.Int("partition", 1, "the number of groonga DBs") +var flagProfile = flag.String("profile", "", "the file for cpu profiling") + +func openOrCreateDB() (*gnx.DB, string, error) { + path := "" + if flag.NArg() != 0 { + path = flag.Arg(0) + } + if *flagTemp { + db, dir, err := gnx.CreateTempDB(path, "gnx", *flagPartition) + if err != nil { + log.Println("gnx.CreateTempDB() failed: err =", err) + return nil, "", err + } + return db, dir, err + } + if *flagNew { + db, err := gnx.CreateDB(path, *flagPartition) + if err != nil { + log.Println("gnx.CreateDB() failed: err =", err) + return nil, "", err + } + return db, "", err + } + db, err := gnx.OpenDB(path) + if err != nil { + log.Println("gnx.OpenDB() failed: err =", err) + return nil, "", err + } + return db, "", err +} + +func consoleMain() int { + // Parse command-line options. + flag.Parse() + if *flagProfile != "" { + file, err := os.Create(*flagProfile) + if err != nil { + log.Println("profile failed: err =", err) + return 1 + } + pprof.StartCPUProfile(file) + defer pprof.StopCPUProfile() + } + + // Open or create a database. + db, dir, err := openOrCreateDB() + if err != nil { + return 1 + } + if len(dir) != 0 { + defer os.RemoveAll(dir) + } + defer db.Close() + + // Read lines from the standard input. + console := bufio.NewReader(os.Stdin) + for { + fmt.Print("> ") + line, err := console.ReadString('\n') + if err == io.EOF { + break + } else if err != nil { + log.Println("console error: err =", err) + return 1 + } + command := strings.TrimRight(line, "\r\n") +// name, options, err := db.ParseCommand(command) +// fmt.Println("name:", name) +// fmt.Println("options:", options) + bytes, err := db.Query(command) + if err != nil { + log.Println("query error: err =", err) + } else { + fmt.Println(string(bytes)) + } + } + return 0 +} + +func main() { + os.Exit(consoleMain()) +} Added: go/groongaConsole.go (+94 -0) 100644 =================================================================== --- /dev/null +++ go/groongaConsole.go 2015-03-20 21:42:11 +0900 (6f21f82) @@ -0,0 +1,94 @@ +package main + +import "./gnx/groonga" +import "bufio" +import "flag" +import "fmt" +import "io" +import "log" +import "os" +import "strings" + +// Command-line flags. +var flagNew = flag.Bool("new", false, "create new database") +var flagTemp = flag.Bool("temp", false, "create temporary database") + +func openOrCreateDB() (*groonga.DB, string, error) { + path := "" + if flag.NArg() != 0 { + path = flag.Arg(0) + } + if *flagTemp { + db, dir, err := groonga.CreateTempDB(path, "groonga") + if err != nil { + log.Println("groonga.CreateTempDB() failed: err =", err) + return nil, "", err + } + return db, dir, err + } + if *flagNew { + db, err := groonga.CreateDB(path) + if err != nil { + log.Println("groonga.CreateDB() failed: err =", err) + return nil, "", err + } + return db, "", err + } + db, err := groonga.OpenDB(path) + if err != nil { + log.Println("groonga.OpenDB() failed: err =", err) + return nil, "", err + } + return db, "", err +} + +func consoleMain() int { + // Parse command-line options. + flag.Parse() + + // Open or create a database. + db, dir, err := openOrCreateDB() + if err != nil { + return 1 + } + if len(dir) != 0 { + defer os.RemoveAll(dir) + } + defer db.Close() + + // Read lines from the standard input. + console := bufio.NewReader(os.Stdin) + for { + fmt.Print("> ") + line, err := console.ReadString('\n') + if err == io.EOF { + break + } else if err != nil { + log.Println("console error: err =", err) + return 1 + } + command := strings.TrimRight(line, "\r\n") + bytes, err := db.Query(command) + if err != nil { + log.Println("query error: err =", err) + } else { + fmt.Println(string(bytes)) + } + + name, options, err := db.ParseCommand(command) + fmt.Println("name:", name) + fmt.Println("options:", options) + if name == "select" { + records, err := db.Select(options) + if err != nil { + log.Println("select error: err =", err) + } + fmt.Println("records:", records) + } + } + return 0 +} + +func main() { + os.Exit(consoleMain()) +}