package gorethink import ( "reflect" "strconv" "strings" "sync/atomic" "gopkg.in/gorethink/gorethink.v2/encoding" p "gopkg.in/gorethink/gorethink.v2/ql2" ) // Helper functions for constructing terms // constructRootTerm is an alias for creating a new term. func constructRootTerm(name string, termType p.Term_TermType, args []interface{}, optArgs map[string]interface{}) Term { return Term{ name: name, rootTerm: true, termType: termType, args: convertTermList(args), optArgs: convertTermObj(optArgs), } } // constructMethodTerm is an alias for creating a new term. Unlike constructRootTerm // this function adds the previous expression in the tree to the argument list to // create a method term. func constructMethodTerm(prevVal Term, name string, termType p.Term_TermType, args []interface{}, optArgs map[string]interface{}) Term { args = append([]interface{}{prevVal}, args...) return Term{ name: name, rootTerm: false, termType: termType, args: convertTermList(args), optArgs: convertTermObj(optArgs), } } // Helper functions for creating internal RQL types func newQuery(t Term, qopts map[string]interface{}, copts *ConnectOpts) (q Query, err error) { queryOpts := map[string]interface{}{} for k, v := range qopts { queryOpts[k], err = Expr(v).Build() if err != nil { return } } if copts.Database != "" { queryOpts["db"], err = DB(copts.Database).Build() if err != nil { return } } builtTerm, err := t.Build() if err != nil { return q, err } // Construct query return Query{ Type: p.Query_START, Term: &t, Opts: queryOpts, builtTerm: builtTerm, }, nil } // makeArray takes a slice of terms and produces a single MAKE_ARRAY term func makeArray(args termsList) Term { return Term{ name: "[...]", termType: p.Term_MAKE_ARRAY, args: args, } } // makeObject takes a map of terms and produces a single MAKE_OBJECT term func makeObject(args termsObj) Term { return Term{ name: "{...}", termType: p.Term_MAKE_OBJ, optArgs: args, } } var nextVarID int64 func makeFunc(f interface{}) Term { value := reflect.ValueOf(f) valueType := value.Type() var argNums = make([]interface{}, valueType.NumIn()) var args = make([]reflect.Value, valueType.NumIn()) for i := 0; i < valueType.NumIn(); i++ { // Get a slice of the VARs to use as the function arguments varID := atomic.AddInt64(&nextVarID, 1) args[i] = reflect.ValueOf(constructRootTerm("var", p.Term_VAR, []interface{}{varID}, map[string]interface{}{})) argNums[i] = varID // make sure all input arguments are of type Term argValueTypeName := valueType.In(i).String() if argValueTypeName != "gorethink.Term" && argValueTypeName != "interface {}" { panic("Function argument is not of type Term or interface {}") } } if valueType.NumOut() != 1 { panic("Function does not have a single return value") } body := value.Call(args)[0].Interface() argsArr := makeArray(convertTermList(argNums)) return constructRootTerm("func", p.Term_FUNC, []interface{}{argsArr, body}, map[string]interface{}{}) } func funcWrap(value interface{}) Term { val := Expr(value) if implVarScan(val) && val.termType != p.Term_ARGS { return makeFunc(func(x Term) Term { return val }) } return val } func funcWrapArgs(args []interface{}) []interface{} { for i, arg := range args { args[i] = funcWrap(arg) } return args } // implVarScan recursivly checks a value to see if it contains an // IMPLICIT_VAR term. If it does it returns true func implVarScan(value Term) bool { if value.termType == p.Term_IMPLICIT_VAR { return true } for _, v := range value.args { if implVarScan(v) { return true } } for _, v := range value.optArgs { if implVarScan(v) { return true } } return false } // Convert an opt args struct to a map. func optArgsToMap(optArgs OptArgs) map[string]interface{} { data, err := encode(optArgs) if err == nil && data != nil { if m, ok := data.(map[string]interface{}); ok { return m } } return map[string]interface{}{} } // Convert a list into a slice of terms func convertTermList(l []interface{}) termsList { if len(l) == 0 { return nil } terms := make(termsList, len(l)) for i, v := range l { terms[i] = Expr(v) } return terms } // Convert a map into a map of terms func convertTermObj(o map[string]interface{}) termsObj { if len(o) == 0 { return nil } terms := make(termsObj, len(o)) for k, v := range o { terms[k] = Expr(v) } return terms } // Helper functions for debugging func allArgsToStringSlice(args termsList, optArgs termsObj) []string { allArgs := make([]string, len(args)+len(optArgs)) i := 0 for _, v := range args { allArgs[i] = v.String() i++ } for k, v := range optArgs { allArgs[i] = k + "=" + v.String() i++ } return allArgs } func argsToStringSlice(args termsList) []string { allArgs := make([]string, len(args)) for i, v := range args { allArgs[i] = v.String() } return allArgs } func optArgsToStringSlice(optArgs termsObj) []string { allArgs := make([]string, len(optArgs)) i := 0 for k, v := range optArgs { allArgs[i] = k + "=" + v.String() i++ } return allArgs } func splitAddress(address string) (hostname string, port int) { hostname = "localhost" port = 28015 addrParts := strings.Split(address, ":") if len(addrParts) >= 1 { hostname = addrParts[0] } if len(addrParts) >= 2 { port, _ = strconv.Atoi(addrParts[1]) } return } func encode(data interface{}) (interface{}, error) { if _, ok := data.(Term); ok { return data, nil } v, err := encoding.Encode(data) if err != nil { return nil, err } return v, nil } // shouldRetryQuery checks the result of a query and returns true if the query // should be retried func shouldRetryQuery(q Query, err error) bool { if err == nil { return false } if _, ok := err.(RQLConnectionError); ok { return true } return err == ErrConnectionClosed }