[Groonga-commit] groonga/grnci at e0765d0 [master] Update v2.

Zurück zum Archiv-Index

Susumu Yata null+****@clear*****
Fri Jun 16 10:52:57 JST 2017


Susumu Yata	2017-06-16 10:52:57 +0900 (Fri, 16 Jun 2017)

  New Revision: e0765d0466e48a525a24bf9e9ee9efbd765582a1
  https://github.com/groonga/grnci/commit/e0765d0466e48a525a24bf9e9ee9efbd765582a1

  Message:
    Update v2.

  Added files:
    v2/command.go
    v2/command_test.go
    v2/info.go
    v2/type.go
  Removed files:
    v2/request.go
    v2/request_test.go
    v2/rule.go
  Modified files:
    v2/address.go
    v2/db.go
    v2/db_test.go
    v2/error.go
    v2/error_test.go
    v2/gqtp.go
    v2/gqtp_test.go
    v2/handler.go
    v2/http.go
    v2/http_test.go
    v2/libgrn/client.go
    v2/libgrn/client_test.go
    v2/libgrn/conn.go
    v2/libgrn/conn_test.go
    v2/libgrn/libgrn.go
    v2/libgrn/response.go
    v2/response.go

  Modified: v2/address.go (+11 -11)
===================================================================
--- v2/address.go    2017-06-14 10:41:58 +0900 (64275ab)
+++ v2/address.go    2017-06-16 10:52:57 +0900 (21648ac)
@@ -35,13 +35,13 @@ func (a *Address) fillGQTP() error {
 		a.Scheme = "gqtp"
 	}
 	if a.Username != "" {
-		return NewError(StatusInvalidAddress, map[string]interface{}{
+		return NewError(InvalidAddress, map[string]interface{}{
 			"username": a.Username,
 			"error":    "GQTP does not accept username.",
 		})
 	}
 	if a.Password != "" {
-		return NewError(StatusInvalidAddress, map[string]interface{}{
+		return NewError(InvalidAddress, map[string]interface{}{
 			"password": a.Password,
 			"error":    "GQTP does not accept password.",
 		})
@@ -53,19 +53,19 @@ func (a *Address) fillGQTP() error {
 		a.Port = GQTPDefaultPort
 	}
 	if a.Path != "" {
-		return NewError(StatusInvalidAddress, map[string]interface{}{
+		return NewError(InvalidAddress, map[string]interface{}{
 			"path":  a.Path,
 			"error": "GQTP does not accept path.",
 		})
 	}
 	if a.Query != "" {
-		return NewError(StatusInvalidAddress, map[string]interface{}{
+		return NewError(InvalidAddress, map[string]interface{}{
 			"query": a.Query,
 			"error": "GQTP does not accept query.",
 		})
 	}
 	if a.Fragment != "" {
-		return NewError(StatusInvalidAddress, map[string]interface{}{
+		return NewError(InvalidAddress, map[string]interface{}{
 			"fragment": a.Fragment,
 			"error":    "GQTP does not accept fragment.",
 		})
@@ -105,7 +105,7 @@ func (a *Address) fill() error {
 			return err
 		}
 	default:
-		return NewError(StatusInvalidAddress, map[string]interface{}{
+		return NewError(InvalidAddress, map[string]interface{}{
 			"scheme": a.Scheme,
 			"error":  "The scheme is not supported.",
 		})
@@ -122,7 +122,7 @@ func (a *Address) parseHostPort(s string) error {
 	if s[0] == '[' {
 		i := strings.IndexByte(s, ']')
 		if i == -1 {
-			return NewError(StatusInvalidAddress, map[string]interface{}{
+			return NewError(InvalidAddress, map[string]interface{}{
 				"address": s,
 				"error":   "IPv6 address must be enclosed in [].",
 			})
@@ -133,7 +133,7 @@ func (a *Address) parseHostPort(s string) error {
 			return nil
 		}
 		if rest[0] != ':' {
-			return NewError(StatusInvalidAddress, map[string]interface{}{
+			return NewError(InvalidAddress, map[string]interface{}{
 				"address": s,
 				"error":   "IPv6 address and port must be separated by ':'.",
 			})
@@ -151,7 +151,7 @@ func (a *Address) parseHostPort(s string) error {
 	if portStr != "" {
 		port, err := net.LookupPort("tcp", portStr)
 		if err != nil {
-			return NewError(StatusInvalidAddress, map[string]interface{}{
+			return NewError(InvalidAddress, map[string]interface{}{
 				"port":  portStr,
 				"error": err.Error(),
 			})
@@ -223,7 +223,7 @@ func ParseGQTPAddress(s string) (*Address, error) {
 	switch strings.ToLower(a.Scheme) {
 	case "", "gqtp":
 	default:
-		return nil, NewError(StatusInvalidAddress, map[string]interface{}{
+		return nil, NewError(InvalidAddress, map[string]interface{}{
 			"scheme": a.Scheme,
 			"error":  "The scheme is not supported.",
 		})
@@ -245,7 +245,7 @@ func ParseHTTPAddress(s string) (*Address, error) {
 	switch strings.ToLower(a.Scheme) {
 	case "", "http", "https":
 	default:
-		return nil, NewError(StatusInvalidAddress, map[string]interface{}{
+		return nil, NewError(InvalidAddress, map[string]interface{}{
 			"scheme": a.Scheme,
 			"error":  "The scheme is not supported.",
 		})

  Added: v2/command.go (+899 -0) 100644
===================================================================
--- /dev/null
+++ v2/command.go    2017-06-16 10:52:57 +0900 (3b97da3)
@@ -0,0 +1,899 @@
+package grnci
+
+import (
+	"io"
+	"reflect"
+	"sort"
+	"strconv"
+	"strings"
+)
+
+// formatParamValue is a function to format a parameter value.
+type formatParamValue func(value interface{}) (string, error)
+
+// formatParamValueDefault is the default formatParamValue.
+var formatParamValueDefault = func(value interface{}) (string, error) {
+	switch v := value.(type) {
+	case bool:
+		return strconv.FormatBool(v), nil
+	case int:
+		return strconv.FormatInt(int64(v), 10), nil
+	case int8:
+		return strconv.FormatInt(int64(v), 10), nil
+	case int16:
+		return strconv.FormatInt(int64(v), 10), nil
+	case int32:
+		return strconv.FormatInt(int64(v), 10), nil
+	case int64:
+		return strconv.FormatInt(v, 10), nil
+	case uint:
+		return strconv.FormatUint(uint64(v), 10), nil
+	case uint8:
+		return strconv.FormatUint(uint64(v), 10), nil
+	case uint16:
+		return strconv.FormatUint(uint64(v), 10), nil
+	case uint32:
+		return strconv.FormatUint(uint64(v), 10), nil
+	case uint64:
+		return strconv.FormatUint(v, 10), nil
+	case float32:
+		return strconv.FormatFloat(float64(v), 'f', -1, 32), nil
+	case float64:
+		return strconv.FormatFloat(v, 'f', -1, 64), nil
+	case string:
+		return v, nil
+	default:
+		return "", NewError(InvalidCommand, map[string]interface{}{
+			"value": value,
+			"type":  reflect.TypeOf(value).Name(),
+			"error": "The type is not supported.",
+		})
+	}
+}
+
+// yesNo returns yes or no.
+func yesNo(value bool) string {
+	if value {
+		return "yes"
+	}
+	return "no"
+}
+
+// formatParamValueYesNo formats an yes/no value.
+func formatParamValueYesNo(value interface{}) (string, error) {
+	switch v := value.(type) {
+	case bool:
+		return yesNo(v), nil
+	case string:
+		switch v {
+		case "yes", "no":
+			return v, nil
+		default:
+			return "", NewError(InvalidCommand, map[string]interface{}{
+				"value": v,
+				"error": "The value must be yes or no.",
+			})
+		}
+	default:
+		return "", NewError(InvalidCommand, map[string]interface{}{
+			"value": value,
+			"type":  reflect.TypeOf(value).Name(),
+			"error": "The type is not supported.",
+		})
+	}
+}
+
+// formatParamValueCSV formats comma-separated values.
+func formatParamValueCSV(value interface{}) (string, error) {
+	switch v := value.(type) {
+	case string:
+		return v, nil
+	case []string:
+		return strings.Join(v, ","), nil
+	default:
+		return "", NewError(InvalidCommand, map[string]interface{}{
+			"value": value,
+			"type":  reflect.TypeOf(value).Name(),
+			"error": "The type is not supported.",
+		})
+	}
+}
+
+// formatParamValueFlags formats pipe-separated values.
+func formatParamValueFlags(value interface{}) (string, error) {
+	switch v := value.(type) {
+	case string:
+		return v, nil
+	case []string:
+		return strings.Join(v, "|"), nil
+	default:
+		return "", NewError(InvalidCommand, map[string]interface{}{
+			"value": value,
+			"type":  reflect.TypeOf(value).Name(),
+			"error": "The type is not supported.",
+		})
+	}
+}
+
+// formatParamValueBorder formats an include/exclude value.
+func formatParamValueBorder(value interface{}) (string, error) {
+	switch v := value.(type) {
+	case bool:
+		if v {
+			return "include", nil
+		}
+		return "exclude", nil
+	case string:
+		switch v {
+		case "include", "exclude":
+			return v, nil
+		default:
+			return "", NewError(InvalidCommand, map[string]interface{}{
+				"value": v,
+				"error": "The value must be include or exclude.",
+			})
+		}
+	default:
+		return "", NewError(InvalidCommand, map[string]interface{}{
+			"value": value,
+			"type":  reflect.TypeOf(value).Name(),
+			"error": "The type is not supported.",
+		})
+	}
+}
+
+type paramFormat struct {
+	key      string           // Parameter key
+	format   formatParamValue // Custom function to format a parameter value.
+	required bool             // Whether or not the parameter is required
+}
+
+// newParamFormat returns a new paramFormat.
+func newParamFormat(key string, format formatParamValue, required bool) *paramFormat {
+	return &paramFormat{
+		key:      key,
+		format:   format,
+		required: required,
+	}
+}
+
+// Format formats a parameter value.
+func (pf *paramFormat) Format(value interface{}) (string, error) {
+	if pf.format != nil {
+		return pf.format(value)
+	}
+	return formatParamValueDefault(value)
+}
+
+// formatParam is a function to format a parameter.
+type formatParam func(key string, value interface{}) (string, error)
+
+// formatParamDefault is the default formatParam.
+func formatParamDefault(key string, value interface{}) (string, error) {
+	if key == "" {
+		return "", NewError(InvalidCommand, map[string]interface{}{
+			"key":   key,
+			"error": "The key must not be empty.",
+		})
+	}
+	for _, c := range key {
+		switch {
+		case c >= 'a' && c <= 'z':
+		case c == '_':
+		default:
+			return "", NewError(InvalidCommand, map[string]interface{}{
+				"key":   key,
+				"error": "The key must consist of [a-z_].",
+			})
+		}
+	}
+	fv, err := formatParamValueDefault(value)
+	if err != nil {
+		return "", EnhanceError(err, map[string]interface{}{
+			"key": key,
+		})
+	}
+	return fv, nil
+}
+
+// formatParamSelect formats a parameter of select.
+func formatParamSelect(key string, value interface{}) (string, error) {
+	if key == "" {
+		return "", NewError(InvalidCommand, map[string]interface{}{
+			"key":   key,
+			"error": "The key must not be empty.",
+		})
+	}
+	for _, c := range key {
+		switch {
+		case c >= '0' && c <= '9':
+		case c >= 'A' && c <= 'Z':
+		case c >= 'a' && c <= 'z':
+		default:
+			switch c {
+			case '#', '@', '-', '_', '.', '[', ']':
+			default:
+				return "", NewError(InvalidCommand, map[string]interface{}{
+					"key":   key,
+					"error": "The key must consist of [0-9A-Za-z#@-_.[]].",
+				})
+			}
+		}
+	}
+	fv, err := formatParamValueDefault(value)
+	if err != nil {
+		return "", EnhanceError(err, map[string]interface{}{
+			"key": key,
+		})
+	}
+	return fv, nil
+}
+
+type commandFormat struct {
+	format         formatParam             // Custom function to format a parameter
+	params         []*paramFormat          // Fixed parameters
+	paramsByKey    map[string]*paramFormat // Index for params
+	requiredParams []*paramFormat          // Required parameters
+}
+
+// newCommandFormat returns a new commandFormat.
+func newCommandFormat(format formatParam, params ...*paramFormat) *commandFormat {
+	paramsByKey := make(map[string]*paramFormat)
+	var requiredParams []*paramFormat
+	for _, param := range params {
+		paramsByKey[param.key] = param
+		if param.required {
+			requiredParams = append(requiredParams, param)
+		}
+	}
+	return &commandFormat{
+		format:         format,
+		params:         params,
+		paramsByKey:    paramsByKey,
+		requiredParams: requiredParams,
+	}
+}
+
+// Format formats a parameter.
+func (cf *commandFormat) Format(key string, value interface{}) (string, error) {
+	if pf, ok := cf.paramsByKey[key]; ok {
+		return pf.Format(value)
+	}
+	if cf.format != nil {
+		return cf.format(key, value)
+	}
+	return formatParamDefault(key, value)
+}
+
+// commandFormats defines the available commands.
+// The contents are set in initCommandFormats.
+var commandFormats = map[string]*commandFormat{
+	"cache_limit": newCommandFormat(
+		nil,
+		newParamFormat("max", nil, false),
+	),
+	"check": newCommandFormat(
+		nil,
+		newParamFormat("obj", nil, true),
+	),
+	"clearlock": newCommandFormat(
+		nil,
+		newParamFormat("objname", nil, true),
+	),
+	"column_copy": newCommandFormat(
+		nil,
+		newParamFormat("from_table", nil, true),
+		newParamFormat("from_name", nil, true),
+		newParamFormat("to_table", nil, true),
+		newParamFormat("to_name", nil, true),
+	),
+	"column_create": newCommandFormat(
+		nil,
+		newParamFormat("table", nil, true),
+		newParamFormat("name", nil, true),
+		newParamFormat("flags", formatParamValueFlags, true),
+		newParamFormat("type", nil, true),
+		newParamFormat("source", nil, false),
+	),
+	"column_list": newCommandFormat(
+		nil,
+		newParamFormat("table", nil, true),
+	),
+	"column_remove": newCommandFormat(nil,
+		newParamFormat("table", nil, true),
+		newParamFormat("name", nil, true),
+	),
+	"column_rename": newCommandFormat(nil,
+		newParamFormat("table", nil, true),
+		newParamFormat("name", nil, true),
+		newParamFormat("new_name", nil, true),
+	),
+	"config_delete": newCommandFormat(
+		nil,
+		newParamFormat("key", nil, true),
+	),
+	"config_get": newCommandFormat(
+		nil,
+		newParamFormat("key", nil, true),
+	),
+	"config_set": newCommandFormat(
+		nil,
+		newParamFormat("key", nil, true),
+		newParamFormat("value", nil, true),
+	),
+	"database_unmap": newCommandFormat(nil),
+	"define_selector": newCommandFormat(
+		nil,
+		newParamFormat("name", nil, true),
+		newParamFormat("table", nil, true),
+		newParamFormat("match_columns", formatParamValueCSV, false),
+		newParamFormat("query", nil, false),
+		newParamFormat("filter", nil, false),
+		newParamFormat("scorer", nil, false),
+		newParamFormat("sortby", formatParamValueCSV, false),
+		newParamFormat("output_columns", formatParamValueCSV, false),
+		newParamFormat("offset", nil, false),
+		newParamFormat("limit", nil, false),
+		newParamFormat("drilldown", nil, false),
+		newParamFormat("drilldown_sortby", formatParamValueCSV, false),
+		newParamFormat("drilldown_output_columns", formatParamValueCSV, false),
+		newParamFormat("drilldown_offset", nil, false),
+		newParamFormat("drilldown_limit", nil, false),
+	),
+	"defrag": newCommandFormat(
+		nil,
+		newParamFormat("objname", nil, true),
+		newParamFormat("threshold", nil, true),
+	),
+	"delete": newCommandFormat(
+		nil,
+		newParamFormat("table", nil, true),
+		newParamFormat("key", nil, false),
+		newParamFormat("id", nil, false),
+		newParamFormat("filter", nil, false),
+	),
+	"dump": newCommandFormat(
+		nil,
+		newParamFormat("tables", formatParamValueCSV, false),
+		newParamFormat("dump_plugins", formatParamValueYesNo, false),
+		newParamFormat("dump_schema", formatParamValueYesNo, false),
+		newParamFormat("dump_records", formatParamValueYesNo, false),
+		newParamFormat("dump_indexes", formatParamValueYesNo, false),
+	),
+	"io_flush": newCommandFormat(
+		nil,
+		newParamFormat("target_name", nil, false),
+		newParamFormat("recursive", nil, false),
+	),
+	"load": newCommandFormat(
+		nil,
+		newParamFormat("values", nil, false), // values may be passed as a body.
+		newParamFormat("table", nil, true),
+		newParamFormat("columns", formatParamValueCSV, false),
+		newParamFormat("ifexists", nil, false),
+		newParamFormat("input_type", nil, false),
+	),
+	"lock_acquire": newCommandFormat(
+		nil,
+		newParamFormat("target_name", nil, false),
+	),
+	"lock_clear": newCommandFormat(
+		nil,
+		newParamFormat("target_name", nil, false),
+	),
+	"lock_release": newCommandFormat(
+		nil,
+		newParamFormat("target_name", nil, false),
+	),
+	"log_level": newCommandFormat(
+		nil,
+		newParamFormat("level", nil, true),
+	),
+	"log_put": newCommandFormat(
+		nil,
+		newParamFormat("level", nil, true),
+		newParamFormat("message", nil, true),
+	),
+	"log_reopen": newCommandFormat(nil),
+	"logical_count": newCommandFormat(
+		nil,
+		newParamFormat("logical_table", nil, true),
+		newParamFormat("shard_key", nil, true),
+		newParamFormat("min", nil, false),
+		newParamFormat("min_border", formatParamValueBorder, false),
+		newParamFormat("max", nil, false),
+		newParamFormat("max_border", formatParamValueBorder, false),
+		newParamFormat("filter", nil, false),
+	),
+	"logical_parameters": newCommandFormat(
+		nil,
+		newParamFormat("range_index", nil, false),
+	),
+	"logical_range_filter": newCommandFormat(
+		nil,
+		newParamFormat("logical_table", nil, true),
+		newParamFormat("shard_key", nil, true),
+		newParamFormat("min", nil, false),
+		newParamFormat("min_border", formatParamValueBorder, false),
+		newParamFormat("max", nil, false),
+		newParamFormat("max_border", formatParamValueBorder, false),
+		newParamFormat("order", nil, false),
+		newParamFormat("filter", nil, false),
+		newParamFormat("offset", nil, false),
+		newParamFormat("limit", nil, false),
+		newParamFormat("output_columns", formatParamValueCSV, false),
+		newParamFormat("use_range_index", nil, false),
+	),
+	"logical_select": newCommandFormat(
+		nil,
+		newParamFormat("logical_table", nil, true),
+		newParamFormat("shard_key", nil, true),
+		newParamFormat("min", nil, false),
+		newParamFormat("min_border", formatParamValueBorder, false),
+		newParamFormat("max", nil, false),
+		newParamFormat("max_border", formatParamValueBorder, false),
+		newParamFormat("filter", nil, false),
+		newParamFormat("sortby", nil, false),
+		newParamFormat("output_columns", formatParamValueCSV, false),
+		newParamFormat("offset", nil, false),
+		newParamFormat("limit", nil, false),
+		newParamFormat("drilldown", nil, false),
+		newParamFormat("drilldown_sortby", formatParamValueCSV, false),
+		newParamFormat("drilldown_output_columns", formatParamValueCSV, false),
+		newParamFormat("drilldown_offset", nil, false),
+		newParamFormat("drilldown_limit", nil, false),
+		newParamFormat("drilldown_calc_types", formatParamValueCSV, false),
+		newParamFormat("drilldown_calc_target", nil, false),
+		newParamFormat("sort_keys", nil, false),
+		newParamFormat("drilldown_sort_keys", formatParamValueCSV, false),
+		newParamFormat("match_columns", formatParamValueCSV, false),
+		newParamFormat("query", nil, false),
+		newParamFormat("drilldown_filter", nil, false),
+	),
+	"logical_shard_list": newCommandFormat(
+		nil,
+		newParamFormat("logical_table", nil, true),
+	),
+	"logical_table_remove": newCommandFormat(
+		nil,
+		newParamFormat("logical_table", nil, true),
+		newParamFormat("shard_key", nil, true),
+		newParamFormat("min", nil, false),
+		newParamFormat("min_border", formatParamValueBorder, false),
+		newParamFormat("max", nil, false),
+		newParamFormat("max_border", formatParamValueBorder, false),
+		newParamFormat("dependent", formatParamValueYesNo, false),
+		newParamFormat("force", formatParamValueYesNo, false),
+	),
+	"normalize": newCommandFormat(
+		nil,
+		newParamFormat("normalizer", nil, true),
+		newParamFormat("string", nil, true),
+		newParamFormat("flags", formatParamValueFlags, false),
+	),
+	"normalizer_list": newCommandFormat(nil),
+	"object_exist": newCommandFormat(
+		nil,
+		newParamFormat("name", nil, true),
+	),
+	"object_inspect": newCommandFormat(
+		nil,
+		newParamFormat("name", nil, false),
+	),
+	"object_list": newCommandFormat(nil),
+	"object_remove": newCommandFormat(
+		nil,
+		newParamFormat("name", nil, true),
+		newParamFormat("force", formatParamValueYesNo, false),
+	),
+	"plugin_register": newCommandFormat(
+		nil,
+		newParamFormat("name", nil, true),
+	),
+	"plugin_unregister": newCommandFormat(
+		nil,
+		newParamFormat("name", nil, true),
+	),
+	"query_expand": newCommandFormat(nil), // TODO
+	"quit":         newCommandFormat(nil),
+	"range_filter": newCommandFormat(nil), // TODO
+	"register": newCommandFormat(
+		nil,
+		newParamFormat("path", nil, true),
+	),
+	"reindex": newCommandFormat(
+		nil,
+		newParamFormat("target_name", nil, false),
+	),
+	"request_cancel": newCommandFormat(
+		nil,
+		newParamFormat("id", nil, true),
+	),
+	"ruby_eval": newCommandFormat(
+		nil,
+		newParamFormat("script", nil, true),
+	),
+	"ruby_load": newCommandFormat(
+		nil,
+		newParamFormat("path", nil, true),
+	),
+	"schema": newCommandFormat(nil),
+	"select": newCommandFormat(
+		formatParamSelect,
+		newParamFormat("table", nil, true),
+		newParamFormat("match_columns", formatParamValueCSV, false),
+		newParamFormat("query", nil, false),
+		newParamFormat("filter", nil, false),
+		newParamFormat("scorer", nil, false),
+		newParamFormat("sortby", nil, false),
+		newParamFormat("output_columns", formatParamValueCSV, false),
+		newParamFormat("offset", nil, false),
+		newParamFormat("limit", nil, false),
+		newParamFormat("drilldown", nil, false),
+		newParamFormat("drilldown_sortby", formatParamValueCSV, false),
+		newParamFormat("drilldown_output_columns", formatParamValueCSV, false),
+		newParamFormat("drilldown_offset", nil, false),
+		newParamFormat("drilldown_limit", nil, false),
+		newParamFormat("cache", formatParamValueYesNo, false),
+		newParamFormat("match_escalation_threshold", nil, false),
+		newParamFormat("query_expansion", nil, false),
+		newParamFormat("query_flags", formatParamValueFlags, false),
+		newParamFormat("query_expander", nil, false),
+		newParamFormat("adjuster", nil, false),
+		newParamFormat("drilldown_calc_types", formatParamValueCSV, false),
+		newParamFormat("drilldown_calc_target", nil, false),
+		newParamFormat("drilldown_filter", nil, false),
+		newParamFormat("sort_keys", formatParamValueCSV, false),
+		newParamFormat("drilldown_sort_keys", formatParamValueCSV, false),
+	),
+	"shutdown": newCommandFormat(
+		nil,
+		newParamFormat("mode", nil, false),
+	),
+	"status": newCommandFormat(nil),
+	"suggest": newCommandFormat(
+		nil,
+		newParamFormat("types", formatParamValueFlags, true),
+		newParamFormat("table", nil, true),
+		newParamFormat("column", nil, true),
+		newParamFormat("query", nil, true),
+		newParamFormat("sortby", formatParamValueCSV, false),
+		newParamFormat("output_columns", formatParamValueCSV, false),
+		newParamFormat("offset", nil, false),
+		newParamFormat("limit", nil, false),
+		newParamFormat("frequency_threshold", nil, false),
+		newParamFormat("conditional_probability_threshold", nil, false),
+		newParamFormat("prefix_search", nil, false),
+	),
+	"table_copy": newCommandFormat(
+		nil,
+		newParamFormat("from_name", nil, true),
+		newParamFormat("to_name", nil, true),
+	),
+	"table_create": newCommandFormat(
+		nil,
+		newParamFormat("name", nil, true),
+		newParamFormat("flags", formatParamValueFlags, false),
+		newParamFormat("key_type", nil, false),
+		newParamFormat("value_type", nil, false),
+		newParamFormat("default_tokenizer", nil, false),
+		newParamFormat("normalizer", nil, false),
+		newParamFormat("token_filters", formatParamValueCSV, false),
+	),
+	"table_list": newCommandFormat(nil),
+	"table_remove": newCommandFormat(
+		nil,
+		newParamFormat("name", nil, true),
+		newParamFormat("dependent", formatParamValueYesNo, false),
+	),
+	"table_rename": newCommandFormat(
+		nil,
+		newParamFormat("name", nil, true),
+		newParamFormat("new_name", nil, true),
+	),
+	"table_tokenize": newCommandFormat(
+		nil,
+		newParamFormat("table", nil, true),
+		newParamFormat("string", nil, true),
+		newParamFormat("flags", formatParamValueFlags, false),
+		newParamFormat("mode", nil, false),
+		newParamFormat("index_column", nil, false),
+	),
+	"thread_limit": newCommandFormat(
+		nil,
+		newParamFormat("max", nil, false),
+	),
+	"tokenize": newCommandFormat(
+		nil,
+		newParamFormat("tokenizer", nil, true),
+		newParamFormat("string", nil, true),
+		newParamFormat("normalizer", nil, false),
+		newParamFormat("flags", formatParamValueFlags, false),
+		newParamFormat("mode", nil, false),
+		newParamFormat("token_filters", formatParamValueCSV, false),
+	),
+	"tokenizer_list": newCommandFormat(nil),
+	"truncate": newCommandFormat(
+		nil,
+		newParamFormat("target_name", nil, true),
+	),
+}
+
+// Command is a command.
+type Command struct {
+	name   string            // Command name
+	format *commandFormat    // Command format
+	params map[string]string // Parameters
+	index  int               // Number of unnamed parameters
+	body   io.Reader         // Command body
+}
+
+// newCommand returns a new Command.
+func newCommand(name string) (*Command, error) {
+	format, ok := commandFormats[name]
+	if !ok {
+		return nil, NewError(InvalidCommand, map[string]interface{}{
+			"name":  name,
+			"error": "The name is not defined.",
+		})
+	}
+	return &Command{
+		name:   name,
+		format: format,
+		params: make(map[string]string),
+	}, nil
+}
+
+// NewCommand formats params and returns a new Command.
+func NewCommand(name string, params map[string]interface{}) (*Command, error) {
+	c, err := newCommand(name)
+	if err != nil {
+		return nil, err
+	}
+	for k, v := range params {
+		if err := c.SetParam(k, v); err != nil {
+			return nil, EnhanceError(err, map[string]interface{}{
+				"name": name,
+			})
+		}
+	}
+	return c, nil
+
+}
+
+// unescapeCommandByte returns an unescaped space character.
+func unescapeCommandByte(b byte) byte {
+	switch b {
+	case 'b':
+		return '\b'
+	case 't':
+		return '\t'
+	case 'r':
+		return '\r'
+	case 'n':
+		return '\n'
+	default:
+		return b
+	}
+}
+
+// tokenizeCommand tokenizes a command.
+func tokenizeCommand(cmd string) ([]string, error) {
+	var tokens []string
+	var token []byte
+	s := cmd
+	for {
+		s = strings.TrimLeft(s, " \t\r\n")
+		if s == "" {
+			break
+		}
+		switch s[0] {
+		case '"', '\'':
+			i := 1
+			for ; i < len(s); i++ {
+				if s[i] == s[0] {
+					i++
+					break
+				}
+				if s[i] != '\\' {
+					token = append(token, s[i])
+					continue
+				}
+				i++
+				if i == len(s) {
+					return nil, NewError(InvalidCommand, map[string]interface{}{
+						"command": cmd,
+						"error":   "The command ends with an unclosed token.",
+					})
+				}
+				token = append(token, unescapeCommandByte(s[i]))
+			}
+			s = s[i:]
+		default:
+			i := 0
+		Loop:
+			for ; i < len(s); i++ {
+				switch s[i] {
+				case ' ', '\t', '\r', '\n', '"', '\'':
+					break Loop
+				case '\\':
+					i++
+					if i == len(s) {
+						return nil, NewError(InvalidCommand, map[string]interface{}{
+							"command": cmd,
+							"error":   "The command ends with an escape character.",
+						})
+					}
+					token = append(token, unescapeCommandByte(s[i]))
+				default:
+					token = append(token, s[i])
+				}
+			}
+			s = s[i:]
+		}
+		tokens = append(tokens, string(token))
+		token = token[:0]
+	}
+	return tokens, nil
+}
+
+// ParseCommand parses cmd and returns a new Command.
+func ParseCommand(cmd string) (*Command, error) {
+	tokens, err := tokenizeCommand(cmd)
+	if err != nil {
+		return nil, err
+	}
+	if len(tokens) == 0 {
+		return nil, NewError(InvalidCommand, map[string]interface{}{
+			"command": cmd,
+			"error":   "The command has no tokens.",
+		})
+	}
+	c, err := newCommand(tokens[0])
+	if err != nil {
+		return nil, EnhanceError(err, map[string]interface{}{
+			"command": cmd,
+		})
+	}
+	for i := 1; i < len(tokens); i++ {
+		var k, v string
+		if strings.HasPrefix(tokens[i], "--") {
+			k = tokens[i][2:]
+			i++
+			if i >= len(tokens) {
+				return nil, NewError(InvalidCommand, map[string]interface{}{
+					"command": cmd,
+					"key":     k,
+					"error":   "The key requires a value.",
+				})
+			}
+		}
+		v = tokens[i]
+		if err := c.SetParam(k, v); err != nil {
+			return nil, err
+		}
+	}
+	return c, nil
+}
+
+// Name returns the command name.
+func (c *Command) Name() string {
+	return c.name
+}
+
+// Params returns the command parameters.
+func (c *Command) Params() map[string]string {
+	return c.params
+}
+
+// Body returns the command body.
+func (c *Command) Body() io.Reader {
+	return c.body
+}
+
+// NeedsBody returns whether or not the command requires a body.
+func (c *Command) NeedsBody() bool {
+	if c.name == "load" {
+		if _, ok := c.params["values"]; !ok {
+			return true
+		}
+	}
+	return false
+}
+
+// Check checks whether or not the command has required parameters.
+func (c *Command) Check() error {
+	for _, pf := range c.format.requiredParams {
+		if _, ok := c.params[pf.key]; !ok {
+			return NewError(InvalidCommand, map[string]interface{}{
+				"name":  c.name,
+				"key":   pf.key,
+				"error": "The command requires the key.",
+			})
+		}
+	}
+	if c.NeedsBody() {
+		if c.body == nil {
+			return NewError(InvalidCommand, map[string]interface{}{
+				"name":  c.name,
+				"error": "The command requires a body",
+			})
+		}
+	}
+	return nil
+}
+
+// SetParam adds or removes a parameter.
+// If value == nil, it adds a parameter.
+// Otherwise, it removes a parameter.
+func (c *Command) SetParam(key string, value interface{}) error {
+	if value == nil {
+		if _, ok := c.params[key]; !ok {
+			return NewError(InvalidCommand, map[string]interface{}{
+				"name":  c.name,
+				"key":   key,
+				"error": "The key does not exist.",
+			})
+		}
+		delete(c.params, key)
+		return nil
+	}
+	if key == "" {
+		if c.index >= len(c.format.params) {
+			return NewError(InvalidCommand, map[string]interface{}{
+				"name":  c.name,
+				"index": c.index,
+				"error": "The index is too large.",
+			})
+		}
+		pf := c.format.params[c.index]
+		fv, err := pf.Format(value)
+		if err != nil {
+			return EnhanceError(err, map[string]interface{}{
+				"name": c.name,
+				"key":  key,
+			})
+		}
+		c.params[pf.key] = fv
+		c.index++
+		return nil
+	}
+	fv, err := c.format.Format(key, value)
+	if err != nil {
+		return EnhanceError(err, map[string]interface{}{
+			"name": c.name,
+		})
+	}
+	c.params[key] = fv
+	return nil
+}
+
+// SetBody sets a body.
+func (c *Command) SetBody(body io.Reader) {
+	c.body = body
+}
+
+// String assembles the command name and parameters.
+func (c *Command) String() string {
+	cmd := []byte(c.name)
+	keys := make([]string, 0, len(c.params))
+	for k := range c.params {
+		keys = append(keys, k)
+	}
+	sort.Strings(keys)
+	for _, k := range keys {
+		v := c.params[k]
+		cmd = append(cmd, " --"...)
+		cmd = append(cmd, k...)
+		cmd = append(cmd, " '"...)
+		for i := 0; i < len(v); i++ {
+			switch v[i] {
+			case '\'', '\\', '\b', '\t', '\r', '\n':
+				cmd = append(cmd, '\\')
+			}
+			cmd = append(cmd, v[i])
+		}
+		cmd = append(cmd, '\'')
+	}
+	return string(cmd)
+}

  Added: v2/command_test.go (+106 -0) 100644
===================================================================
--- /dev/null
+++ v2/command_test.go    2017-06-16 10:52:57 +0900 (a8dea64)
@@ -0,0 +1,106 @@
+package grnci
+
+import (
+	"testing"
+)
+
+func TestNewCommand(t *testing.T) {
+	params := map[string]interface{}{
+		"table":     "Tbl",
+		"filter":    "value < 100",
+		"sort_keys": "value",
+		"cache":     false,
+		"offset":    0,
+		"limit":     -1,
+	}
+	cmd, err := NewCommand("select", params)
+	if err != nil {
+		t.Fatalf("NewCommand failed: %v", err)
+	}
+	if cmd.Name() != "select" {
+		t.Fatalf("NewCommand failed: name = %s, want = %s", cmd.Name(), "select")
+	}
+	if key, want := "table", "Tbl"; cmd.Params()[key] != want {
+		t.Fatalf("NewCommand failed: params[\"%s\"] = %s, want = %v", key, cmd.Params()[key], want)
+	}
+	if key, want := "cache", "no"; cmd.Params()[key] != want {
+		t.Fatalf("NewCommand failed: params[\"%s\"] = %s, want = %v", key, cmd.Params()[key], want)
+	}
+	if key, want := "limit", "-1"; cmd.Params()[key] != want {
+		t.Fatalf("NewCommand failed: params[\"%s\"] = %s, want = %v", key, cmd.Params()[key], want)
+	}
+}
+
+func TestParseCommand(t *testing.T) {
+	cmd, err := ParseCommand(`select Tbl --query '"apple juice"' --filter 'price < 100' --cache no`)
+	if err != nil {
+		t.Fatalf("ParseCommand failed: %v", err)
+	}
+	if want := "select"; cmd.Name() != want {
+		t.Fatalf("ParseCommand failed: name = %s, want = %s", cmd.Name(), want)
+	}
+	if key, want := "table", "Tbl"; cmd.Params()[key] != want {
+		t.Fatalf("NewCommand failed: params[\"%s\"] = %s, want = %v", key, cmd.Params()[key], want)
+	}
+	if key, want := "query", `"apple juice"`; cmd.Params()[key] != want {
+		t.Fatalf("NewCommand failed: params[\"%s\"] = %s, want = %v", key, cmd.Params()[key], want)
+	}
+	if key, want := "cache", "no"; cmd.Params()[key] != want {
+		t.Fatalf("NewCommand failed: params[\"%s\"] = %s, want = %v", key, cmd.Params()[key], want)
+	}
+}
+
+func TestCommandSetParam(t *testing.T) {
+	cmd, err := NewCommand("select", nil)
+	if err != nil {
+		t.Fatalf("NewCommand failed: %v", err)
+	}
+	if err := cmd.SetParam("", "Tbl"); err != nil {
+		t.Fatalf("cmd.SetParam failed: %v", err)
+	}
+	if err := cmd.SetParam("cache", false); err != nil {
+		t.Fatalf("cmd.SetParam failed: %v", err)
+	}
+	if err := cmd.SetParam("cache", true); err != nil {
+		t.Fatalf("cmd.SetParam failed: %v", err)
+	}
+	if err := cmd.SetParam("cache", nil); err != nil {
+		t.Fatalf("cmd.SetParam failed: %v", err)
+	}
+}
+
+func TestCommandString(t *testing.T) {
+	params := map[string]interface{}{
+		"table": "Tbl",
+		"cache": "no",
+		"limit": -1,
+	}
+	cmd, err := NewCommand("select", params)
+	if err != nil {
+		t.Fatalf("NewCommand failed: %v", err)
+	}
+	actual := cmd.String()
+	want := "select --cache 'no' --limit '-1' --table 'Tbl'"
+	if actual != want {
+		t.Fatalf("cmd.String failed: actual = %s, want = %s", actual, want)
+	}
+}
+
+func TestCommandNeedsBody(t *testing.T) {
+	data := map[string]bool{
+		"status":                       false,
+		"select Tbl":                   false,
+		"load --table Tbl":             true,
+		"load --table Tbl --values []": false,
+	}
+	for src, want := range data {
+		cmd, err := ParseCommand(src)
+		if err != nil {
+			t.Fatalf("ParseCommand failed: %v", err)
+		}
+		actual := cmd.NeedsBody()
+		if actual != want {
+			t.Fatalf("cmd.NeedsBody failed: cmd = %s, needsBody = %v, want = %v", cmd, actual, want)
+		}
+	}
+}

  Modified: v2/db.go (+310 -153)
===================================================================
--- v2/db.go    2017-06-14 10:41:58 +0900 (4b49949)
+++ v2/db.go    2017-06-16 10:52:57 +0900 (418f6c2)
@@ -1,10 +1,12 @@
 package grnci
 
 import (
+	"bytes"
 	"encoding/json"
 	"fmt"
 	"io"
 	"io/ioutil"
+	"reflect"
 	"strings"
 	"time"
 )
@@ -21,10 +23,10 @@ func NewDB(h Handler) *DB {
 
 // ColumnCreate executes column_create.
 func (db *DB) ColumnCreate(tbl, name, typ, flags string) (bool, Response, error) {
-	req, err := NewRequest("column_create", map[string]interface{}{
+	cmd, err := NewCommand("column_create", map[string]interface{}{
 		"table": tbl,
 		"name":  name,
-	}, nil)
+	})
 	if err != nil {
 		return false, nil, err
 	}
@@ -50,18 +52,18 @@ func (db *DB) ColumnCreate(tbl, name, typ, flags string) (bool, Response, error)
 	if withSection {
 		flags += "|WITH_SECTION"
 	}
-	if err := req.AddParam("flags", flags); err != nil {
+	if err := cmd.SetParam("flags", flags); err != nil {
 		return false, nil, err
 	}
-	if err := req.AddParam("type", typ); err != nil {
+	if err := cmd.SetParam("type", typ); err != nil {
 		return false, nil, err
 	}
 	if src != "" {
-		if err := req.AddParam("source", src); err != nil {
+		if err := cmd.SetParam("source", src); err != nil {
 			return false, nil, err
 		}
 	}
-	resp, err := db.Query(req)
+	resp, err := db.Query(cmd)
 	if err != nil {
 		return false, nil, err
 	}
@@ -72,7 +74,7 @@ func (db *DB) ColumnCreate(tbl, name, typ, flags string) (bool, Response, error)
 	}
 	var result bool
 	if err := json.Unmarshal(jsonData, &result); err != nil {
-		return false, resp, NewError(StatusInvalidResponse, map[string]interface{}{
+		return false, resp, NewError(InvalidResponse, map[string]interface{}{
 			"method": "json.Unmarshal",
 			"error":  err.Error(),
 		})
@@ -82,14 +84,14 @@ func (db *DB) ColumnCreate(tbl, name, typ, flags string) (bool, Response, error)
 
 // ColumnRemove executes column_remove.
 func (db *DB) ColumnRemove(tbl, name string) (bool, Response, error) {
-	req, err := NewRequest("column_remove", map[string]interface{}{
+	cmd, err := NewCommand("column_remove", map[string]interface{}{
 		"table": tbl,
 		"name":  name,
-	}, nil)
+	})
 	if err != nil {
 		return false, nil, err
 	}
-	resp, err := db.Query(req)
+	resp, err := db.Query(cmd)
 	if err != nil {
 		return false, nil, err
 	}
@@ -100,7 +102,7 @@ func (db *DB) ColumnRemove(tbl, name string) (bool, Response, error) {
 	}
 	var result bool
 	if err := json.Unmarshal(jsonData, &result); err != nil {
-		return false, resp, NewError(StatusInvalidResponse, map[string]interface{}{
+		return false, resp, NewError(InvalidResponse, map[string]interface{}{
 			"method": "json.Unmarshal",
 			"error":  err.Error(),
 		})
@@ -108,30 +110,30 @@ func (db *DB) ColumnRemove(tbl, name string) (bool, Response, error) {
 	return result, resp, nil
 }
 
-// DumpOptions stores options for DB.Dump.
-type DumpOptions struct {
+// DBDumpOptions stores options for DB.Dump.
+type DBDumpOptions struct {
 	Tables      string // --table
-	DumpPlugins string // --dump_plugins
-	DumpSchema  string // --dump_schema
-	DumpRecords string // --dump_records
-	DumpIndexes string // --dump_indexes
+	DumpPlugins bool   // --dump_plugins
+	DumpSchema  bool   // --dump_schema
+	DumpRecords bool   // --dump_records
+	DumpIndexes bool   // --dump_indexes
 }
 
-// NewDumpOptions returns the default DumpOptions.
-func NewDumpOptions() *DumpOptions {
-	return &DumpOptions{
-		DumpPlugins: "yes",
-		DumpSchema:  "yes",
-		DumpRecords: "yes",
-		DumpIndexes: "yes",
+// NewDBDumpOptions returns the default DBDumpOptions.
+func NewDBDumpOptions() *DBDumpOptions {
+	return &DBDumpOptions{
+		DumpPlugins: true,
+		DumpSchema:  true,
+		DumpRecords: true,
+		DumpIndexes: true,
 	}
 }
 
 // Dump executes dump.
 // On success, it is the caller's responsibility to close the response.
-func (db *DB) Dump(options *DumpOptions) (Response, error) {
+func (db *DB) Dump(options *DBDumpOptions) (Response, error) {
 	if options == nil {
-		options = NewDumpOptions()
+		options = NewDBDumpOptions()
 	}
 	params := map[string]interface{}{
 		"dump_plugins": options.DumpPlugins,
@@ -142,38 +144,41 @@ func (db *DB) Dump(options *DumpOptions) (Response, error) {
 	if options.Tables != "" {
 		params["tables"] = options.Tables
 	}
-	req, err := NewRequest("dump", params, nil)
-	if err != nil {
-		return nil, err
-	}
-	return db.Query(req)
+	return db.Invoke("dump", params, nil)
 }
 
-// LoadOptions stores options for DB.Load.
+// DBLoadOptions stores options for DB.Load.
 // http://groonga.org/docs/reference/commands/load.html
-type LoadOptions struct {
-	Columns  string // --columns
-	IfExists string // --ifexists
+type DBLoadOptions struct {
+	Columns  []string // --columns
+	IfExists string   // --ifexists
+}
+
+// NewDBLoadOptions returns the default DBLoadOptions.
+func NewDBLoadOptions() *DBLoadOptions {
+	return &DBLoadOptions{}
 }
 
 // Load executes load.
-func (db *DB) Load(tbl string, values io.Reader, options *LoadOptions) (int, Response, error) {
+func (db *DB) Load(tbl string, values io.Reader, options *DBLoadOptions) (int, Response, error) {
 	params := map[string]interface{}{
 		"table": tbl,
 	}
-	if options != nil {
-		if options.Columns != "" {
-			params["columns"] = options.Columns
-		}
-		if options.IfExists != "" {
-			params["ifexists"] = options.IfExists
-		}
+	if options == nil {
+		options = NewDBLoadOptions()
 	}
-	req, err := NewRequest("load", params, values)
+	if options.Columns != nil {
+		params["columns"] = options.Columns
+	}
+	if options.IfExists != "" {
+		params["ifexists"] = options.IfExists
+	}
+	cmd, err := NewCommand("load", params)
 	if err != nil {
 		return 0, nil, err
 	}
-	resp, err := db.Query(req)
+	cmd.SetBody(values)
+	resp, err := db.Query(cmd)
 	if err != nil {
 		return 0, nil, err
 	}
@@ -184,7 +189,7 @@ func (db *DB) Load(tbl string, values io.Reader, options *LoadOptions) (int, Res
 	}
 	var result int
 	if err := json.Unmarshal(jsonData, &result); err != nil {
-		return 0, resp, NewError(StatusInvalidResponse, map[string]interface{}{
+		return 0, resp, NewError(InvalidResponse, map[string]interface{}{
 			"method": "json.Unmarshal",
 			"error":  err.Error(),
 		})
@@ -192,99 +197,236 @@ func (db *DB) Load(tbl string, values io.Reader, options *LoadOptions) (int, Res
 	return result, resp, nil
 }
 
-// For --columns[NAME].stage, type, value.
-// type SelectOptionsColumn struct {
-// 	Stage string // --columns[NAME].stage
-// 	Type  string // --columns[NAME].type
-// 	Value string // --columns[NAME].value
-// }
+// encodeRow encodes a row.
+func (db *DB) encodeRow(body []byte, row reflect.Value, fis []*StructFieldInfo) []byte {
+	// TODO
+	return body
+}
+
+// encodeRows encodes rows.
+func (db *DB) encodeRows(rows reflect.Value, fis []*StructFieldInfo) ([]byte, error) {
+	body := []byte("[")
+	for rows.Kind() == reflect.Ptr {
+		rows = rows.Elem()
+	}
+	switch rows.Kind() {
+	case reflect.Array, reflect.Slice:
+		for i := 0; i < rows.Len(); i++ {
+			row := rows.Index(i)
+			for row.Kind() == reflect.Ptr {
+				row = row.Elem()
+			}
+			body = db.encodeRow(body, row, fis)
+		}
+	case reflect.Struct:
+	}
+	body = append(body, ']')
+	return body, nil
+}
+
+// LoadRows executes load.
+func (db *DB) LoadRows(tbl string, rows interface{}, options *DBLoadOptions) (int, Response, error) {
+	if options == nil {
+		options = NewDBLoadOptions()
+	}
+	si, err := GetStructInfo(rows)
+	if err != nil {
+		return 0, nil, err
+	}
+	var fis []*StructFieldInfo
+	if options.Columns == nil {
+		fis = si.Fields
+		for _, fi := range fis {
+			options.Columns = append(options.Columns, fi.ColumnName)
+		}
+	} else {
+		for _, col := range options.Columns {
+			fi, ok := si.FieldsByColumnName[col]
+			if !ok {
+				return 0, nil, NewError(InvalidCommand, map[string]interface{}{
+					"error": "",
+				})
+			}
+			fis = append(fis, fi)
+		}
+	}
+	body, err := db.encodeRows(reflect.ValueOf(rows), fis)
+	if err != nil {
+		return 0, nil, err
+	}
+	return db.Load(tbl, bytes.NewReader(body), options)
+}
 
-// For --drilldowns[LABEL].columns[NAME].
-// type SelectOptionsDorilldownColumn struct {
-// 	Stage           string // --drilldowns[LABEL].columns[NAME].stage
-// 	Flags           string // --drilldowns[LABEL].columns[NAME].flags
-// 	Type            string // --drilldowns[LABEL].columns[NAME].type
-// 	Value           string // --drilldowns[LABEL].columns[NAME].value
-// 	WindowSortKeys  string // --drilldowns[LABEL].columns[NAME].window.sort_keys
-// 	WindowGroupKeys string // --drilldowns[LABEL].columns[NAME].window.group_keys
-// }
+// DBSelectOptionsColumn stores --columns[NAME].
+type DBSelectOptionsColumn struct {
+	Stage string // --columns[NAME].stage
+	Type  string // --columns[NAME].type
+	Value string // --columns[NAME].value
+}
 
-// For --drilldowns[LABEL].keys, sort_keys, output_columns, offset, limit, calc_types, calc_target, filter, columns[].
-// type SelectOptionsDrilldown struct {
-// 	Keys          string // --drilldowns[LABEL].keys
-// 	SortKeys      string // --drilldowns[LABEL].sort_keys
-// 	OutputColumns string // --drilldowns[LABEL].output_columns
-// 	Offset        int    // --drilldowns[LABEL].offset
-// 	Limit         int    // --drilldowns[LABEL].limit
-// 	CalcTypes     string // --drilldowns[LABEL].calc_types
-// 	CalcTarget    string // --drilldowns[LABEL].calc_target
-// 	Filter        string // --drilldowns[LABEL].filter
-// 	Columns       map[string]*SelectOptionsDorilldownColumn
-// }
+// NewDBSelectOptionsColumn returns the default DBSelectOptionsColumn.
+func NewDBSelectOptionsColumn() *DBSelectOptionsColumn {
+	return &DBSelectOptionsColumn{}
+}
 
-// NewSelectOptionsDrilldown returns the default SelectOptionsDrilldown.
-// func NewSelectOptionsDrilldown() *SelectOptionsDrilldown {
-// 	return &SelectOptionsDrilldown{
-// 		Limit: 10,
-// 	}
-// }
+// DBSelectOptionsDrilldownColumn stores --drilldowns[LABEL].columns[NAME].
+type DBSelectOptionsDrilldownColumn struct {
+	Stage           string   // --drilldowns[LABEL].columns[NAME].stage
+	Flags           string   // --drilldowns[LABEL].columns[NAME].flags
+	Type            string   // --drilldowns[LABEL].columns[NAME].type
+	Value           string   // --drilldowns[LABEL].columns[NAME].value
+	WindowSortKeys  []string // --drilldowns[LABEL].columns[NAME].window.sort_keys
+	WindowGroupKeys []string // --drilldowns[LABEL].columns[NAME].window.group_keys
+}
 
-// SelectOptions stores options for DB.Select.
+// NewDBSelectOptionsDrilldownColumn returns the default DBSelectOptionsDrilldownColumn.
+func NewDBSelectOptionsDrilldownColumn() *DBSelectOptionsDrilldownColumn {
+	return &DBSelectOptionsDrilldownColumn{}
+}
+
+// DBSelectOptionsDrilldown stores --drilldowns[LABEL].
+type DBSelectOptionsDrilldown struct {
+	Keys          []string // --drilldowns[LABEL].keys
+	SortKeys      []string // --drilldowns[LABEL].sort_keys
+	OutputColumns []string // --drilldowns[LABEL].output_columns
+	Offset        int      // --drilldowns[LABEL].offset
+	Limit         int      // --drilldowns[LABEL].limit
+	CalcTypes     []string // --drilldowns[LABEL].calc_types
+	CalcTarget    string   // --drilldowns[LABEL].calc_target
+	Filter        string   // --drilldowns[LABEL].filter
+	Columns       map[string]*DBSelectOptionsDrilldownColumn
+}
+
+// NewDBSelectOptionsDrilldown returns the default DBSelectOptionsDrilldown.
+func NewDBSelectOptionsDrilldown() *DBSelectOptionsDrilldown {
+	return &DBSelectOptionsDrilldown{
+		Limit: 10,
+	}
+}
+
+// DBSelectOptions stores options for DB.Select.
 // http://groonga.org/docs/reference/commands/select.html
-type SelectOptions struct {
-	MatchColumns             string // --match_columns
-	Query                    string // --query
-	Filter                   string // --filter
-	Scorer                   string // --scorer
-	SortKeys                 string // --sort_keys
-	OutputColumns            string // --output_columns
-	Offset                   int    // --offset
-	Limit                    int    // --limit
-	Drilldown                string // --drilldown
-	DrilldownSortKeys        string // --drilldown_sort_keys
-	DrilldownOutputColumns   string // --drilldown_output_columns
-	DrillDownOffset          int    // drilldown_offset
-	DrillDownLimit           int    // drilldown_limit
-	Cache                    bool   // --cache
-	MatchEscalationThreshold int    // --match_escalation_threshold
-	QueryExpansion           string // --query_expansion
-	QueryFlags               string // --query_flags
-	QueryExpander            string // --query_expander
-	Adjuster                 string // --adjuster
-	DrilldownCalcTypes       string // --drilldown_calc_types
-	DrilldownCalcTarget      string // --drilldown_calc_target
-	DrilldownFilter          string // --drilldown_filter
-	// Columns    map[string]*SelectOptionsColumn    // --columns[NAME]
-	// Drilldowns map[string]*SelectOptionsDrilldown // --drilldowns[LABEL]
+type DBSelectOptions struct {
+	MatchColumns             []string // --match_columns
+	Query                    string   // --query
+	Filter                   string   // --filter
+	Scorer                   string   // --scorer
+	SortKeys                 []string // --sort_keys
+	OutputColumns            []string // --output_columns
+	Offset                   int      // --offset
+	Limit                    int      // --limit
+	Drilldown                string   // --drilldown
+	DrilldownSortKeys        []string // --drilldown_sort_keys
+	DrilldownOutputColumns   []string // --drilldown_output_columns
+	DrilldownOffset          int      // --drilldown_offset
+	DrilldownLimit           int      // --drilldown_limit
+	Cache                    bool     // --cache
+	MatchEscalationThreshold int      // --match_escalation_threshold
+	QueryExpansion           string   // --query_expansion
+	QueryFlags               []string // --query_flags
+	QueryExpander            string   // --query_expander
+	Adjuster                 string   // --adjuster
+	DrilldownCalcTypes       []string // --drilldown_calc_types
+	DrilldownCalcTarget      string   // --drilldown_calc_target
+	DrilldownFilter          string   // --drilldown_filter
+	Columns                  map[string]*DBSelectOptionsColumn
+	Drilldowns               map[string]*DBSelectOptionsDrilldown
 }
 
-// NewSelectOptions returns the default SelectOptions.
-func NewSelectOptions() *SelectOptions {
-	return &SelectOptions{
+// NewDBSelectOptions returns the default DBSelectOptions.
+func NewDBSelectOptions() *DBSelectOptions {
+	return &DBSelectOptions{
 		Limit:          10,
-		DrillDownLimit: 10,
+		DrilldownLimit: 10,
 	}
 }
 
 // Select executes select.
 // On success, it is the caller's responsibility to close the response.
-func (db *DB) Select(tbl string, options *SelectOptions) (Response, error) {
+func (db *DB) Select(tbl string, options *DBSelectOptions) (Response, error) {
 	if options == nil {
-		options = NewSelectOptions()
+		options = NewDBSelectOptions()
 	}
 	params := map[string]interface{}{
 		"table": tbl,
 	}
-	// TODO: copy entries from options to params.
-	req, err := NewRequest("dump", params, nil)
-	if err != nil {
-		return nil, err
+	if options.MatchColumns != nil {
+		params["match_columns"] = options.MatchColumns
+	}
+	if options.Query != "" {
+		params["query"] = options.Query
+	}
+	if options.Filter != "" {
+		params["filter"] = options.Filter
+	}
+	if options.Scorer != "" {
+		params["scorer"] = options.Scorer
+	}
+	if options.SortKeys != nil {
+		params["sort_keys"] = options.SortKeys
+	}
+	if options.OutputColumns != nil {
+		params["query"] = options.Query
+	}
+	if options.Offset != 0 {
+		params["offset"] = options.Offset
+	}
+	if options.Limit != 10 {
+		params["limit"] = options.Limit
+	}
+	if options.Drilldown != "" {
+		params["drilldown"] = options.Drilldown
+	}
+	if options.DrilldownSortKeys != nil {
+		params["drilldown_sort_keys"] = options.DrilldownSortKeys
+	}
+	if options.DrilldownOutputColumns != nil {
+		params["drilldown_output_columns"] = options.DrilldownOutputColumns
+	}
+	if options.DrilldownOffset != 0 {
+		params["drilldown_offset"] = options.DrilldownOffset
 	}
-	return db.Query(req)
+	if options.DrilldownLimit != 10 {
+		params["drilldown_limit"] = options.DrilldownLimit
+	}
+	if !options.Cache {
+		params["cache"] = options.Cache
+	}
+	if options.MatchEscalationThreshold != 0 {
+		params["match_escalation_threshold"] = options.MatchEscalationThreshold
+	}
+	if options.QueryExpansion != "" {
+		params["query_expansion"] = options.QueryExpansion
+	}
+	if options.QueryFlags != nil {
+		params["query_flags"] = options.QueryFlags
+	}
+	if options.QueryExpander != "" {
+		params["query_expander"] = options.QueryExpander
+	}
+	if options.Adjuster != "" {
+		params["adjuster"] = options.Adjuster
+	}
+	if options.DrilldownCalcTypes != nil {
+		params["drilldown_calc_types"] = options.DrilldownCalcTypes
+	}
+	if options.DrilldownCalcTarget != "" {
+		params["drilldown_calc_target"] = options.DrilldownCalcTarget
+	}
+	if options.DrilldownFilter != "" {
+		params["drilldown_filter"] = options.DrilldownFilter
+	}
+	return db.Invoke("select", params, nil)
+}
+
+// SelectRows executes select.
+func (db *DB) SelectRows(tbl string, rows interface{}, options *DBSelectOptions) (Response, error) {
+	// TODO
+	return nil, nil
 }
 
-// StatusResult is a response of status.
-type StatusResult struct {
+// DBStatusResult is a response of status.
+type DBStatusResult struct {
 	AllocCount            int           `json:"alloc_count"`
 	CacheHitRate          float64       `json:"cache_hit_rate"`
 	CommandVersion        int           `json:"command_version"`
@@ -297,7 +439,7 @@ type StatusResult struct {
 }
 
 // Status executes status.
-func (db *DB) Status() (*StatusResult, Response, error) {
+func (db *DB) Status() (*DBStatusResult, Response, error) {
 	resp, err := db.Exec("status", nil)
 	if err != nil {
 		return nil, nil, err
@@ -309,12 +451,12 @@ func (db *DB) Status() (*StatusResult, Response, error) {
 	}
 	var data map[string]interface{}
 	if err := json.Unmarshal(jsonData, &data); err != nil {
-		return nil, resp, NewError(StatusInvalidResponse, map[string]interface{}{
+		return nil, resp, NewError(InvalidResponse, map[string]interface{}{
 			"method": "json.Unmarshal",
 			"error":  err.Error(),
 		})
 	}
-	var result StatusResult
+	var result DBStatusResult
 	if v, ok := data["alloc_count"]; ok {
 		if v, ok := v.(float64); ok {
 			result.AllocCount = int(v)
@@ -363,48 +505,67 @@ func (db *DB) Status() (*StatusResult, Response, error) {
 	return &result, resp, nil
 }
 
-// TableCreateOptions stores options for DB.TableCreate.
+// DBTableCreateOptions stores options for DB.TableCreate.
 // http://groonga.org/docs/reference/commands/table_create.html
-type TableCreateOptions struct {
-	Flags            string // --flags
-	KeyType          string // --key_type
-	ValueType        string // --value_type
-	DefaultTokenizer string // --default_tokenizer
-	Normalizer       string // --normalizer
-	TokenFilters     string // --token_filters
+type DBTableCreateOptions struct {
+	Flags            []string // --flags
+	KeyType          string   // --key_type
+	ValueType        string   // --value_type
+	DefaultTokenizer string   // --default_tokenizer
+	Normalizer       string   // --normalizer
+	TokenFilters     []string // --token_filters
+}
+
+// NewDBTableCreateOptions returns the default DBTableCreateOptions.
+func NewDBTableCreateOptions() *DBTableCreateOptions {
+	return &DBTableCreateOptions{}
 }
 
 // TableCreate executes table_create.
-func (db *DB) TableCreate(name string, options *TableCreateOptions) (bool, Response, error) {
+func (db *DB) TableCreate(name string, options *DBTableCreateOptions) (bool, Response, error) {
 	if options == nil {
-		options = &TableCreateOptions{}
+		options = NewDBTableCreateOptions()
 	}
 	params := map[string]interface{}{
 		"name": name,
 	}
-	flags, keyFlag := "", ""
-	if options.Flags != "" {
-		for _, flag := range strings.Split(options.Flags, "|") {
+	flags := options.Flags
+	var keyFlag string
+	if options.Flags != nil {
+		for _, flag := range flags {
 			switch flag {
 			case "TABLE_NO_KEY":
 				if keyFlag != "" {
-					return false, nil, fmt.Errorf("TABLE_NO_KEY must not be set with %s", keyFlag)
+					return false, nil, NewError(InvalidCommand, map[string]interface{}{
+						"flags": flags,
+						"error": "The combination of flags is wrong.",
+					})
 				}
 				if options.KeyType != "" {
-					return false, nil, fmt.Errorf("TABLE_NO_KEY disallows KeyType")
+					return false, nil, NewError(InvalidCommand, map[string]interface{}{
+						"flags":    flags,
+						"key_type": options.KeyType,
+						"error":    "TABLE_NO_KEY denies key_type.",
+					})
 				}
 				keyFlag = flag
 			case "TABLE_HASH_KEY", "TABLE_PAT_KEY", "TABLE_DAT_KEY":
 				if keyFlag != "" {
-					return false, nil, fmt.Errorf("%s must not be set with %s", flag, keyFlag)
+					return false, nil, NewError(InvalidCommand, map[string]interface{}{
+						"flags": flags,
+						"error": "The combination of flags is wrong.",
+					})
 				}
 				if options.KeyType == "" {
-					return false, nil, fmt.Errorf("%s requires KeyType", flag)
+					return false, nil, NewError(InvalidCommand, map[string]interface{}{
+						"flags":    flags,
+						"key_type": options.KeyType,
+						"error":    fmt.Sprintf("%s requires key_type.", flag),
+					})
 				}
 				keyFlag = flag
 			}
 		}
-		flags = options.Flags
 	}
 	if keyFlag == "" {
 		if options.KeyType == "" {
@@ -412,15 +573,11 @@ func (db *DB) TableCreate(name string, options *TableCreateOptions) (bool, Respo
 		} else {
 			keyFlag = "TABLE_HASH_KEY"
 		}
-		if flags == "" {
-			flags = keyFlag
-		} else {
-			flags += "|" + keyFlag
+		if len(flags) == 0 {
+			flags = append(flags, keyFlag)
 		}
 	}
-	if flags != "" {
-		params["flags"] = flags
-	}
+	params["flags"] = flags
 	if options.KeyType != "" {
 		params["key_type"] = options.KeyType
 	}
@@ -433,7 +590,7 @@ func (db *DB) TableCreate(name string, options *TableCreateOptions) (bool, Respo
 	if options.Normalizer != "" {
 		params["normalizer"] = options.Normalizer
 	}
-	if options.TokenFilters != "" {
+	if options.TokenFilters != nil {
 		params["token_filters"] = options.TokenFilters
 	}
 	resp, err := db.Invoke("table_create", params, nil)
@@ -447,7 +604,7 @@ func (db *DB) TableCreate(name string, options *TableCreateOptions) (bool, Respo
 	}
 	var result bool
 	if err := json.Unmarshal(jsonData, &result); err != nil {
-		return false, resp, NewError(StatusInvalidResponse, map[string]interface{}{
+		return false, resp, NewError(InvalidResponse, map[string]interface{}{
 			"method": "json.Unmarshal",
 			"error":  err.Error(),
 		})
@@ -457,14 +614,14 @@ func (db *DB) TableCreate(name string, options *TableCreateOptions) (bool, Respo
 
 // TableRemove executes table_remove.
 func (db *DB) TableRemove(name string, dependent bool) (bool, Response, error) {
-	req, err := NewRequest("table_remove", map[string]interface{}{
+	cmd, err := NewCommand("table_remove", map[string]interface{}{
 		"name":      name,
 		"dependent": dependent,
-	}, nil)
+	})
 	if err != nil {
 		return false, nil, err
 	}
-	resp, err := db.Query(req)
+	resp, err := db.Query(cmd)
 	if err != nil {
 		return false, nil, err
 	}
@@ -475,7 +632,7 @@ func (db *DB) TableRemove(name string, dependent bool) (bool, Response, error) {
 	}
 	var result bool
 	if err := json.Unmarshal(jsonData, &result); err != nil {
-		return false, resp, NewError(StatusInvalidResponse, map[string]interface{}{
+		return false, resp, NewError(InvalidResponse, map[string]interface{}{
 			"method": "json.Unmarshal",
 			"error":  err.Error(),
 		})
@@ -498,7 +655,7 @@ func (db *DB) Truncate(target string) (bool, Response, error) {
 	}
 	var result bool
 	if err := json.Unmarshal(jsonData, &result); err != nil {
-		return false, resp, NewError(StatusInvalidResponse, map[string]interface{}{
+		return false, resp, NewError(InvalidResponse, map[string]interface{}{
 			"method": "json.Unmarshal",
 			"error":  err.Error(),
 		})

  Modified: v2/db_test.go (+67 -0)
===================================================================
--- v2/db_test.go    2017-06-14 10:41:58 +0900 (ce22d55)
+++ v2/db_test.go    2017-06-16 10:52:57 +0900 (9ac8a08)
@@ -1,7 +1,9 @@
 package grnci
 
 import (
+	"io/ioutil"
 	"log"
+	"strings"
 	"testing"
 )
 
@@ -24,6 +26,71 @@ func TestDBColumnRemove(t *testing.T) {
 	}
 }
 
+func TestDBDump(t *testing.T) {
+	client, err := NewHTTPClient("", nil)
+	if err != nil {
+		t.Skipf("NewHTTPClient failed: %v", err)
+	}
+	db := NewDB(client)
+	defer db.Close()
+
+	resp, err := db.Dump(nil)
+	if err != nil {
+		t.Fatalf("db.Dump failed: %v", err)
+	}
+	result, err := ioutil.ReadAll(resp)
+	if err != nil {
+		t.Fatalf("ioutil.ReadAll failed: %v", err)
+	}
+	log.Printf("result = %s", result)
+	log.Printf("resp = %#v", resp)
+	if err := resp.Err(); err != nil {
+		log.Printf("error = %#v", err)
+	}
+}
+
+func TestDBLoad(t *testing.T) {
+	client, err := NewHTTPClient("", nil)
+	if err != nil {
+		t.Skipf("NewHTTPClient failed: %v", err)
+	}
+	db := NewDB(client)
+	defer db.Close()
+
+	result, resp, err := db.Load("Tbl", strings.NewReader("[]"), nil)
+	if err != nil {
+		t.Fatalf("db.Dump failed: %v", err)
+	}
+	log.Printf("result = %d", result)
+	log.Printf("resp = %#v", resp)
+	if err := resp.Err(); err != nil {
+		log.Printf("error = %#v", err)
+	}
+}
+
+func TestDBSelect(t *testing.T) {
+	client, err := NewHTTPClient("", nil)
+	if err != nil {
+		t.Skipf("NewHTTPClient failed: %v", err)
+	}
+	db := NewDB(client)
+	defer db.Close()
+
+	resp, err := db.Select("Tbl", nil)
+	if err != nil {
+		t.Fatalf("db.Select failed: %v", err)
+	}
+	result, err := ioutil.ReadAll(resp)
+	if err != nil {
+		t.Fatalf("ioutil.ReadAll failed: %v", err)
+	}
+	log.Printf("result = %s", result)
+	log.Printf("resp = %#v", resp)
+	if err := resp.Err(); err != nil {
+		log.Printf("error = %#v", err)
+	}
+}
+
 func TestDBStatus(t *testing.T) {
 	client, err := NewHTTPClient("", nil)
 	if err != nil {

  Modified: v2/error.go (+26 -22)
===================================================================
--- v2/error.go    2017-06-14 10:41:58 +0900 (2f50085)
+++ v2/error.go    2017-06-16 10:52:57 +0900 (fae8b6a)
@@ -5,23 +5,22 @@ import (
 	"net/http"
 )
 
-// Error status codes.
+// Error codes.
 const (
-	StatusInvalidAddress = 1000 + iota
-	StatusInvalidCommand
-	StatusInvalidOperation
-	StatusInvalidResponse
-	StatusNetworkError
-	StatusUnknownError
+	InvalidAddress = 1000 + iota
+	InvalidCommand
+	InvalidOperation
+	InvalidResponse
+	InvalidType
+	NetworkError
+	UnknownError
 )
 
-// StatusText returns a status text.
-func StatusText(status int) string {
-	text := http.StatusText(status)
-	if text != "" {
-		return text
-	}
-	switch status {
+// getCodeText returns a string that briefly describes the specified code.
+// getCodeText supports Groonga return codes (C.grn_rc) [,0],
+// Grnci error codes [1000,] and HTTP status codes [100,999].
+func getCodeText(code int) string {
+	switch code {
 	case 0:
 		return "GRN_SUCCESS"
 	case 1:
@@ -185,20 +184,25 @@ func StatusText(status int) string {
 	case -79:
 		return "GRN_ZSTD_ERROR"
 
-	case StatusInvalidAddress:
+	case InvalidAddress:
 		return "invalid address"
-	case StatusInvalidCommand:
+	case InvalidCommand:
 		return "invalid command"
-	case StatusInvalidOperation:
+	case InvalidOperation:
 		return "invalid operation"
-	case StatusInvalidResponse:
+	case InvalidResponse:
 		return "invalid response"
-	case StatusNetworkError:
+	case InvalidType:
+		return "invalid type"
+	case NetworkError:
 		return "network error"
-	case StatusUnknownError:
+	case UnknownError:
 		return "unknown error"
 
 	default:
+		if text := http.StatusText(code); text != "" {
+			return text
+		}
 		return "undefined error"
 	}
 }
@@ -214,7 +218,7 @@ type Error struct {
 func NewError(code int, data map[string]interface{}) *Error {
 	return &Error{
 		Code: code,
-		Text: StatusText(code),
+		Text: getCodeText(code),
 		Data: data,
 	}
 }
@@ -239,7 +243,7 @@ func EnhanceError(err error, data map[string]interface{}) *Error {
 	} else if _, ok := data["error"]; !ok {
 		data["error"] = err.Error()
 	}
-	return NewError(StatusUnknownError, data)
+	return NewError(UnknownError, data)
 }
 
 // Error returns a string which describes the Error.

  Modified: v2/error_test.go (+32 -27)
===================================================================
--- v2/error_test.go    2017-06-14 10:41:58 +0900 (2ab83c5)
+++ v2/error_test.go    2017-06-16 10:52:57 +0900 (8cc4292)
@@ -3,44 +3,49 @@ package grnci
 import "testing"
 
 func TestNewError(t *testing.T) {
-	err := NewError(StatusInvalidAddress, map[string]interface{}{
-		"key": "value",
-	})
-	if err.Code != StatusInvalidAddress {
+	data := map[string]interface{}{
+		"string": "value",
+		"int":    100,
+	}
+	err := NewError(InvalidAddress, data)
+	if err.Code != InvalidAddress {
 		t.Fatalf("NewError failed: Code: actual = %d, want = %d",
-			err.Code, StatusInvalidAddress)
+			err.Code, InvalidAddress)
 	}
-	if err.Text != StatusText(StatusInvalidAddress) {
+	if err.Text != getCodeText(InvalidAddress) {
 		t.Fatalf("NewError failed: Text: actual = %s, want = %s",
-			err.Text, StatusText(StatusInvalidAddress))
+			err.Text, getCodeText(InvalidAddress))
 	}
-	if err.Data["key"] != "value" {
-		t.Fatalf("NewError failed: Data[\"key\"]: actual = %s, want = %s",
-			err.Data["key"], "value")
+	for k, v := range data {
+		if err.Data[k] != v {
+			t.Fatalf("NewError failed: Data[\"key\"]: actual = %s, want = %s", err.Data[k], v)
+		}
 	}
 }
 
 func TestEnhanceError(t *testing.T) {
-	err := NewError(StatusInvalidAddress, map[string]interface{}{
-		"key": "value",
-	})
-	err = EnhanceError(err, map[string]interface{}{
-		"newKey": "newValue",
-	})
-	if err.Code != StatusInvalidAddress {
+	data := map[string]interface{}{
+		"string": "value",
+		"int":    100,
+	}
+	newData := map[string]interface{}{
+		"string": "value2",
+		"int":    1000,
+		"float":  1.0,
+	}
+	err := NewError(InvalidAddress, data)
+	err = EnhanceError(err, newData)
+	if err.Code != InvalidAddress {
 		t.Fatalf("NewError failed: Code: actual = %d, want = %d",
-			err.Code, StatusInvalidAddress)
+			err.Code, InvalidAddress)
 	}
-	if err.Text != StatusText(StatusInvalidAddress) {
+	if err.Text != getCodeText(InvalidAddress) {
 		t.Fatalf("NewError failed: Text: actual = %s, want = %s",
-			err.Text, StatusText(StatusInvalidAddress))
-	}
-	if err.Data["key"] != "value" {
-		t.Fatalf("NewError failed: Data[\"key\"]: actual = %s, want = %s",
-			err.Data["key"], "value")
+			err.Text, getCodeText(InvalidAddress))
 	}
-	if err.Data["newKey"] != "newValue" {
-		t.Fatalf("NewError failed: Data[\"newKey\"]: actual = %s, want = %s",
-			err.Data["newKey"], "newValue")
+	for k, v := range newData {
+		if err.Data[k] != v {
+			t.Fatalf("NewError failed: Data[\"key\"]: actual = %s, want = %s", err.Data[k], v)
+		}
 	}
 }

  Modified: v2/gqtp.go (+59 -56)
===================================================================
--- v2/gqtp.go    2017-06-14 10:41:58 +0900 (c83aa83)
+++ v2/gqtp.go    2017-06-16 10:52:57 +0900 (8ad8413)
@@ -69,29 +69,12 @@ func newGQTPResponse(conn *GQTPConn, head gqtpHeader, start time.Time, name stri
 		left:    int(head.Size),
 	}
 	if head.Status > 32767 {
-		status := int(head.Status) - 65536
-		resp.err = NewError(status, nil)
-		if _, ok := CommandRules[name]; !ok {
-			data, err := ioutil.ReadAll(resp)
-			if err != nil {
-				resp.broken = true
-			} else {
-				resp.err = EnhanceError(resp.err, map[string]interface{}{
-					"error": string(data),
-				})
-			}
-		}
+		code := int(head.Status) - 65536
+		resp.err = NewError(code, nil)
 	}
 	return resp
 }
 
-func (r *gqtpResponse) Status() int {
-	if err, ok := r.err.(*Error); ok {
-		return err.Code
-	}
-	return 0
-}
-
 func (r *gqtpResponse) Start() time.Time {
 	return r.start
 }
@@ -126,7 +109,7 @@ func (r *gqtpResponse) Read(p []byte) (int, error) {
 	}
 	if err != nil {
 		r.broken = true
-		return n, NewError(StatusNetworkError, map[string]interface{}{
+		return n, NewError(NetworkError, map[string]interface{}{
 			"method": "net.Conn.Read",
 			"n":      n,
 			"error":  err.Error(),
@@ -142,7 +125,7 @@ func (r *gqtpResponse) Close() error {
 	var err error
 	if _, e := io.CopyBuffer(ioutil.Discard, r, r.conn.getBuffer()); e != nil {
 		r.broken = true
-		err = NewError(StatusNetworkError, map[string]interface{}{
+		err = NewError(NetworkError, map[string]interface{}{
 			"method": "io.CopyBuffer",
 			"error":  err.Error(),
 		})
@@ -190,7 +173,7 @@ func DialGQTP(addr string) (*GQTPConn, error) {
 	}
 	conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", a.Host, a.Port))
 	if err != nil {
-		return nil, NewError(StatusNetworkError, map[string]interface{}{
+		return nil, NewError(NetworkError, map[string]interface{}{
 			"host":  a.Host,
 			"port":  a.Port,
 			"error": err.Error(),
@@ -211,7 +194,7 @@ func NewGQTPConn(conn net.Conn) *GQTPConn {
 // Close closes the connection.
 func (c *GQTPConn) Close() error {
 	if err := c.conn.Close(); err != nil {
-		return NewError(StatusNetworkError, map[string]interface{}{
+		return NewError(NetworkError, map[string]interface{}{
 			"method": "net.Conn.Close",
 			"error":  err.Error(),
 		})
@@ -243,7 +226,7 @@ func (c *GQTPConn) sendHeader(flags byte, size int) error {
 		Size:     uint32(size),
 	}
 	if err := binary.Write(c.conn, binary.BigEndian, head); err != nil {
-		return NewError(StatusNetworkError, map[string]interface{}{
+		return NewError(NetworkError, map[string]interface{}{
 			"method": "binary.Write",
 			"error":  err.Error(),
 		})
@@ -257,7 +240,7 @@ func (c *GQTPConn) sendChunkBytes(data []byte, flags byte) error {
 		return err
 	}
 	if _, err := c.conn.Write(data); err != nil {
-		return NewError(StatusNetworkError, map[string]interface{}{
+		return NewError(NetworkError, map[string]interface{}{
 			"method": "net.Conn.Write",
 			"error":  err.Error(),
 		})
@@ -271,7 +254,7 @@ func (c *GQTPConn) sendChunkString(data string, flags byte) error {
 		return err
 	}
 	if _, err := io.WriteString(c.conn, data); err != nil {
-		return NewError(StatusNetworkError, map[string]interface{}{
+		return NewError(NetworkError, map[string]interface{}{
 			"method": "io.WriteString",
 			"error":  err.Error(),
 		})
@@ -283,7 +266,7 @@ func (c *GQTPConn) sendChunkString(data string, flags byte) error {
 func (c *GQTPConn) recvHeader() (gqtpHeader, error) {
 	var head gqtpHeader
 	if err := binary.Read(c.conn, binary.BigEndian, &head); err != nil {
-		return head, NewError(StatusNetworkError, map[string]interface{}{
+		return head, NewError(NetworkError, map[string]interface{}{
 			"method": "binary.Read",
 			"error":  err.Error(),
 		})
@@ -291,8 +274,8 @@ func (c *GQTPConn) recvHeader() (gqtpHeader, error) {
 	return head, nil
 }
 
-// exec sends a command without body and receives a response.
-func (c *GQTPConn) exec(cmd string) (Response, error) {
+// execNoBody sends a command without body and receives a response.
+func (c *GQTPConn) execNoBody(cmd string) (Response, error) {
 	start := time.Now()
 	name := strings.TrimLeft(cmd, " \t\r\n")
 	if idx := strings.IndexAny(name, " \t\r\n"); idx != -1 {
@@ -356,44 +339,54 @@ func (c *GQTPConn) execBody(cmd string, body io.Reader) (Response, error) {
 	}
 }
 
-// Exec sends a request and receives a response.
-// It is the caller's responsibility to close the response.
-// The GQTPConn should not be used until the response is closed.
-func (c *GQTPConn) Exec(cmd string, body io.Reader) (Response, error) {
+// exec sends a command without body and receives a response.
+func (c *GQTPConn) exec(cmd string, body io.Reader) (Response, error) {
 	if !c.ready {
-		return nil, NewError(StatusInvalidOperation, map[string]interface{}{
-			"error": "The connection is not ready to send a request.",
+		return nil, NewError(InvalidOperation, map[string]interface{}{
+			"error": "The connection is not ready to send a command.",
 		})
 	}
 	if len(cmd) > gqtpMaxChunkSize {
-		return nil, NewError(StatusInvalidCommand, map[string]interface{}{
+		return nil, NewError(InvalidCommand, map[string]interface{}{
 			"length": len(cmd),
 			"error":  "The command is too long.",
 		})
 	}
 	c.ready = false
 	if body == nil {
-		return c.exec(cmd)
+		return c.execNoBody(cmd)
 	}
 	return c.execBody(cmd, body)
 }
 
-// Invoke assembles cmd, params and body into a Request and calls Query.
-func (c *GQTPConn) Invoke(cmd string, params map[string]interface{}, body io.Reader) (Response, error) {
-	req, err := NewRequest(cmd, params, body)
+// Exec parses cmd, reassembles it and calls Query.
+// The GQTPConn must not be used until the response is closed.
+func (c *GQTPConn) Exec(cmd string, body io.Reader) (Response, error) {
+	command, err := ParseCommand(cmd)
 	if err != nil {
 		return nil, err
 	}
-	return c.Query(req)
+	command.SetBody(body)
+	return c.Query(command)
 }
 
-// Query calls Exec with req.GQTPRequest and returns the result.
-func (c *GQTPConn) Query(req *Request) (Response, error) {
-	cmd, body, err := req.GQTPRequest()
+// Invoke assembles name, params and body into a command and calls Query.
+func (c *GQTPConn) Invoke(name string, params map[string]interface{}, body io.Reader) (Response, error) {
+	cmd, err := NewCommand(name, params)
 	if err != nil {
 		return nil, err
 	}
-	return c.Exec(cmd, body)
+	cmd.SetBody(body)
+	return c.Query(cmd)
+}
+
+// Query sends a command and receives a response.
+// It is the caller's responsibility to close the response.
+func (c *GQTPConn) Query(cmd *Command) (Response, error) {
+	if err := cmd.Check(); err != nil {
+		return nil, err
+	}
+	return c.exec(cmd.String(), cmd.Body())
 }
 
 // GQTPClient is a thread-safe GQTP client.
@@ -438,9 +431,8 @@ func (c *GQTPClient) Close() error {
 	}
 }
 
-// Exec sends a request and receives a response.
-// It is the caller's responsibility to close the response.
-func (c *GQTPClient) Exec(cmd string, body io.Reader) (Response, error) {
+// exec sends a request and receives a response.
+func (c *GQTPClient) exec(cmd string, body io.Reader) (Response, error) {
 	var conn *GQTPConn
 	var err error
 	select {
@@ -460,20 +452,31 @@ func (c *GQTPClient) Exec(cmd string, body io.Reader) (Response, error) {
 	return resp, nil
 }
 
-// Invoke assembles cmd, params and body into a Request and calls Query.
-func (c *GQTPClient) Invoke(cmd string, params map[string]interface{}, body io.Reader) (Response, error) {
-	req, err := NewRequest(cmd, params, body)
+// Exec parses cmd, reassembles it and calls Query.
+func (c *GQTPClient) Exec(cmd string, body io.Reader) (Response, error) {
+	command, err := ParseCommand(cmd)
 	if err != nil {
 		return nil, err
 	}
-	return c.Query(req)
+	command.SetBody(body)
+	return c.Query(command)
 }
 
-// Query calls Exec with req.GQTPRequest and returns the result.
-func (c *GQTPClient) Query(req *Request) (Response, error) {
-	cmd, body, err := req.GQTPRequest()
+// Invoke assembles name, params and body into a command and calls Query.
+func (c *GQTPClient) Invoke(name string, params map[string]interface{}, body io.Reader) (Response, error) {
+	cmd, err := NewCommand(name, params)
 	if err != nil {
 		return nil, err
 	}
-	return c.Exec(cmd, body)
+	cmd.SetBody(body)
+	return c.Query(cmd)
+}
+
+// Query sends a command and receives a response.
+// It is the caller's responsibility to close the response.
+func (c *GQTPClient) Query(cmd *Command) (Response, error) {
+	if err := cmd.Check(); err != nil {
+		return nil, err
+	}
+	return c.exec(cmd.String(), cmd.Body())
 }

  Modified: v2/gqtp_test.go (+8 -4)
===================================================================
--- v2/gqtp_test.go    2017-06-14 10:41:58 +0900 (e334f64)
+++ v2/gqtp_test.go    2017-06-16 10:52:57 +0900 (a012671)
@@ -14,7 +14,7 @@ func TestGQTPConn(t *testing.T) {
 		Body    string
 	}
 	pairs := []Pair{
-		Pair{"no_such_command", ""},
+		// Pair{"no_such_command", ""},
 		Pair{"status", ""},
 		Pair{`table_create Tbl TABLE_PAT_KEY ShortText`, ""},
 		Pair{`column_create Tbl Col COLUMN_SCALAR Int32`, ""},
@@ -46,9 +46,11 @@ func TestGQTPConn(t *testing.T) {
 		if err != nil {
 			t.Fatalf("ioutil.ReadAll failed: %v", err)
 		}
-		log.Printf("status = %d, err = %v", resp.Status(), resp.Err())
 		log.Printf("start = %v, elapsed = %v", resp.Start(), resp.Elapsed())
 		log.Printf("result = %s", result)
+		if err := resp.Err(); err != nil {
+			log.Printf("err = %v", err)
+		}
 		if err := resp.Close(); err != nil {
 			t.Fatalf("resp.Close failed: %v", err)
 		}
@@ -61,7 +63,7 @@ func TestGQTPClient(t *testing.T) {
 		Body    string
 	}
 	pairs := []Pair{
-		Pair{"no_such_command", ""},
+		// Pair{"no_such_command", ""},
 		Pair{"status", ""},
 		Pair{`table_create Tbl TABLE_PAT_KEY ShortText`, ""},
 		Pair{`column_create Tbl Col COLUMN_SCALAR Int32`, ""},
@@ -93,9 +95,11 @@ func TestGQTPClient(t *testing.T) {
 		if err != nil {
 			t.Fatalf("ioutil.ReadAll failed: %v", err)
 		}
-		log.Printf("status = %d, err = %v", resp.Status(), resp.Err())
 		log.Printf("start = %v, elapsed = %v", resp.Start(), resp.Elapsed())
 		log.Printf("result = %s", result)
+		if err := resp.Err(); err != nil {
+			log.Printf("err = %v", err)
+		}
 		if err := resp.Close(); err != nil {
 			t.Fatalf("resp.Close failed: %v", err)
 		}

  Modified: v2/handler.go (+2 -2)
===================================================================
--- v2/handler.go    2017-06-14 10:41:58 +0900 (0ecc56d)
+++ v2/handler.go    2017-06-16 10:52:57 +0900 (8fcb3b2)
@@ -5,7 +5,7 @@ import "io"
 // Handler defines the required methods of DB clients and handles.
 type Handler interface {
 	Exec(cmd string, body io.Reader) (Response, error)
-	Invoke(cmd string, params map[string]interface{}, body io.Reader) (Response, error)
-	Query(req *Request) (Response, error)
+	Invoke(name string, params map[string]interface{}, body io.Reader) (Response, error)
+	Query(cmd *Command) (Response, error)
 	Close() error
 }

  Modified: v2/http.go (+41 -51)
===================================================================
--- v2/http.go    2017-06-14 10:41:58 +0900 (fd81667)
+++ v2/http.go    2017-06-16 10:52:57 +0900 (39bddd9)
@@ -31,7 +31,7 @@ type httpResponse struct {
 func extractHTTPResponseHeader(data []byte) (head, left []byte, err error) {
 	left = bytes.TrimLeft(data[1:], " \t\r\n")
 	if !bytes.HasPrefix(left, []byte("[")) {
-		err = NewError(StatusInvalidResponse, map[string]interface{}{
+		err = NewError(InvalidResponse, map[string]interface{}{
 			"error": "The response does not contain a header.",
 		})
 		return
@@ -47,7 +47,7 @@ Loop:
 			stack = append(stack, '}')
 		case ']', '}':
 			if left[i] != stack[len(stack)-1] {
-				err = NewError(StatusInvalidResponse, map[string]interface{}{
+				err = NewError(InvalidResponse, map[string]interface{}{
 					"error": "The response header is broken.",
 				})
 				return
@@ -69,7 +69,7 @@ Loop:
 		}
 	}
 	if len(stack) != 0 {
-		err = NewError(StatusInvalidResponse, map[string]interface{}{
+		err = NewError(InvalidResponse, map[string]interface{}{
 			"error": "The response header is too long or broken.",
 		})
 		return
@@ -83,8 +83,8 @@ Loop:
 }
 
 // parseHTTPResponseHeaderError parses the error information in the HTTP resonse header.
-func parseHTTPResponseHeaderError(status int, elems []interface{}) error {
-	err := NewError(status, nil)
+func parseHTTPResponseHeaderError(code int, elems []interface{}) error {
+	err := NewError(code, nil)
 	if len(elems) >= 1 {
 		err = EnhanceError(err, map[string]interface{}{
 			"message": elems[0],
@@ -131,28 +131,28 @@ func parseHTTPResponseHeader(resp *http.Response, data []byte) (*httpResponse, e
 
 	var elems []interface{}
 	if err := json.Unmarshal(head, &elems); err != nil {
-		return nil, NewError(StatusInvalidResponse, map[string]interface{}{
+		return nil, NewError(InvalidResponse, map[string]interface{}{
 			"method": "json.Unmarshal",
 			"error":  err.Error(),
 		})
 	}
 	if len(elems) < 3 {
-		return nil, NewError(StatusInvalidResponse, map[string]interface{}{
+		return nil, NewError(InvalidResponse, map[string]interface{}{
 			"method": "json.Unmarshal",
 			"error":  "Too few elements in the response header.",
 		})
 	}
 	f, ok := elems[0].(float64)
 	if !ok {
-		return nil, NewError(StatusInvalidResponse, map[string]interface{}{
-			"status": elems[0],
-			"error":  "status must be a number.",
+		return nil, NewError(InvalidResponse, map[string]interface{}{
+			"code":  elems[0],
+			"error": "code must be a number.",
 		})
 	}
-	status := int(f)
+	code := int(f)
 	f, ok = elems[1].(float64)
 	if !ok {
-		return nil, NewError(StatusInvalidResponse, map[string]interface{}{
+		return nil, NewError(InvalidResponse, map[string]interface{}{
 			"start": elems[1],
 			"error": "start must be a number.",
 		})
@@ -161,15 +161,15 @@ func parseHTTPResponseHeader(resp *http.Response, data []byte) (*httpResponse, e
 	start := time.Unix(int64(i), int64(math.Floor(f*1000000+0.5))*1000).Local()
 	f, ok = elems[2].(float64)
 	if !ok {
-		return nil, NewError(StatusInvalidResponse, map[string]interface{}{
+		return nil, NewError(InvalidResponse, map[string]interface{}{
 			"elapsed": elems[2],
 			"error":   "elapsed must be a number.",
 		})
 	}
 	elapsed := time.Duration(f * float64(time.Second))
 
-	if status != 0 {
-		err = parseHTTPResponseHeaderError(status, elems[3:])
+	if code != 0 {
+		err = parseHTTPResponseHeaderError(code, elems[3:])
 	}
 
 	return &httpResponse{
@@ -192,7 +192,7 @@ func newHTTPResponse(resp *http.Response, start time.Time) (*httpResponse, error
 			break
 		}
 		if err != nil {
-			return nil, NewError(StatusNetworkError, map[string]interface{}{
+			return nil, NewError(NetworkError, map[string]interface{}{
 				"method": "http.Response.Body.Read",
 				"error":  err.Error(),
 			})
@@ -218,13 +218,6 @@ func newHTTPResponse(resp *http.Response, start time.Time) (*httpResponse, error
 	}, nil
 }
 
-func (r *httpResponse) Status() int {
-	if err, ok := r.err.(*Error); ok {
-		return err.Code
-	}
-	return 0
-}
-
 func (r *httpResponse) Start() time.Time {
 	return r.start
 }
@@ -250,7 +243,7 @@ func (r *httpResponse) Read(p []byte) (n int, err error) {
 				n--
 			}
 			if err != io.EOF {
-				err = NewError(StatusNetworkError, map[string]interface{}{
+				err = NewError(NetworkError, map[string]interface{}{
 					"method": "http.Response.Body.Read",
 					"error":  err.Error(),
 				})
@@ -270,7 +263,7 @@ func (r *httpResponse) Read(p []byte) (n int, err error) {
 		n--
 	}
 	if err != io.EOF {
-		err = NewError(StatusNetworkError, map[string]interface{}{
+		err = NewError(NetworkError, map[string]interface{}{
 			"method": "http.Response.Body.Read",
 			"error":  err.Error(),
 		})
@@ -281,7 +274,7 @@ func (r *httpResponse) Read(p []byte) (n int, err error) {
 func (r *httpResponse) Close() error {
 	io.Copy(ioutil.Discard, r.resp.Body)
 	if err := r.resp.Body.Close(); err != nil {
-		return NewError(StatusNetworkError, map[string]interface{}{
+		return NewError(NetworkError, map[string]interface{}{
 			"method": "http.Response.Body.Close",
 			"error":  err.Error(),
 		})
@@ -310,7 +303,7 @@ func NewHTTPClient(addr string, client *http.Client) (*HTTPClient, error) {
 	}
 	url, err := url.Parse(a.String())
 	if err != nil {
-		return nil, NewError(StatusInvalidAddress, map[string]interface{}{
+		return nil, NewError(InvalidAddress, map[string]interface{}{
 			"url":    a.String(),
 			"method": "url.Parse",
 			"error":  err.Error(),
@@ -330,10 +323,11 @@ func (c *HTTPClient) Close() error {
 	return nil
 }
 
-// exec sends a request and receives a response.
-func (c *HTTPClient) exec(cmd string, params map[string]string, body io.Reader) (*http.Response, error) {
+// exec sends a command and receives a response.
+func (c *HTTPClient) exec(name string, params map[string]string, body io.Reader) (Response, error) {
+	start := time.Now()
 	url := *c.url
-	url.Path = path.Join(url.Path, cmd)
+	url.Path = path.Join(url.Path, name)
 	if len(params) != 0 {
 		query := url.Query()
 		for k, v := range params {
@@ -344,54 +338,50 @@ func (c *HTTPClient) exec(cmd string, params map[string]string, body io.Reader)
 	if body == nil {
 		resp, err := c.client.Get(url.String())
 		if err != nil {
-			return nil, NewError(StatusNetworkError, map[string]interface{}{
+			return nil, NewError(NetworkError, map[string]interface{}{
 				"url":    url.String(),
 				"method": "http.Client.Get",
 				"error":  err.Error(),
 			})
 		}
-		return resp, nil
+		return newHTTPResponse(resp, start)
 	}
 	resp, err := c.client.Post(url.String(), "application/json", body)
 	if err != nil {
-		return nil, NewError(StatusNetworkError, map[string]interface{}{
+		return nil, NewError(NetworkError, map[string]interface{}{
 			"url":    url.String(),
 			"method": "http.Client.Post",
 			"error":  err.Error(),
 		})
 	}
-	return resp, nil
+	return newHTTPResponse(resp, start)
 }
 
-// Exec assembles cmd and body into a Request and calls Query.
+// Exec assembles cmd and body into a Command and calls Query.
 func (c *HTTPClient) Exec(cmd string, body io.Reader) (Response, error) {
-	req, err := ParseRequest(cmd, body)
+	command, err := ParseCommand(cmd)
 	if err != nil {
 		return nil, err
 	}
-	return c.Query(req)
+	command.SetBody(body)
+	return c.Query(command)
 }
 
-// Invoke assembles cmd, params and body into a Request and calls Query.
-func (c *HTTPClient) Invoke(cmd string, params map[string]interface{}, body io.Reader) (Response, error) {
-	req, err := NewRequest(cmd, params, body)
+// Invoke assembles name, params and body into a Command and calls Query.
+func (c *HTTPClient) Invoke(name string, params map[string]interface{}, body io.Reader) (Response, error) {
+	cmd, err := NewCommand(name, params)
 	if err != nil {
 		return nil, err
 	}
-	return c.Query(req)
+	cmd.SetBody(body)
+	return c.Query(cmd)
 }
 
-// Query sends a request and receives a response.
+// Query sends a command and receives a response.
 // It is the caller's responsibility to close the response.
-func (c *HTTPClient) Query(req *Request) (Response, error) {
-	start := time.Now()
-	cmd, params, body, err := req.HTTPRequest()
-	if err != nil {
+func (c *HTTPClient) Query(cmd *Command) (Response, error) {
+	if err := cmd.Check(); err != nil {
 		return nil, err
 	}
-	resp, err := c.exec(cmd, params, body)
-	if err != nil {
-		return nil, err
-	}
-	return newHTTPResponse(resp, start)
+	return c.exec(cmd.Name(), cmd.Params(), cmd.Body())
 }

  Modified: v2/http_test.go (+4 -2)
===================================================================
--- v2/http_test.go    2017-06-14 10:41:58 +0900 (dd016d1)
+++ v2/http_test.go    2017-06-16 10:52:57 +0900 (0cc8707)
@@ -14,7 +14,7 @@ func TestHTTPClient(t *testing.T) {
 		Body    string
 	}
 	pairs := []Pair{
-		Pair{"no_such_command", ""},
+		// Pair{"no_such_command", ""},
 		Pair{"status", ""},
 		Pair{`table_create Tbl TABLE_PAT_KEY ShortText`, ""},
 		Pair{`column_create Tbl Col COLUMN_SCALAR Int32`, ""},
@@ -46,9 +46,11 @@ func TestHTTPClient(t *testing.T) {
 		if err != nil {
 			t.Fatalf("ioutil.ReadAll failed: %v", err)
 		}
-		log.Printf("status = %d, err = %v", resp.Status(), resp.Err())
 		log.Printf("start = %v, elapsed = %v", resp.Start(), resp.Elapsed())
 		log.Printf("result = %s", result)
+		if err := resp.Err(); err != nil {
+			log.Printf("err = %v", err)
+		}
 		if err := resp.Close(); err != nil {
 			t.Fatalf("resp.Close failed: %v", err)
 		}

  Added: v2/info.go (+156 -0) 100644
===================================================================
--- /dev/null
+++ v2/info.go    2017-06-16 10:52:57 +0900 (8b8434f)
@@ -0,0 +1,156 @@
+package grnci
+
+import (
+	"fmt"
+	"reflect"
+	"strings"
+	"sync"
+	"time"
+)
+
+const (
+	// tagKey is the tag key for struct fields associated with Groonga columns.
+	tagKey = "grnci"
+
+	// tagSep is the separator in a struct field tag value.
+	tagSep = ';'
+)
+
+var (
+	structInfos      = make(map[reflect.Type]*StructInfo)
+	structInfosMutex sync.RWMutex
+)
+
+type StructFieldInfo struct {
+	Index      int                  // Field position
+	Field      *reflect.StructField // Field
+	Type       reflect.Type         // Field's underlying type
+	Tags       []string             // Field tag semicolon-separated values
+	ColumnName string               // Column name
+	Dimension  int                  // Vector dimension
+}
+
+// newStructFieldInfo returns a StructFieldInfo.
+func newStructFieldInfo(index int, field *reflect.StructField) (*StructFieldInfo, error) {
+	tagValue := field.Tag.Get(tagKey)
+	tags := strings.Split(tagValue, ";")
+	for _, tag := range tags {
+		tag = strings.TrimSpace(tag)
+	}
+	if strings.HasSuffix(tags[0], "*") {
+		return nil, NewError(InvalidType, map[string]interface{}{
+			"tag":   tagValue,
+			"error": "The first tag must not end with '*'.",
+		})
+	}
+	typ := field.Type
+	dim := 0
+	for {
+		if typ.Kind() == reflect.Ptr {
+			typ = typ.Elem()
+		} else if typ.Kind() == reflect.Slice {
+			typ = typ.Elem()
+			dim++
+		} else {
+			break
+		}
+	}
+	switch reflect.Zero(typ).Interface().(type) {
+	case bool:
+	case int, int8, int16, int32, int64:
+	case uint, uint8, uint16, uint32, uint64:
+	case float32, float64:
+	case string:
+	case time.Time:
+	case Geo:
+	default:
+		return nil, NewError(InvalidType, map[string]interface{}{
+			"type":  typ.Name(),
+			"error": "The type is not supported.",
+		})
+	}
+
+	return &StructFieldInfo{
+		Index:     index,
+		Field:     field,
+		Tags:      tags,
+		Type:      field.Type,
+		Dimension: dim,
+	}, nil
+}
+
+type StructInfo struct {
+	Type               reflect.Type
+	Fields             []*StructFieldInfo
+	FieldsByName       map[string]*StructFieldInfo
+	FieldsByColumnName map[string]*StructFieldInfo
+}
+
+// getStructInfo returns the StructInfo that represents typ.
+func getStructInfo(typ reflect.Type) (*StructInfo, error) {
+	structInfosMutex.Lock()
+	defer structInfosMutex.Unlock()
+	if si, ok := structInfos[typ]; ok {
+		return si, nil
+	}
+	fis := make([]*StructFieldInfo, 0)
+	fisByName := make(map[string]*StructFieldInfo)
+	fisByColumnName := make(map[string]*StructFieldInfo)
+	for i := 0; i < typ.NumField(); i++ {
+		f := typ.Field(i)
+		if f.PkgPath != "" { // Skip unexported fields.
+			continue
+		}
+		tag := f.Tag.Get(tagKey)
+		if tag == "" || tag == "-" { // Skip untagged fields.
+			continue
+		}
+		fi, err := newStructFieldInfo(i, &f)
+		if err != nil {
+			return nil, err
+		}
+		fis = append(fis, fi)
+		fisByName[f.Name] = fi
+		if _, ok := fisByColumnName[fi.ColumnName]; ok {
+			return nil, NewError(InvalidType, map[string]interface{}{
+				"columnName": fi.ColumnName,
+				"error":      "The column name appears more than once.",
+			})
+		}
+		fisByColumnName[fi.ColumnName] = fi
+	}
+	si := &StructInfo{
+		Type:               typ,
+		Fields:             fis,
+		FieldsByName:       fisByName,
+		FieldsByColumnName: fisByColumnName,
+	}
+	structInfos[typ] = si
+	return si, nil
+}
+
+// GetStructInfo returns the StructInfo that represents the underlying struct of i.
+// If i is nil or the underlying type is not a struct, GetStructInfo returns an error.
+func GetStructInfo(v interface{}) (*StructInfo, error) {
+	if v == nil {
+		return nil, NewError(InvalidType, map[string]interface{}{
+			"value": nil,
+			"error": "The value must not be nil.",
+		})
+	}
+	typ := reflect.TypeOf(v)
+	for {
+		switch typ.Kind() {
+		case reflect.Ptr, reflect.Slice, reflect.Array:
+			typ = typ.Elem()
+		default:
+			if kind := typ.Kind(); kind != reflect.Struct {
+				return nil, NewError(InvalidType, map[string]interface{}{
+					"kind":  kind.String(),
+					"error": fmt.Sprintf("The kind must be %s.", reflect.Struct),
+				})
+			}
+			return getStructInfo(typ)
+		}
+	}
+}

  Modified: v2/libgrn/client.go (+21 -11)
===================================================================
--- v2/libgrn/client.go    2017-06-14 10:41:58 +0900 (a873c47)
+++ v2/libgrn/client.go    2017-06-16 10:52:57 +0900 (d36bd39)
@@ -82,9 +82,8 @@ Loop:
 	return err
 }
 
-// Exec sends a request and receives a response.
-// It is the caller's responsibility to close the response.
-func (c *Client) Exec(cmd string, body io.Reader) (grnci.Response, error) {
+// exec sends a command and receives a response.
+func (c *Client) exec(cmd string, body io.Reader) (grnci.Response, error) {
 	var conn *Conn
 	var err error
 	select {
@@ -111,20 +110,31 @@ func (c *Client) Exec(cmd string, body io.Reader) (grnci.Response, error) {
 	return resp, nil
 }
 
-// Invoke assembles cmd, params and body into a grnci.Request and calls Query.
-func (c *Client) Invoke(cmd string, params map[string]interface{}, body io.Reader) (grnci.Response, error) {
-	req, err := grnci.NewRequest(cmd, params, body)
+// Exec parses cmd, reassembles it and calls Query.
+func (c *Client) Exec(cmd string, body io.Reader) (grnci.Response, error) {
+	command, err := grnci.ParseCommand(cmd)
 	if err != nil {
 		return nil, err
 	}
-	return c.Query(req)
+	command.SetBody(body)
+	return c.Query(command)
 }
 
-// Query calls Exec with req.GQTPRequest and returns the result.
-func (c *Client) Query(req *grnci.Request) (grnci.Response, error) {
-	cmd, body, err := req.GQTPRequest()
+// Invoke assembles name, params and body into a command and calls Query.
+func (c *Client) Invoke(name string, params map[string]interface{}, body io.Reader) (grnci.Response, error) {
+	cmd, err := grnci.NewCommand(name, params)
 	if err != nil {
 		return nil, err
 	}
-	return c.Exec(cmd, body)
+	cmd.SetBody(body)
+	return c.Query(cmd)
+}
+
+// Query sends a command and receives a response.
+// It is the caller's responsibility to close the response.
+func (c *Client) Query(cmd *grnci.Command) (grnci.Response, error) {
+	if err := cmd.Check(); err != nil {
+		return nil, err
+	}
+	return c.exec(cmd.String(), cmd.Body())
 }

  Modified: v2/libgrn/client_test.go (+8 -4)
===================================================================
--- v2/libgrn/client_test.go    2017-06-14 10:41:58 +0900 (20f92ef)
+++ v2/libgrn/client_test.go    2017-06-16 10:52:57 +0900 (634186f)
@@ -16,7 +16,7 @@ func TestClientGQTP(t *testing.T) {
 		Body    string
 	}
 	pairs := []Pair{
-		Pair{"no_such_command", ""},
+		// Pair{"no_such_command", ""},
 		Pair{"status", ""},
 		Pair{`table_create Tbl TABLE_PAT_KEY ShortText`, ""},
 		Pair{`column_create Tbl Col COLUMN_SCALAR Int32`, ""},
@@ -48,9 +48,11 @@ func TestClientGQTP(t *testing.T) {
 		if err != nil {
 			t.Fatalf("ioutil.ReadAll failed: %v", err)
 		}
-		log.Printf("status = %d, err = %v", resp.Status(), resp.Err())
 		log.Printf("start = %v, elapsed = %v", resp.Start(), resp.Elapsed())
 		log.Printf("result = %s", result)
+		if err := resp.Err(); err != nil {
+			log.Printf("err = %v", err)
+		}
 		if err := resp.Close(); err != nil {
 			t.Fatalf("resp.Close failed: %v", err)
 		}
@@ -63,7 +65,7 @@ func TestClientDB(t *testing.T) {
 		Body    string
 	}
 	pairs := []Pair{
-		Pair{"no_such_command", ""},
+		// Pair{"no_such_command", ""},
 		Pair{"status", ""},
 		Pair{`table_create Tbl TABLE_PAT_KEY ShortText`, ""},
 		Pair{`column_create Tbl Col COLUMN_SCALAR Int32`, ""},
@@ -95,9 +97,11 @@ func TestClientDB(t *testing.T) {
 		if err != nil {
 			t.Fatalf("ioutil.ReadAll failed: %v", err)
 		}
-		log.Printf("status = %d, err = %v", resp.Status(), resp.Err())
 		log.Printf("start = %v, elapsed = %v", resp.Start(), resp.Elapsed())
 		log.Printf("result = %s", result)
+		if err := resp.Err(); err != nil {
+			log.Printf("err = %v", err)
+		}
 		if err := resp.Close(); err != nil {
 			t.Fatalf("resp.Close failed: %v", err)
 		}

  Modified: v2/libgrn/conn.go (+35 -25)
===================================================================
--- v2/libgrn/conn.go    2017-06-14 10:41:58 +0900 (677e503)
+++ v2/libgrn/conn.go    2017-06-16 10:52:57 +0900 (872dd65)
@@ -89,7 +89,7 @@ func Create(path string) (*Conn, error) {
 // Dup duplicates the Conn if it is a DB handle.
 func (c *Conn) Dup() (*Conn, error) {
 	if c.db == nil {
-		return nil, grnci.NewError(grnci.StatusInvalidOperation, map[string]interface{}{
+		return nil, grnci.NewError(grnci.InvalidOperation, map[string]interface{}{
 			"error": "GQTP clients do not support Dup.",
 		})
 	}
@@ -132,8 +132,8 @@ func (c *Conn) getBuffer() []byte {
 	return c.buf
 }
 
-// execGQTP sends a command and receives a response.
-func (c *Conn) execGQTP(cmd string) (grnci.Response, error) {
+// execNoBodyGQTP sends a command and receives a response.
+func (c *Conn) execNoBodyGQTP(cmd string) (grnci.Response, error) {
 	start := time.Now()
 	name := strings.TrimLeft(cmd, " \t\r\n")
 	if idx := strings.IndexAny(name, " \t\r\n"); idx != -1 {
@@ -149,8 +149,8 @@ func (c *Conn) execGQTP(cmd string) (grnci.Response, error) {
 	return newGQTPResponse(c, start, name, data, flags, err), nil
 }
 
-// execDB executes a command and receives a response.
-func (c *Conn) execDB(cmd string) (grnci.Response, error) {
+// execNoBodyDB executes a command and receives a response.
+func (c *Conn) execNoBodyDB(cmd string) (grnci.Response, error) {
 	start := time.Now()
 	if err := c.ctx.Send([]byte(cmd), flagTail); err != nil {
 		data, flags, _ := c.ctx.Recv()
@@ -160,12 +160,12 @@ func (c *Conn) execDB(cmd string) (grnci.Response, error) {
 	return newDBResponse(c, start, data, flags, err), nil
 }
 
-// exec sends a command without body and receives a response.
-func (c *Conn) exec(cmd string) (grnci.Response, error) {
+// execNoBody sends a command without body and receives a response.
+func (c *Conn) execNoBody(cmd string) (grnci.Response, error) {
 	if c.db == nil {
-		return c.execGQTP(cmd)
+		return c.execNoBodyGQTP(cmd)
 	}
-	return c.execDB(cmd)
+	return c.execNoBodyDB(cmd)
 }
 
 // execBodyGQTP sends a command and receives a response.
@@ -262,42 +262,52 @@ func (c *Conn) execBody(cmd string, body io.Reader) (grnci.Response, error) {
 	return c.execBodyDB(cmd, body)
 }
 
-// Exec sends a request and receives a response.
-// It is the caller's responsibility to close the response.
-// The Conn should not be used until the response is closed.
-func (c *Conn) Exec(cmd string, body io.Reader) (grnci.Response, error) {
+// exec sends a command and receives a response.
+func (c *Conn) exec(cmd string, body io.Reader) (grnci.Response, error) {
 	if !c.ready {
-		return nil, grnci.NewError(grnci.StatusInvalidOperation, map[string]interface{}{
-			"error": "The connection is not ready to send a request.",
+		return nil, grnci.NewError(grnci.InvalidOperation, map[string]interface{}{
+			"error": "The connection is not ready to send a command.",
 		})
 	}
 	if len(cmd) > maxChunkSize {
-		return nil, grnci.NewError(grnci.StatusInvalidCommand, map[string]interface{}{
+		return nil, grnci.NewError(grnci.InvalidCommand, map[string]interface{}{
 			"length": len(cmd),
 			"error":  "The command is too long.",
 		})
 	}
 	c.ready = false
 	if body == nil {
-		return c.exec(cmd)
+		return c.execNoBody(cmd)
 	}
 	return c.execBody(cmd, body)
 }
 
-// Invoke assembles cmd, params and body into a grnci.Request and calls Query.
-func (c *Conn) Invoke(cmd string, params map[string]interface{}, body io.Reader) (grnci.Response, error) {
-	req, err := grnci.NewRequest(cmd, params, body)
+// Exec parses cmd, reassembles it and calls Query.
+// The Conn must not be used until the response is closed.
+func (c *Conn) Exec(cmd string, body io.Reader) (grnci.Response, error) {
+	command, err := grnci.ParseCommand(cmd)
 	if err != nil {
 		return nil, err
 	}
-	return c.Query(req)
+	command.SetBody(body)
+	return c.Query(command)
 }
 
-// Query calls Exec with req.GQTPRequest and returns the result.
-func (c *Conn) Query(req *grnci.Request) (grnci.Response, error) {
-	cmd, body, err := req.GQTPRequest()
+// Invoke assembles name, params and body into a command and calls Query.
+func (c *Conn) Invoke(name string, params map[string]interface{}, body io.Reader) (grnci.Response, error) {
+	cmd, err := grnci.NewCommand(name, params)
 	if err != nil {
 		return nil, err
 	}
-	return c.Exec(cmd, body)
+	cmd.SetBody(body)
+	return c.Query(cmd)
+}
+
+// Query sends a command and receives a response.
+// It is the caller's responsibility to close the response.
+func (c *Conn) Query(cmd *grnci.Command) (grnci.Response, error) {
+	if err := cmd.Check(); err != nil {
+		return nil, err
+	}
+	return c.exec(cmd.String(), cmd.Body())
 }

  Modified: v2/libgrn/conn_test.go (+8 -4)
===================================================================
--- v2/libgrn/conn_test.go    2017-06-14 10:41:58 +0900 (b9a39b9)
+++ v2/libgrn/conn_test.go    2017-06-16 10:52:57 +0900 (87a5b25)
@@ -16,7 +16,7 @@ func TestConnGQTP(t *testing.T) {
 		Body    string
 	}
 	pairs := []Pair{
-		Pair{"no_such_command", ""},
+		// Pair{"no_such_command", ""},
 		Pair{"status", ""},
 		Pair{`table_create Tbl TABLE_PAT_KEY ShortText`, ""},
 		Pair{`column_create Tbl Col COLUMN_SCALAR Int32`, ""},
@@ -48,9 +48,11 @@ func TestConnGQTP(t *testing.T) {
 		if err != nil {
 			t.Fatalf("ioutil.ReadAll failed: %v", err)
 		}
-		log.Printf("status = %d, err = %v", resp.Status(), resp.Err())
 		log.Printf("start = %v, elapsed = %v", resp.Start(), resp.Elapsed())
 		log.Printf("result = %s", result)
+		if err := resp.Err(); err != nil {
+			log.Printf("err = %v", err)
+		}
 		if err := resp.Close(); err != nil {
 			t.Fatalf("resp.Close failed: %v", err)
 		}
@@ -63,7 +65,7 @@ func TestConnDB(t *testing.T) {
 		Body    string
 	}
 	pairs := []Pair{
-		Pair{"no_such_command", ""},
+		// Pair{"no_such_command", ""},
 		Pair{"status", ""},
 		Pair{`table_create Tbl TABLE_PAT_KEY ShortText`, ""},
 		Pair{`column_create Tbl Col COLUMN_SCALAR Int32`, ""},
@@ -95,9 +97,11 @@ func TestConnDB(t *testing.T) {
 		if err != nil {
 			t.Fatalf("ioutil.ReadAll failed: %v", err)
 		}
-		log.Printf("status = %d, err = %v", resp.Status(), resp.Err())
 		log.Printf("start = %v, elapsed = %v", resp.Start(), resp.Elapsed())
 		log.Printf("result = %s", result)
+		if err := resp.Err(); err != nil {
+			log.Printf("err = %v", err)
+		}
 		if err := resp.Close(); err != nil {
 			t.Fatalf("resp.Close failed: %v", err)
 		}

  Modified: v2/libgrn/libgrn.go (+5 -5)
===================================================================
--- v2/libgrn/libgrn.go    2017-06-14 10:41:58 +0900 (07e3edf)
+++ v2/libgrn/libgrn.go    2017-06-16 10:52:57 +0900 (030983e)
@@ -63,7 +63,7 @@ func Fin() error {
 	libMutex.Lock()
 	defer libMutex.Unlock()
 	if libCount <= 0 {
-		return grnci.NewError(grnci.StatusInvalidOperation, map[string]interface{}{
+		return grnci.NewError(grnci.InvalidOperation, map[string]interface{}{
 			"libCount": libCount,
 			"error":    "libCount must be greater than 0.",
 		})
@@ -100,7 +100,7 @@ func newGrnCtx() (*grnCtx, error) {
 	ctx := C.grn_ctx_open(C.int(0))
 	if ctx == nil {
 		Fin()
-		return nil, grnci.NewError(grnci.StatusUnknownError, map[string]interface{}{
+		return nil, grnci.NewError(grnci.UnknownError, map[string]interface{}{
 			"method": "C.grn_ctx_open",
 		})
 	}
@@ -193,7 +193,7 @@ func createGrnDB(ctx *grnCtx, path string) (*grnDB, error) {
 		if err := ctx.Err("C.grn_db_create"); err != nil {
 			return nil, err
 		}
-		return nil, grnci.NewError(grnci.StatusUnknownError, map[string]interface{}{
+		return nil, grnci.NewError(grnci.UnknownError, map[string]interface{}{
 			"method": "C.grn_db_create",
 		})
 	}
@@ -212,7 +212,7 @@ func openGrnDB(ctx *grnCtx, path string) (*grnDB, error) {
 		if err := ctx.Err("C.grn_db_open"); err != nil {
 			return nil, err
 		}
-		return nil, grnci.NewError(grnci.StatusUnknownError, map[string]interface{}{
+		return nil, grnci.NewError(grnci.UnknownError, map[string]interface{}{
 			"method": "C.grn_db_open",
 		})
 	}
@@ -227,7 +227,7 @@ func (db *grnDB) Close(ctx *grnCtx) error {
 	db.mutex.Lock()
 	defer db.mutex.Unlock()
 	if db.count <= 0 {
-		return grnci.NewError(grnci.StatusInvalidOperation, map[string]interface{}{
+		return grnci.NewError(grnci.InvalidOperation, map[string]interface{}{
 			"count": db.count,
 			"error": "count must be greater than 0.",
 		})

  Modified: v2/libgrn/response.go (+2 -22)
===================================================================
--- v2/libgrn/response.go    2017-06-14 10:41:58 +0900 (f73e353)
+++ v2/libgrn/response.go    2017-06-16 10:52:57 +0900 (a3fb52e)
@@ -23,7 +23,7 @@ type response struct {
 
 // newGQTPResponse returns a new GQTP response.
 func newGQTPResponse(conn *Conn, start time.Time, name string, data []byte, flags byte, err error) *response {
-	resp := &response{
+	return &response{
 		conn:    conn,
 		start:   start,
 		elapsed: time.Now().Sub(start),
@@ -31,18 +31,6 @@ func newGQTPResponse(conn *Conn, start time.Time, name string, data []byte, flag
 		flags:   flags,
 		err:     err,
 	}
-	if resp.err != nil {
-		if _, ok := grnci.CommandRules[name]; !ok {
-			data, err := ioutil.ReadAll(resp)
-			resp.err = grnci.EnhanceError(resp.err, map[string]interface{}{
-				"error": string(data),
-			})
-			if err != nil {
-				resp.broken = true
-			}
-		}
-	}
-	return resp
 }
 
 // newDBResponse returns a new DB response.
@@ -57,14 +45,6 @@ func newDBResponse(conn *Conn, start time.Time, data []byte, flags byte, err err
 	}
 }
 
-// Status returns the status code.
-func (r *response) Status() int {
-	if err, ok := r.err.(*grnci.Error); ok {
-		return err.Code
-	}
-	return 0
-}
-
 // Start returns the start time.
 func (r *response) Start() time.Time {
 	return r.start
@@ -107,7 +87,7 @@ func (r *response) Close() error {
 	if !r.broken {
 		if _, err = io.CopyBuffer(ioutil.Discard, r, r.conn.getBuffer()); err != nil {
 			r.broken = true
-			err = grnci.NewError(grnci.StatusNetworkError, map[string]interface{}{
+			err = grnci.NewError(grnci.NetworkError, map[string]interface{}{
 				"method": "io.CopyBuffer",
 				"error":  err.Error(),
 			})

  Deleted: v2/request.go (+0 -354) 100644
===================================================================
--- v2/request.go    2017-06-14 10:41:58 +0900 (7488815)
+++ /dev/null
@@ -1,354 +0,0 @@
-package grnci
-
-import (
-	"fmt"
-	"io"
-	"reflect"
-	"sort"
-	"strconv"
-	"strings"
-)
-
-// Request is a request.
-type Request struct {
-	Command     string            // Command name
-	CommandRule *CommandRule      // Command rule
-	Params      map[string]string // Command parameters
-	NAnonParams int               // Number of unnamed parameters
-	Body        io.Reader         // Body (nil is allowed)
-}
-
-// newRequest returns a new Request with empty Params.
-func newRequest(cmd string, body io.Reader) *Request {
-	return &Request{
-		Command:     cmd,
-		CommandRule: GetCommandRule(cmd),
-		Params:      make(map[string]string),
-		Body:        body,
-	}
-}
-
-// NewRequest returns a new Request.
-func NewRequest(cmd string, params map[string]interface{}, body io.Reader) (*Request, error) {
-	if err := checkCommand(cmd); err != nil {
-		return nil, err
-	}
-	r := newRequest(cmd, body)
-	for k, v := range params {
-		if err := r.AddParam(k, v); err != nil {
-			return nil, EnhanceError(err, map[string]interface{}{
-				"command": cmd,
-			})
-		}
-	}
-	return r, nil
-}
-
-// unescapeCommandByte returns an unescaped byte.
-func unescapeCommandByte(b byte) byte {
-	switch b {
-	case 'b':
-		return '\b'
-	case 't':
-		return '\t'
-	case 'r':
-		return '\r'
-	case 'n':
-		return '\n'
-	default:
-		return b
-	}
-}
-
-// tokenizeCommand tokenizes s as a command.
-func tokenizeCommand(s string) []string {
-	var tokens []string
-	var token []byte
-	for {
-		s = strings.TrimLeft(s, " \t\r\n")
-		if len(s) == 0 {
-			break
-		}
-		switch s[0] {
-		case '"', '\'':
-			i := 1
-			for ; i < len(s); i++ {
-				if s[i] == s[0] {
-					i++
-					break
-				}
-				if s[i] != '\\' {
-					token = append(token, s[i])
-					continue
-				}
-				i++
-				if i == len(s) {
-					break
-				}
-				token = append(token, unescapeCommandByte(s[i]))
-			}
-			s = s[i:]
-		default:
-			i := 0
-		Loop:
-			for ; i < len(s); i++ {
-				switch s[i] {
-				case ' ', '\t', '\r', '\n', '"', '\'':
-					break Loop
-				case '\\':
-					i++
-					if i == len(s) {
-						break Loop
-					}
-					token = append(token, unescapeCommandByte(s[i]))
-				default:
-					token = append(token, s[i])
-				}
-			}
-			s = s[i:]
-		}
-		tokens = append(tokens, string(token))
-		token = token[:0]
-	}
-	return tokens
-}
-
-// ParseRequest parses a request.
-func ParseRequest(cmd string, body io.Reader) (*Request, error) {
-	tokens := tokenizeCommand(cmd)
-	if len(tokens) == 0 {
-		return nil, NewError(StatusInvalidCommand, map[string]interface{}{
-			"tokens": tokens,
-			"error":  "len(tokens) must not be 0.",
-		})
-	}
-	if err := checkCommand(tokens[0]); err != nil {
-		return nil, err
-	}
-	r := newRequest(tokens[0], body)
-	for i := 1; i < len(tokens); i++ {
-		var k, v string
-		if strings.HasPrefix(tokens[i], "--") {
-			k = tokens[i][2:]
-			i++
-			if i < len(tokens) {
-				v = tokens[i]
-			}
-		} else {
-			v = tokens[i]
-		}
-		if err := r.AddParam(k, v); err != nil {
-			return nil, err
-		}
-	}
-	return r, nil
-}
-
-// convertParamValue converts a parameter value.
-func (r *Request) convertParamValue(k string, v interface{}) (string, error) {
-	if v == nil {
-		return "null", nil
-	}
-	val := reflect.ValueOf(v)
-	switch val.Kind() {
-	case reflect.Bool:
-		return strconv.FormatBool(val.Bool()), nil
-	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
-		return strconv.FormatInt(val.Int(), 10), nil
-	case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
-		return strconv.FormatUint(val.Uint(), 10), nil
-	case reflect.String:
-		return val.String(), nil
-	default:
-		return "", NewError(StatusInvalidCommand, map[string]interface{}{
-			"key":   k,
-			"value": v,
-			"error": "The value type is not supported.",
-		})
-	}
-}
-
-// AddParam adds a parameter.
-// AddParam assumes that Command is already set.
-func (r *Request) AddParam(key string, value interface{}) error {
-	if r.CommandRule == nil {
-		r.CommandRule = GetCommandRule(r.Command)
-	}
-	if key == "" {
-		if r.NAnonParams >= len(r.CommandRule.ParamRules) {
-			return NewError(StatusInvalidCommand, map[string]interface{}{
-				"command": r.Command,
-				"error": fmt.Sprintf("The command accepts at most %d unnamed parameters.",
-					len(r.CommandRule.ParamRules)),
-			})
-		}
-		pr := r.CommandRule.ParamRules[r.NAnonParams]
-		if err := pr.CheckValue(value); err != nil {
-			return EnhanceError(err, map[string]interface{}{
-				"command": r.Command,
-				"key":     key,
-			})
-		}
-		v, err := r.convertParamValue(pr.Key, value)
-		if err != nil {
-			return EnhanceError(err, map[string]interface{}{
-				"command": r.Command,
-			})
-		}
-		if r.Params == nil {
-			r.Params = make(map[string]string)
-		}
-		r.Params[pr.Key] = v
-		r.NAnonParams++
-		return nil
-	}
-	if err := r.CommandRule.CheckParam(key, value); err != nil {
-		return EnhanceError(err, map[string]interface{}{
-			"command": r.Command,
-		})
-	}
-	v, err := r.convertParamValue(key, value)
-	if err != nil {
-		return EnhanceError(err, map[string]interface{}{
-			"command": r.Command,
-		})
-	}
-	if r.Params == nil {
-		r.Params = make(map[string]string)
-	}
-	r.Params[key] = v
-	return nil
-}
-
-// RemoveParam removes a parameter.
-func (r *Request) RemoveParam(key string) error {
-	if _, ok := r.Params[key]; !ok {
-		return NewError(StatusInvalidOperation, map[string]interface{}{
-			"key":   key,
-			"error": "The key does not exist.",
-		})
-	}
-	delete(r.Params, key)
-	return nil
-}
-
-// GQTPRequest returns components for a GQTP request.
-// If the request is invalid, GQTPRequest returns an error.
-//
-// GQTPRequest assembles Command and Params into a string.
-// Parameters in the string are sorted in key order.
-func (r *Request) GQTPRequest() (cmd string, body io.Reader, err error) {
-	if err = r.Check(); err != nil {
-		return
-	}
-	size := len(r.Command)
-	for k, v := range r.Params {
-		size += len(k) + 3
-		size += len(v)*2 + 3
-	}
-	buf := make([]byte, 0, size)
-	buf = append(buf, r.Command...)
-	keys := make([]string, 0, len(r.Params))
-	for k := range r.Params {
-		keys = append(keys, k)
-	}
-	sort.Strings(keys)
-	for _, k := range keys {
-		v := r.Params[k]
-		buf = append(buf, " --"...)
-		buf = append(buf, k...)
-		buf = append(buf, " '"...)
-		for i := 0; i < len(v); i++ {
-			switch v[i] {
-			case '\'', '\\', '\b', '\t', '\r', '\n':
-				buf = append(buf, '\\')
-			}
-			buf = append(buf, v[i])
-		}
-		buf = append(buf, '\'')
-	}
-	cmd = string(buf)
-	body = r.Body
-	return
-}
-
-// HTTPRequest returns components for an HTTP request.
-// If the request is invalid, HTTPRequest returns an error.
-func (r *Request) HTTPRequest() (cmd string, params map[string]string, body io.Reader, err error) {
-	if err = r.Check(); err != nil {
-		return
-	}
-	cmd = r.Command
-	params = r.Params
-	body = r.Body
-	return
-}
-
-// NeedBody returns whether or not the request requires a body.
-func (r *Request) NeedBody() bool {
-	switch r.Command {
-	case "load":
-		_, ok := r.Params["values"]
-		return !ok
-	default:
-		return false
-	}
-}
-
-// Check checks whether or not the request is valid.
-func (r *Request) Check() error {
-	if err := checkCommand(r.Command); err != nil {
-		return err
-	}
-	cr := r.CommandRule
-	if cr == nil {
-		cr = GetCommandRule(r.Command)
-	}
-	for k, v := range r.Params {
-		if err := cr.CheckParam(k, v); err != nil {
-			return EnhanceError(err, map[string]interface{}{
-				"command": r.Command,
-			})
-		}
-	}
-	for _, pr := range cr.ParamRules {
-		if pr.Required {
-			if _, ok := r.Params[pr.Key]; !ok {
-				return NewError(StatusInvalidCommand, map[string]interface{}{
-					"command": r.Command,
-					"key":     pr.Key,
-					"error":   "The parameter is required.",
-				})
-			}
-		}
-	}
-	switch r.Command {
-	case "load":
-		if _, ok := r.Params["values"]; ok {
-			if r.Body != nil {
-				return NewError(StatusInvalidCommand, map[string]interface{}{
-					"command":   r.Command,
-					"hasValues": true,
-					"hasBody":   true,
-					"error":     "The command does not accept a body.",
-				})
-			}
-		} else if r.Body == nil {
-			return NewError(StatusInvalidCommand, map[string]interface{}{
-				"command":   r.Command,
-				"hasValues": false,
-				"hasBody":   false,
-				"error":     "The command requires a body.",
-			})
-		}
-	default:
-		if r.Body != nil {
-			return NewError(StatusInvalidCommand, map[string]interface{}{
-				"command": r.Command,
-				"hasBody": true,
-				"error":   "The command does not accept a body.",
-			})
-		}
-	}
-	return nil
-}

  Deleted: v2/request_test.go (+0 -207) 100644
===================================================================
--- v2/request_test.go    2017-06-14 10:41:58 +0900 (9b3f9ee)
+++ /dev/null
@@ -1,207 +0,0 @@
-package grnci
-
-import (
-	"fmt"
-	"testing"
-)
-
-func TestNewRequest(t *testing.T) {
-	params := map[string]interface{}{
-		"table":     "Tbl",
-		"filter":    "value < 100",
-		"sort_keys": "value",
-		"offset":    0,
-		"limit":     -1,
-	}
-	req, err := NewRequest("select", params, nil)
-	if err != nil {
-		t.Fatalf("NewRequest failed: %v", err)
-	}
-	if req.Command != "select" {
-		t.Fatalf("ParseRequest failed: cmd = %s, want = %s",
-			req.Command, "select")
-	}
-	for key, value := range params {
-		if req.Params[key] != fmt.Sprint(value) {
-			t.Fatalf("ParseRequest failed: params[\"%s\"] = %s, want = %v",
-				key, req.Params[key], value)
-		}
-	}
-}
-
-func TestParseRequest(t *testing.T) {
-	req, err := ParseRequest(`select Tbl --query "\"apple juice\"" --filter 'price < 100'`, nil)
-	if err != nil {
-		t.Fatalf("ParseRequest failed: %v", err)
-	}
-	if req.Command != "select" {
-		t.Fatalf("ParseRequest failed: command: actual = %s, want = %s",
-			req.Command, "select")
-	}
-	if req.Params["table"] != "Tbl" {
-		t.Fatalf("ParseRequest failed: params[\"table\"] = %s, want = %s",
-			req.Params["table"], "Tbl")
-	}
-	if req.Params["query"] != "\"apple juice\"" {
-		t.Fatalf("ParseRequest failed: params[\"query\"] = %s, want = %s",
-			req.Params["query"], "apple juice")
-	}
-	if req.Params["filter"] != "price < 100" {
-		t.Fatalf("ParseRequest failed: params[\"filter\"] = %s, want = %s",
-			req.Params["filter"], "price < 100")
-	}
-}
-
-func TestRequestAddParam(t *testing.T) {
-	params := map[string]interface{}{
-		"table":     "Tbl",
-		"filter":    "value < 100",
-		"sort_keys": "value",
-		"offset":    0,
-		"limit":     -1,
-	}
-	req, err := NewRequest("select", nil, nil)
-	if err != nil {
-		t.Fatalf("NewRequest failed: %v", err)
-	}
-	for key, value := range params {
-		if err := req.AddParam(key, value); err != nil {
-			t.Fatalf("req.AddParam failed: %v", err)
-		}
-	}
-	if req.Command != "select" {
-		t.Fatalf("req.AddParam failed: cmd = %s, want = %s",
-			req.Command, "select")
-	}
-	for key, value := range params {
-		if req.Params[key] != fmt.Sprint(value) {
-			t.Fatalf("req.AddParam failed: params[\"%s\"] = %s, want = %v",
-				key, req.Params[key], value)
-		}
-	}
-}
-
-func TestRequestRemoveParam(t *testing.T) {
-	params := map[string]interface{}{
-		"table":     "Tbl",
-		"filter":    "value < 100",
-		"sort_keys": "value",
-		"offset":    0,
-		"limit":     -1,
-	}
-	req, err := NewRequest("select", nil, nil)
-	if err != nil {
-		t.Fatalf("NewRequest failed: %v", err)
-	}
-	for key, value := range params {
-		if err := req.AddParam(key, value); err != nil {
-			t.Fatalf("req.AddParam failed: %v", err)
-		}
-	}
-	for key := range params {
-		if err := req.RemoveParam(key); err != nil {
-			t.Fatalf("req.RemoveParam failed: %v", err)
-		}
-	}
-	if req.Command != "select" {
-		t.Fatalf("req.RemoveParam failed: cmd = %s, want = %s",
-			req.Command, "select")
-	}
-	for key := range params {
-		if _, ok := req.Params[key]; ok {
-			t.Fatalf("req.RemoveParam failed: params[\"%s\"] = %s",
-				key, req.Params[key])
-		}
-	}
-}
-
-func TestRequestCheck(t *testing.T) {
-	data := map[string]bool{
-		"status":                       true,
-		"select Tbl":                   true,
-		"select --123 xyz":             false,
-		"_select --table Tbl":          true,
-		"load --table Tbl":             false,
-		"load --table Tbl --values []": true,
-	}
-	for cmd, want := range data {
-		req, err := ParseRequest(cmd, nil)
-		if err != nil {
-			t.Fatalf("ParseRequest failed: %v", err)
-		}
-		err = req.Check()
-		actual := err == nil
-		if actual != want {
-			t.Fatalf("req.Check failed: cmd = %s, actual = %v, want = %v, err = %v",
-				cmd, actual, want, err)
-		}
-	}
-}
-
-func TestRequestGQTPRequest(t *testing.T) {
-	params := map[string]interface{}{
-		"table":     "Tbl",
-		"filter":    "value < 100",
-		"sort_keys": "value",
-		"offset":    0,
-		"limit":     -1,
-	}
-	req, err := NewRequest("select", params, nil)
-	if err != nil {
-		t.Fatalf("NewRequest failed: %v", err)
-	}
-	actual, _, err := req.GQTPRequest()
-	if err != nil {
-		t.Fatalf("req.GQTPRequest failed: %v", err)
-	}
-	want := "select --filter 'value < 100' --limit '-1' --offset '0' --sort_keys 'value' --table 'Tbl'"
-	if actual != want {
-		t.Fatalf("req.GQTPRequest failed: actual = %s, want = %s",
-			actual, want)
-	}
-}
-
-func TestRequestHTTPRequest(t *testing.T) {
-	req, err := ParseRequest(`select Tbl --query "\"apple juice\"" --filter 'price < 100'`, nil)
-	if err != nil {
-		t.Fatalf("ParseRequest failed: %v", err)
-	}
-	cmd, params, _, err := req.HTTPRequest()
-	if err != nil {
-		t.Fatalf("req.HTTPRequest failed: %v", err)
-	}
-	if cmd != "select" {
-		t.Fatalf("req.HTTPRequest failed: cmd = %s, want = %s", cmd, "select")
-	}
-	if params["table"] != "Tbl" {
-		t.Fatalf("ParseRequest failed: params[\"table\"] = %s, want = %s",
-			params["table"], "Tbl")
-	}
-	if params["query"] != "\"apple juice\"" {
-		t.Fatalf("ParseRequest failed: params[\"query\"] = %s, want = %s",
-			params["query"], "apple juice")
-	}
-	if params["filter"] != "price < 100" {
-		t.Fatalf("ParseRequest failed: params[\"filter\"] = %s, want = %s",
-			params["filter"], "price < 100")
-	}
-}
-
-func TestRequestNeedBody(t *testing.T) {
-	data := map[string]bool{
-		"status":                       false,
-		"select Tbl":                   false,
-		"load --table Tbl":             true,
-		"load --table Tbl --values []": false,
-	}
-	for cmd, want := range data {
-		req, err := ParseRequest(cmd, nil)
-		if err != nil {
-			t.Fatalf("ParseRequest failed: %v", err)
-		}
-		actual := req.NeedBody()
-		if actual != want {
-			t.Fatalf("req.NeedBody failed: actual = %v, want = %v", actual, want)
-		}
-	}
-}

  Modified: v2/response.go (+0 -3)
===================================================================
--- v2/response.go    2017-06-14 10:41:58 +0900 (dac839d)
+++ v2/response.go    2017-06-16 10:52:57 +0900 (f6cbb2b)
@@ -6,9 +6,6 @@ import (
 
 // Response is an interface for responses.
 type Response interface {
-	// Status returns the status code.
-	Status() int
-
 	// Start returns the start time.
 	Start() time.Time
 

  Deleted: v2/rule.go (+0 -542) 100644
===================================================================
--- v2/rule.go    2017-06-14 10:41:58 +0900 (ccbaf93)
+++ /dev/null
@@ -1,542 +0,0 @@
-package grnci
-
-import (
-	"reflect"
-)
-
-// TODO: add functions to check parameters.
-
-// checkParamKeyDefault is the default function to check parameter keys.
-func checkParamKeyDefault(k string) error {
-	if k == "" {
-		return NewError(StatusInvalidCommand, map[string]interface{}{
-			"key":   k,
-			"error": "len(key) must not be 0.",
-		})
-	}
-	for i := 0; i < len(k); i++ {
-		switch {
-		case k[i] >= '0' && k[i] <= '9':
-		case k[i] >= 'a' && k[i] <= 'z':
-		case k[i] >= 'A' && k[i] <= 'Z':
-		default:
-			switch k[i] {
-			case '#', '@', '-', '_', '.', '[', ']':
-			default:
-				return NewError(StatusInvalidCommand, map[string]interface{}{
-					"key":   k,
-					"error": "key must consist of [0-9a-zA-Z#@-_.[]].",
-				})
-			}
-		}
-	}
-	return nil
-}
-
-// checkParamValueDefault is the default function to check parameter values.
-func checkParamValueDefault(v interface{}) error {
-	if v == nil {
-		return nil
-	}
-	val := reflect.ValueOf(v)
-	switch val.Kind() {
-	case reflect.Bool:
-	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
-	case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
-	case reflect.String:
-	default:
-		return NewError(StatusInvalidCommand, map[string]interface{}{
-			"value": v,
-			"error": "The value type is not supported.",
-		})
-	}
-	return nil
-}
-
-// checkParamDefault is the default function to check parameters.
-func checkParamDefault(k string, v interface{}) error {
-	if err := checkParamKeyDefault(k); err != nil {
-		return EnhanceError(err, map[string]interface{}{
-			"value": v,
-		})
-	}
-	if err := checkParamValueDefault(v); err != nil {
-		return EnhanceError(err, map[string]interface{}{
-			"key": k,
-		})
-	}
-	return nil
-}
-
-// checkCommand checks whether s is valid as a command.
-func checkCommand(s string) error {
-	if _, ok := CommandRules[s]; ok {
-		return nil
-	}
-	if s == "" {
-		return NewError(StatusInvalidCommand, map[string]interface{}{
-			"command": s,
-			"error":   "len(command) must not be 0.",
-		})
-	}
-	for i := 0; i < len(s); i++ {
-		if !(s[i] >= 'a' && s[i] <= 'z') && s[i] != '_' {
-			return NewError(StatusInvalidCommand, map[string]interface{}{
-				"command": s,
-				"error":   "command must consist of [a-z_].",
-			})
-		}
-	}
-	return nil
-}
-
-// ParamRule is a parameter rule.
-type ParamRule struct {
-	Key          string                    // Parameter key
-	ValueChecker func(v interface{}) error // Function to check parameter values
-	Required     bool                      // Whether the parameter is required
-}
-
-// NewParamRule returns a new ParamRule.
-func NewParamRule(key string, valueChecker func(v interface{}) error, required bool) *ParamRule {
-	return &ParamRule{
-		Key:          key,
-		ValueChecker: valueChecker,
-		Required:     required,
-	}
-}
-
-// CheckValue checks a parameter value.
-func (pr *ParamRule) CheckValue(v interface{}) error {
-	if pr.ValueChecker != nil {
-		return pr.ValueChecker(v)
-	}
-	return checkParamValueDefault(v)
-}
-
-// CommandRule is a command rule.
-type CommandRule struct {
-	ParamChecker  func(k string, v interface{}) error // Function to check uncommon parameters
-	ParamRules    []*ParamRule                        // Ordered common parameters
-	ParamRulesMap map[string]*ParamRule               // Index for ParamRules
-}
-
-// GetCommandRule returns the command rule for the specified command.
-func GetCommandRule(cmd string) *CommandRule {
-	if cr := CommandRules[cmd]; cr != nil {
-		return cr
-	}
-	return DefaultCommandRule
-}
-
-// NewCommandRule returns a new CommandRule.
-func NewCommandRule(paramChecker func(k string, v interface{}) error, prs ...*ParamRule) *CommandRule {
-	prMap := make(map[string]*ParamRule)
-	for _, pr := range prs {
-		prMap[pr.Key] = pr
-	}
-	return &CommandRule{
-		ParamChecker:  paramChecker,
-		ParamRules:    prs,
-		ParamRulesMap: prMap,
-	}
-}
-
-// CheckParam checks a parameter.
-func (cr *CommandRule) CheckParam(k string, v interface{}) error {
-	if cr, ok := cr.ParamRulesMap[k]; ok {
-		if err := cr.CheckValue(v); err != nil {
-			return EnhanceError(err, map[string]interface{}{
-				"key": k,
-			})
-		}
-		return nil
-	}
-	if cr.ParamChecker != nil {
-		return cr.ParamChecker(k, v)
-	}
-	return checkParamDefault(k, v)
-}
-
-// commandRules is provided to hide CommandRules in doc.
-var commandRules = map[string]*CommandRule{
-	"cache_limit": NewCommandRule(
-		nil,
-		NewParamRule("max", nil, false),
-	),
-	"check": NewCommandRule(
-		nil,
-		NewParamRule("obj", nil, true),
-	),
-	"clearlock": NewCommandRule(
-		nil,
-		NewParamRule("objname", nil, true),
-	),
-	"column_copy": NewCommandRule(
-		nil,
-		NewParamRule("from_table", nil, true),
-		NewParamRule("from_name", nil, true),
-		NewParamRule("to_table", nil, true),
-		NewParamRule("to_name", nil, true),
-	),
-	"column_create": NewCommandRule(
-		nil,
-		NewParamRule("table", nil, true),
-		NewParamRule("name", nil, true),
-		NewParamRule("flags", nil, true),
-		NewParamRule("type", nil, true),
-		NewParamRule("source", nil, false),
-	),
-	"column_list": NewCommandRule(
-		nil,
-		NewParamRule("table", nil, true),
-	),
-	"column_remove": NewCommandRule(
-		nil,
-		NewParamRule("table", nil, true),
-		NewParamRule("name", nil, true),
-	),
-	"column_rename": NewCommandRule(
-		nil,
-		NewParamRule("table", nil, true),
-		NewParamRule("name", nil, true),
-		NewParamRule("new_name", nil, true),
-	),
-	"config_delete": NewCommandRule(
-		nil,
-		NewParamRule("key", nil, true),
-	),
-	"config_get": NewCommandRule(
-		nil,
-		NewParamRule("key", nil, true),
-	),
-	"config_set": NewCommandRule(
-		nil,
-		NewParamRule("key", nil, true),
-		NewParamRule("value", nil, true),
-	),
-	"database_unmap": NewCommandRule(
-		nil,
-	),
-	"define_selector": NewCommandRule(
-		nil,
-		NewParamRule("name", nil, true),
-		NewParamRule("table", nil, true),
-		NewParamRule("match_columns", nil, false),
-		NewParamRule("query", nil, false),
-		NewParamRule("filter", nil, false),
-		NewParamRule("scorer", nil, false),
-		NewParamRule("sortby", nil, false),
-		NewParamRule("output_columns", nil, false),
-		NewParamRule("offset", nil, false),
-		NewParamRule("limit", nil, false),
-		NewParamRule("drilldown", nil, false),
-		NewParamRule("drilldown_sortby", nil, false),
-		NewParamRule("drilldown_output_columns", nil, false),
-		NewParamRule("drilldown_offset", nil, false),
-		NewParamRule("drilldown_limit", nil, false),
-	),
-	"defrag": NewCommandRule(
-		nil,
-		NewParamRule("objname", nil, true),
-		NewParamRule("threshold", nil, true),
-	),
-	"delete": NewCommandRule(
-		nil,
-		NewParamRule("table", nil, true),
-		NewParamRule("key", nil, false),
-		NewParamRule("id", nil, false),
-		NewParamRule("filter", nil, false),
-	),
-	"dump": NewCommandRule(
-		nil,
-		NewParamRule("tables", nil, false),
-		NewParamRule("dump_plugins", nil, false),
-		NewParamRule("dump_schema", nil, false),
-		NewParamRule("dump_records", nil, false),
-		NewParamRule("dump_indexes", nil, false),
-	),
-	"io_flush": NewCommandRule(
-		nil,
-		NewParamRule("target_name", nil, false),
-		NewParamRule("recursive", nil, false),
-	),
-	"load": NewCommandRule(
-		nil,
-		NewParamRule("values", nil, false), // values may be passes as a body.
-		NewParamRule("table", nil, true),
-		NewParamRule("columns", nil, false),
-		NewParamRule("ifexists", nil, false),
-		NewParamRule("input_type", nil, false),
-	),
-	"lock_acquire": NewCommandRule(
-		nil,
-		NewParamRule("target_name", nil, false),
-	),
-	"lock_clear": NewCommandRule(
-		nil,
-		NewParamRule("target_name", nil, false),
-	),
-	"lock_release": NewCommandRule(
-		nil,
-		NewParamRule("target_name", nil, false),
-	),
-	"log_level": NewCommandRule(
-		nil,
-		NewParamRule("level", nil, true),
-	),
-	"log_put": NewCommandRule(
-		nil,
-		NewParamRule("level", nil, true),
-		NewParamRule("message", nil, true),
-	),
-	"log_reopen": NewCommandRule(
-		nil,
-	),
-	"logical_count": NewCommandRule(
-		nil,
-		NewParamRule("logical_table", nil, true),
-		NewParamRule("shard_key", nil, true),
-		NewParamRule("min", nil, false),
-		NewParamRule("min_border", nil, false),
-		NewParamRule("max", nil, false),
-		NewParamRule("max_border", nil, false),
-		NewParamRule("filter", nil, false),
-	),
-	"logical_parameters": NewCommandRule(
-		nil,
-		NewParamRule("range_index", nil, false),
-	),
-	"logical_range_filter": NewCommandRule(
-		nil,
-		NewParamRule("logical_table", nil, true),
-		NewParamRule("shard_key", nil, true),
-		NewParamRule("min", nil, false),
-		NewParamRule("min_border", nil, false),
-		NewParamRule("max", nil, false),
-		NewParamRule("max_border", nil, false),
-		NewParamRule("order", nil, false),
-		NewParamRule("filter", nil, false),
-		NewParamRule("offset", nil, false),
-		NewParamRule("limit", nil, false),
-		NewParamRule("output_columns", nil, false),
-		NewParamRule("use_range_index", nil, false),
-	),
-	"logical_select": NewCommandRule(
-		nil,
-		NewParamRule("logical_table", nil, true),
-		NewParamRule("shard_key", nil, true),
-		NewParamRule("min", nil, false),
-		NewParamRule("min_border", nil, false),
-		NewParamRule("max", nil, false),
-		NewParamRule("max_border", nil, false),
-		NewParamRule("filter", nil, false),
-		NewParamRule("sortby", nil, false),
-		NewParamRule("output_columns", nil, false),
-		NewParamRule("offset", nil, false),
-		NewParamRule("limit", nil, false),
-		NewParamRule("drilldown", nil, false),
-		NewParamRule("drilldown_sortby", nil, false),
-		NewParamRule("drilldown_output_columns", nil, false),
-		NewParamRule("drilldown_offset", nil, false),
-		NewParamRule("drilldown_limit", nil, false),
-		NewParamRule("drilldown_calc_types", nil, false),
-		NewParamRule("drilldown_calc_target", nil, false),
-		NewParamRule("sort_keys", nil, false),
-		NewParamRule("drilldown_sort_keys", nil, false),
-		NewParamRule("match_columns", nil, false),
-		NewParamRule("query", nil, false),
-		NewParamRule("drilldown_filter", nil, false),
-	),
-	"logical_shard_list": NewCommandRule(
-		nil,
-		NewParamRule("logical_table", nil, true),
-	),
-	"logical_table_remove": NewCommandRule(
-		nil,
-		NewParamRule("logical_table", nil, true),
-		NewParamRule("shard_key", nil, true),
-		NewParamRule("min", nil, false),
-		NewParamRule("min_border", nil, false),
-		NewParamRule("max", nil, false),
-		NewParamRule("max_border", nil, false),
-		NewParamRule("dependent", nil, false),
-		NewParamRule("force", nil, false),
-	),
-	"normalize": NewCommandRule(
-		nil,
-		NewParamRule("normalizer", nil, true),
-		NewParamRule("string", nil, true),
-		NewParamRule("flags", nil, false),
-	),
-	"normalizer_list": NewCommandRule(
-		nil,
-	),
-	"object_exist": NewCommandRule(
-		nil,
-		NewParamRule("name", nil, true),
-	),
-	"object_inspect": NewCommandRule(
-		nil,
-		NewParamRule("name", nil, false),
-	),
-	"object_list": NewCommandRule(
-		nil,
-	),
-	"object_remove": NewCommandRule(
-		nil,
-		NewParamRule("name", nil, true),
-		NewParamRule("force", nil, false),
-	),
-	"plugin_register": NewCommandRule(
-		nil,
-		NewParamRule("name", nil, true),
-	),
-	"plugin_unregister": NewCommandRule(
-		nil,
-		NewParamRule("name", nil, true),
-	),
-	"query_expand": NewCommandRule(
-		nil,
-	), // TODO
-	"quit": NewCommandRule(
-		nil,
-	),
-	"range_filter": NewCommandRule(
-		nil,
-	), // TODO
-	"register": NewCommandRule(
-		nil,
-		NewParamRule("path", nil, true),
-	),
-	"reindex": NewCommandRule(
-		nil,
-		NewParamRule("target_name", nil, false),
-	),
-	"request_cancel": NewCommandRule(
-		nil,
-		NewParamRule("id", nil, true),
-	),
-	"ruby_eval": NewCommandRule(
-		nil,
-		NewParamRule("script", nil, true),
-	),
-	"ruby_load": NewCommandRule(
-		nil,
-		NewParamRule("path", nil, true),
-	),
-	"schema": NewCommandRule(
-		nil,
-	),
-	"select": NewCommandRule(
-		nil,
-		NewParamRule("table", nil, true),
-		NewParamRule("match_columns", nil, false),
-		NewParamRule("query", nil, false),
-		NewParamRule("filter", nil, false),
-		NewParamRule("scorer", nil, false),
-		NewParamRule("sortby", nil, false),
-		NewParamRule("output_columns", nil, false),
-		NewParamRule("offset", nil, false),
-		NewParamRule("limit", nil, false),
-		NewParamRule("drilldown", nil, false),
-		NewParamRule("drilldown_sortby", nil, false),
-		NewParamRule("drilldown_output_columns", nil, false),
-		NewParamRule("drilldown_offset", nil, false),
-		NewParamRule("drilldown_limit", nil, false),
-		NewParamRule("cache", nil, false),
-		NewParamRule("match_escalation_threshold", nil, false),
-		NewParamRule("query_expansion", nil, false),
-		NewParamRule("query_flags", nil, false),
-		NewParamRule("query_expander", nil, false),
-		NewParamRule("adjuster", nil, false),
-		NewParamRule("drilldown_calc_types", nil, false),
-		NewParamRule("drilldown_calc_target", nil, false),
-		NewParamRule("drilldown_filter", nil, false),
-		NewParamRule("sort_keys", nil, false),
-		NewParamRule("drilldown_sort_keys", nil, false),
-	),
-	"shutdown": NewCommandRule(
-		nil,
-		NewParamRule("mode", nil, false),
-	),
-	"status": NewCommandRule(
-		nil,
-	),
-	"suggest": NewCommandRule(
-		nil,
-		NewParamRule("types", nil, true),
-		NewParamRule("table", nil, true),
-		NewParamRule("column", nil, true),
-		NewParamRule("query", nil, true),
-		NewParamRule("sortby", nil, false),
-		NewParamRule("output_columns", nil, false),
-		NewParamRule("offset", nil, false),
-		NewParamRule("limit", nil, false),
-		NewParamRule("frequency_threshold", nil, false),
-		NewParamRule("conditional_probability_threshold", nil, false),
-		NewParamRule("prefix_search", nil, false),
-	),
-	"table_copy": NewCommandRule(
-		nil,
-		NewParamRule("from_name", nil, true),
-		NewParamRule("to_name", nil, true),
-	),
-	"table_create": NewCommandRule(
-		nil,
-		NewParamRule("name", nil, true),
-		NewParamRule("flags", nil, false),
-		NewParamRule("key_type", nil, false),
-		NewParamRule("value_type", nil, false),
-		NewParamRule("default_tokenizer", nil, false),
-		NewParamRule("normalizer", nil, false),
-		NewParamRule("token_filters", nil, false),
-	),
-	"table_list": NewCommandRule(
-		nil,
-	),
-	"table_remove": NewCommandRule(
-		nil,
-		NewParamRule("name", nil, true),
-		NewParamRule("dependent", nil, false),
-	),
-	"table_rename": NewCommandRule(
-		nil,
-		NewParamRule("name", nil, true),
-		NewParamRule("new_name", nil, true),
-	),
-	"table_tokenize": NewCommandRule(
-		nil,
-		NewParamRule("table", nil, true),
-		NewParamRule("string", nil, true),
-		NewParamRule("flags", nil, false),
-		NewParamRule("mode", nil, false),
-		NewParamRule("index_column", nil, false),
-	),
-	"thread_limit": NewCommandRule(
-		nil,
-		NewParamRule("max", nil, false),
-	),
-	"tokenize": NewCommandRule(
-		nil,
-		NewParamRule("tokenizer", nil, true),
-		NewParamRule("string", nil, true),
-		NewParamRule("normalizer", nil, false),
-		NewParamRule("flags", nil, false),
-		NewParamRule("mode", nil, false),
-		NewParamRule("token_filters", nil, false),
-	),
-	"tokenizer_list": NewCommandRule(
-		nil,
-	),
-	"truncate": NewCommandRule(
-		nil,
-		NewParamRule("target_name", nil, true),
-	),
-}
-
-// CommandRules is a map of command rules.
-var CommandRules = commandRules
-
-// DefaultCommandRule is applied to commands not listed in CommandRules.
-var DefaultCommandRule = NewCommandRule(nil)

  Added: v2/type.go (+7 -0) 100644
===================================================================
--- /dev/null
+++ v2/type.go    2017-06-16 10:52:57 +0900 (86ab078)
@@ -0,0 +1,7 @@
+package grnci
+
+// Geo represents TokyoGeoPoint and WGS84GeoPoint.
+type Geo struct {
+	Lat  int32 // Latitude in milliseconds.
+	Long int32 // Longitude in milliseconds.
+}




More information about the Groonga-commit mailing list
Zurück zum Archiv-Index