[Groonga-commit] groonga/grnxx at f0cbcc8 [master] Gnx: first commit for Gnx.

Back to archive index

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())
+}




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