syncbase: syncQL: support varargs in functions, functions cleanup

StrCat() now takes a variable number of args (but requires at
least two).

In addition, nearly all of the custom arg checking functions
have been deleted.  Writing custom arg checks is usually not
required.  That is, if the only check is to see if the args
match the types specified in the function definitions, nil
can be used for the check function as arg type checking is
now part of the standard check.

Note: There were some wrong types specified for some of
the date functions. Now that the arg types specified are being
used to check the types, they were uncovered and fixed.

Change-Id: I062ea666302a5cd049478f5f3b864bf4c132ff4c
diff --git a/v23/syncbase/nosql/internal/query/conversions/conversions.go b/v23/syncbase/nosql/internal/query/conversions/conversions.go
index 12a1dcd..b9fcce6 100644
--- a/v23/syncbase/nosql/internal/query/conversions/conversions.go
+++ b/v23/syncbase/nosql/internal/query/conversions/conversions.go
@@ -9,6 +9,7 @@
 	"fmt"
 	"math/big"
 	"strconv"
+	"strings"
 
 	"v.io/syncbase/v23/syncbase/nosql/internal/query/query_parser"
 )
@@ -125,6 +126,27 @@
 	return &c, nil
 }
 
+func ConvertValueToBool(o *query_parser.Operand) (*query_parser.Operand, error) {
+	var c query_parser.Operand
+	c.Type = query_parser.TypBool
+	switch o.Type {
+	case query_parser.TypBool:
+		c.Bool = o.Bool
+	case query_parser.TypStr:
+		switch strings.ToLower(o.Str) {
+		case "true":
+			c.Bool = true
+		case "false":
+			c.Bool = false
+		default:
+			return nil, errors.New("Cannot convert object to bool.")
+		}
+	default:
+		return nil, errors.New("Cannot convert operand to bool.")
+	}
+	return &c, nil
+}
+
 func ConvertValueToBigInt(o *query_parser.Operand) (*query_parser.Operand, error) {
 	// Operand cannot be literal, big.Rat or float.
 	var c query_parser.Operand
diff --git a/v23/syncbase/nosql/internal/query/query_functions/date_funcs.go b/v23/syncbase/nosql/internal/query/query_functions/date_funcs.go
index f62fb78..2db74f0 100644
--- a/v23/syncbase/nosql/internal/query/query_functions/date_funcs.go
+++ b/v23/syncbase/nosql/internal/query/query_functions/date_funcs.go
@@ -13,32 +13,6 @@
 	"v.io/syncbase/v23/syncbase/nosql/syncql"
 )
 
-// If possible, check if arg is convertable to a time.  Fields and not yet computed
-// functions cannot be checked and will just return nil.
-func checkIfPossibleThatArgIsConvertableToTime(db query_db.Database, arg *query_parser.Operand) error {
-	// If arg is a literal or an already computed function,
-	// make sure it can be converted to a time.
-	switch arg.Type {
-	case query_parser.TypBigInt, query_parser.TypBigRat, query_parser.TypBool, query_parser.TypComplex, query_parser.TypFloat, query_parser.TypInt, query_parser.TypStr, query_parser.TypTime, query_parser.TypUint:
-		_, err := conversions.ConvertValueToTime(arg)
-		if err != nil {
-			return syncql.NewErrTimeConversionError(db.GetContext(), arg.Off, err)
-		} else {
-			return nil
-		}
-	case query_parser.TypFunction:
-		if arg.Function.Computed {
-			_, err := conversions.ConvertValueToTime(arg.Function.RetValue)
-			if err != nil {
-				return syncql.NewErrTimeConversionError(db.GetContext(), arg.Off, err)
-			} else {
-				return nil
-			}
-		}
-	}
-	return nil
-}
-
 // If possible, check if arg is convertable to a location.  Fields and not yet computed
 // functions cannot be checked and will just return nil.
 func checkIfPossibleThatArgIsConvertableToLocation(db query_db.Database, arg *query_parser.Operand) error {
@@ -259,18 +233,12 @@
 	return &o
 }
 
-func timeAndStringArgsCheck(db query_db.Database, off int64, args []*query_parser.Operand) error {
-	// The first arg must be a time.
-	if err := checkIfPossibleThatArgIsConvertableToTime(db, args[0]); err != nil {
-		return err
-	}
-	// The second arg must be a string and convertable to a location.
+func secondArgLocationCheck(db query_db.Database, off int64, args []*query_parser.Operand) error {
+	// At this point, for the args that can be evaluated before execution, it is known that
+	// there are two args, a time followed by a string.
+	// Just need to check that the 2nd arg is convertable to a location.
 	if err := checkIfPossibleThatArgIsConvertableToLocation(db, args[1]); err != nil {
 		return err
 	}
 	return nil
 }
-
-func singleTimeArgCheck(db query_db.Database, off int64, args []*query_parser.Operand) error {
-	return checkIfPossibleThatArgIsConvertableToString(db, args[0])
-}
diff --git a/v23/syncbase/nosql/internal/query/query_functions/doc.go b/v23/syncbase/nosql/internal/query/query_functions/doc.go
index 596fa46..08a2b10 100644
--- a/v23/syncbase/nosql/internal/query/query_functions/doc.go
+++ b/v23/syncbase/nosql/internal/query/query_functions/doc.go
@@ -30,6 +30,10 @@
 //               informational only as the function itself is required
 //               to attempt to coerce the args to the correct type or
 //               return an error.
+// hasVarArgs    bool
+//               True if, in addition to any types listed in argTypes, the function
+//               can take additional (optional) args.
+// varArgsType   The type of the additional (optional) args.
 // returnType    query_parser.OperandType
 //               The return type of the function, for informational purposes
 //               only.
@@ -42,4 +46,6 @@
 //               This function should check any arguments that it can at checker time.
 //               It can check literals.  Note: if all args are literals, the function itself
 //               is called at checker time rather than this function.
+//               DO NOT sepecify a checkArgsAddr if all that is to be checked is the number
+//               and types of args. These checks are standard.
 package query_functions
diff --git a/v23/syncbase/nosql/internal/query/query_functions/math_funcs.go b/v23/syncbase/nosql/internal/query/query_functions/math_funcs.go
index 9823140..b49fc3d 100644
--- a/v23/syncbase/nosql/internal/query/query_functions/math_funcs.go
+++ b/v23/syncbase/nosql/internal/query/query_functions/math_funcs.go
@@ -8,7 +8,6 @@
 	"v.io/syncbase/v23/syncbase/nosql/internal/query/conversions"
 	"v.io/syncbase/v23/syncbase/nosql/internal/query/query_parser"
 	"v.io/syncbase/v23/syncbase/nosql/query_db"
-	"v.io/syncbase/v23/syncbase/nosql/syncql"
 )
 
 func complexFunc(db query_db.Database, off int64, args []*query_parser.Operand) (*query_parser.Operand, error) {
@@ -24,13 +23,3 @@
 
 	return makeComplexOp(off, complex(r.Float, i.Float)), nil
 }
-
-func twoFloatsArgsCheck(db query_db.Database, off int64, args []*query_parser.Operand) error {
-	// The two args must be convertable to floats.
-	for i := 0; i < 2; i++ {
-		if err := checkIfPossibleThatArgIsConvertableToFloat(db, args[i]); err != nil {
-			return syncql.NewErrFloatConversionError(db.GetContext(), args[i].Off, err)
-		}
-	}
-	return nil
-}
diff --git a/v23/syncbase/nosql/internal/query/query_functions/query_functions.go b/v23/syncbase/nosql/internal/query/query_functions/query_functions.go
index 7207b19..e051a03 100644
--- a/v23/syncbase/nosql/internal/query/query_functions/query_functions.go
+++ b/v23/syncbase/nosql/internal/query/query_functions/query_functions.go
@@ -19,6 +19,8 @@
 
 type function struct {
 	argTypes      []query_parser.OperandType
+	hasVarArgs    bool
+	varArgsType   query_parser.OperandType // ignored if !hasVarArgs
 	returnType    query_parser.OperandType
 	funcAddr      queryFunc
 	checkArgsAddr checkArgsFunc
@@ -30,33 +32,33 @@
 func init() {
 	functions = make(map[string]function)
 
-	functions["Date"] = function{[]query_parser.OperandType{query_parser.TypStr}, query_parser.TypTime, date, singleTimeArgCheck}
-	functions["DateTime"] = function{[]query_parser.OperandType{query_parser.TypStr}, query_parser.TypTime, dateTime, singleTimeArgCheck}
-	functions["Y"] = function{[]query_parser.OperandType{query_parser.TypStr, query_parser.TypStr}, query_parser.TypTime, y, timeAndStringArgsCheck}
-	functions["YM"] = function{[]query_parser.OperandType{query_parser.TypStr, query_parser.TypStr}, query_parser.TypTime, ym, timeAndStringArgsCheck}
-	functions["YMD"] = function{[]query_parser.OperandType{query_parser.TypStr, query_parser.TypStr}, query_parser.TypTime, ymd, timeAndStringArgsCheck}
-	functions["YMDH"] = function{[]query_parser.OperandType{query_parser.TypStr, query_parser.TypStr}, query_parser.TypTime, ymdh, timeAndStringArgsCheck}
-	functions["YMDHM"] = function{[]query_parser.OperandType{query_parser.TypStr, query_parser.TypStr}, query_parser.TypTime, ymdhm, timeAndStringArgsCheck}
-	functions["YMDHMS"] = function{[]query_parser.OperandType{query_parser.TypStr, query_parser.TypStr}, query_parser.TypTime, ymdhms, timeAndStringArgsCheck}
-	functions["Now"] = function{[]query_parser.OperandType{}, query_parser.TypTime, now, nil}
+	functions["Date"] = function{[]query_parser.OperandType{query_parser.TypStr}, false, query_parser.TypNil, query_parser.TypTime, date, nil}
+	functions["DateTime"] = function{[]query_parser.OperandType{query_parser.TypStr}, false, query_parser.TypNil, query_parser.TypTime, dateTime, nil}
+	functions["Y"] = function{[]query_parser.OperandType{query_parser.TypTime, query_parser.TypStr}, false, query_parser.TypNil, query_parser.TypTime, y, secondArgLocationCheck}
+	functions["YM"] = function{[]query_parser.OperandType{query_parser.TypTime, query_parser.TypStr}, false, query_parser.TypNil, query_parser.TypTime, ym, secondArgLocationCheck}
+	functions["YMD"] = function{[]query_parser.OperandType{query_parser.TypTime, query_parser.TypStr}, false, query_parser.TypNil, query_parser.TypTime, ymd, secondArgLocationCheck}
+	functions["YMDH"] = function{[]query_parser.OperandType{query_parser.TypTime, query_parser.TypStr}, false, query_parser.TypNil, query_parser.TypTime, ymdh, secondArgLocationCheck}
+	functions["YMDHM"] = function{[]query_parser.OperandType{query_parser.TypTime, query_parser.TypStr}, false, query_parser.TypNil, query_parser.TypTime, ymdhm, secondArgLocationCheck}
+	functions["YMDHMS"] = function{[]query_parser.OperandType{query_parser.TypTime, query_parser.TypStr}, false, query_parser.TypNil, query_parser.TypTime, ymdhms, secondArgLocationCheck}
+	functions["Now"] = function{[]query_parser.OperandType{}, false, query_parser.TypNil, query_parser.TypTime, now, nil}
 
 	// String Functions
-	functions["Lowercase"] = function{[]query_parser.OperandType{query_parser.TypStr}, query_parser.TypStr, lowerCase, singleStringArgCheck}
-	functions["Split"] = function{[]query_parser.OperandType{query_parser.TypStr, query_parser.TypStr}, query_parser.TypObject, split, twoStringArgsCheck}
-	functions["Type"] = function{[]query_parser.OperandType{query_parser.TypObject}, query_parser.TypStr, typeFunc, singleFieldArgCheck}
-	functions["Uppercase"] = function{[]query_parser.OperandType{query_parser.TypStr}, query_parser.TypStr, upperCase, singleStringArgCheck}
-	functions["StrCat"] = function{[]query_parser.OperandType{query_parser.TypStr, query_parser.TypStr}, query_parser.TypStr, strCat, twoStringArgsCheck}
-	functions["StrIndex"] = function{[]query_parser.OperandType{query_parser.TypStr, query_parser.TypStr}, query_parser.TypInt, strIndex, twoStringArgsCheck}
-	functions["StrRepeat"] = function{[]query_parser.OperandType{query_parser.TypStr, query_parser.TypInt}, query_parser.TypStr, strRepeat, stringIntArgsCheck}
-	functions["StrReplace"] = function{[]query_parser.OperandType{query_parser.TypStr, query_parser.TypStr, query_parser.TypStr}, query_parser.TypStr, strReplace, threeStringArgsCheck}
-	functions["StrLastIndex"] = function{[]query_parser.OperandType{query_parser.TypStr, query_parser.TypStr}, query_parser.TypInt, strLastIndex, twoStringArgsCheck}
-	functions["Trim"] = function{[]query_parser.OperandType{query_parser.TypStr}, query_parser.TypStr, trim, singleStringArgCheck}
-	functions["TrimLeft"] = function{[]query_parser.OperandType{query_parser.TypStr}, query_parser.TypStr, trimLeft, singleStringArgCheck}
-	functions["TrimRight"] = function{[]query_parser.OperandType{query_parser.TypStr}, query_parser.TypStr, trimRight, singleStringArgCheck}
+	functions["Lowercase"] = function{[]query_parser.OperandType{query_parser.TypStr}, false, query_parser.TypNil, query_parser.TypStr, lowerCase, nil}
+	functions["Split"] = function{[]query_parser.OperandType{query_parser.TypStr, query_parser.TypStr}, false, query_parser.TypNil, query_parser.TypObject, split, nil}
+	functions["Type"] = function{[]query_parser.OperandType{query_parser.TypObject}, false, query_parser.TypNil, query_parser.TypStr, typeFunc, typeFuncFieldCheck}
+	functions["Uppercase"] = function{[]query_parser.OperandType{query_parser.TypStr}, false, query_parser.TypNil, query_parser.TypStr, upperCase, nil}
+	functions["StrCat"] = function{[]query_parser.OperandType{query_parser.TypStr, query_parser.TypStr}, true, query_parser.TypStr, query_parser.TypStr, strCat, nil}
+	functions["StrIndex"] = function{[]query_parser.OperandType{query_parser.TypStr, query_parser.TypStr}, false, query_parser.TypNil, query_parser.TypInt, strIndex, nil}
+	functions["StrRepeat"] = function{[]query_parser.OperandType{query_parser.TypStr, query_parser.TypInt}, false, query_parser.TypNil, query_parser.TypStr, strRepeat, nil}
+	functions["StrReplace"] = function{[]query_parser.OperandType{query_parser.TypStr, query_parser.TypStr, query_parser.TypStr}, false, query_parser.TypNil, query_parser.TypStr, strReplace, nil}
+	functions["StrLastIndex"] = function{[]query_parser.OperandType{query_parser.TypStr, query_parser.TypStr}, false, query_parser.TypNil, query_parser.TypInt, strLastIndex, nil}
+	functions["Trim"] = function{[]query_parser.OperandType{query_parser.TypStr}, false, query_parser.TypNil, query_parser.TypStr, trim, nil}
+	functions["TrimLeft"] = function{[]query_parser.OperandType{query_parser.TypStr}, false, query_parser.TypNil, query_parser.TypStr, trimLeft, nil}
+	functions["TrimRight"] = function{[]query_parser.OperandType{query_parser.TypStr}, false, query_parser.TypNil, query_parser.TypStr, trimRight, nil}
 
-	functions["Complex"] = function{[]query_parser.OperandType{query_parser.TypFloat, query_parser.TypFloat}, query_parser.TypComplex, complexFunc, twoFloatsArgsCheck}
+	functions["Complex"] = function{[]query_parser.OperandType{query_parser.TypFloat, query_parser.TypFloat}, false, query_parser.TypNil, query_parser.TypComplex, complexFunc, nil}
 
-	functions["Len"] = function{[]query_parser.OperandType{query_parser.TypObject}, query_parser.TypInt, lenFunc, nil}
+	functions["Len"] = function{[]query_parser.OperandType{query_parser.TypObject}, false, query_parser.TypNil, query_parser.TypInt, lenFunc, nil}
 
 	// Build lowercaseFuncName->funcName
 	lowercaseFunctions = make(map[string]string)
@@ -66,8 +68,7 @@
 }
 
 // Check that function exists and that the number of args passed matches the spec.
-// Call query_functions.CheckFunction.  This will check for correct number of args
-// and, to the extent possible, correct types.
+// Call query_functions.CheckFunction.  This will check for, to the extent possible, correct types.
 // Furthermore, it may execute the function if the function takes no args or
 // takes only literal args (or an arg that is a function that is also executed
 // early).  CheckFunction will fill in arg types, return types and may fill in
@@ -78,9 +79,16 @@
 	} else {
 		f.ArgTypes = entry.argTypes
 		f.RetType = entry.returnType
-		if len(f.ArgTypes) != len(f.Args) {
+		if !entry.hasVarArgs && len(f.Args) != len(entry.argTypes) {
 			return syncql.NewErrFunctionArgCount(db.GetContext(), f.Off, f.Name, int64(len(f.ArgTypes)), int64(len(f.Args)))
 		}
+		if entry.hasVarArgs && len(f.Args) < len(entry.argTypes) {
+			return syncql.NewErrFunctionAtLeastArgCount(db.GetContext(), f.Off, f.Name, int64(len(f.ArgTypes)), int64(len(f.Args)))
+		}
+		// Standard check for types of fixed and var args
+		if err = argsStandardCheck(db, f.Off, entry, f.Args); err != nil {
+			return err
+		}
 		// Check if the function can be executed now.
 		// If any arg is not a literal and not a function that has been already executed,
 		// then okToExecuteNow will be set to false.
@@ -218,115 +226,87 @@
 	return &o
 }
 
-func singleStringArgCheck(db query_db.Database, off int64, args []*query_parser.Operand) error {
-	return checkIfPossibleThatArgIsConvertableToString(db, args[0])
-}
-
-func twoStringArgsCheck(db query_db.Database, off int64, args []*query_parser.Operand) error {
-	if err := checkIfPossibleThatArgIsConvertableToString(db, args[0]); err != nil {
-		return err
-	}
-	return checkIfPossibleThatArgIsConvertableToString(db, args[1])
-}
-
-func threeStringArgsCheck(db query_db.Database, off int64, args []*query_parser.Operand) error {
-	if err := checkIfPossibleThatArgIsConvertableToString(db, args[0]); err != nil {
-		return err
-	}
-	if err := checkIfPossibleThatArgIsConvertableToString(db, args[1]); err != nil {
-		return err
-	}
-	return checkIfPossibleThatArgIsConvertableToString(db, args[2])
-}
-
-func stringIntArgsCheck(db query_db.Database, off int64, args []*query_parser.Operand) error {
-	if err := checkIfPossibleThatArgIsConvertableToString(db, args[0]); err != nil {
-		return err
-	}
-	return checkIfPossibleThatArgIsConvertableToInt(db, args[1])
-}
-
-func singleFieldArgCheck(db query_db.Database, off int64, args []*query_parser.Operand) error {
-	// single argument must be of type field
-	// It must begin with a v segment.
-	if args[0].Type != query_parser.TypField || len(args[0].Column.Segments) < 1 || args[0].Column.Segments[0].Value != "v" {
-		return syncql.NewErrArgMustBeField(db.GetContext(), args[0].Off)
-	}
-	return nil
-}
-
-// If possible, check if arg is convertable to a string.  Fields and not yet computed
-// functions cannot be checked and will just return nil.
-func checkIfPossibleThatArgIsConvertableToString(db query_db.Database, arg *query_parser.Operand) error {
-	// If arg is a literal or an already computed function,
-	// make sure it can be converted to a string.
+func checkArg(db query_db.Database, off int64, argType query_parser.OperandType, arg *query_parser.Operand) error {
+	// We can't check unless the arg is a literal or an already computed function,
+	var operandToConvert *query_parser.Operand
 	switch arg.Type {
 	case query_parser.TypBigInt, query_parser.TypBigRat, query_parser.TypBool, query_parser.TypComplex, query_parser.TypFloat, query_parser.TypInt, query_parser.TypStr, query_parser.TypTime, query_parser.TypUint:
-		_, err := conversions.ConvertValueToString(arg)
-		if err != nil {
-			return syncql.NewErrStringConversionError(db.GetContext(), arg.Off, err)
-		} else {
-			return nil
-		}
+		operandToConvert = arg
 	case query_parser.TypFunction:
 		if arg.Function.Computed {
-			_, err := conversions.ConvertValueToString(arg.Function.RetValue)
-			if err != nil {
-				return syncql.NewErrStringConversionError(db.GetContext(), arg.Off, err)
-			} else {
-				return nil
-			}
+			operandToConvert = arg.Function.RetValue
+		} else {
+			return nil // arg is not yet resolved, we can't check
+		}
+	default:
+		return nil // arg is not yet resolved, we can't check
+	}
+	// make sure it can be converted to argType.
+	var err error
+	switch argType {
+	case query_parser.TypBigInt:
+		_, err = conversions.ConvertValueToBigInt(operandToConvert)
+		if err != nil {
+			err = syncql.NewErrBigIntConversionError(db.GetContext(), arg.Off, err)
+		}
+	case query_parser.TypBigRat:
+		_, err = conversions.ConvertValueToBigRat(operandToConvert)
+		if err != nil {
+			err = syncql.NewErrBigRatConversionError(db.GetContext(), arg.Off, err)
+		}
+	case query_parser.TypBool:
+		_, err = conversions.ConvertValueToBool(operandToConvert)
+		if err != nil {
+			err = syncql.NewErrBoolConversionError(db.GetContext(), arg.Off, err)
+		}
+	case query_parser.TypComplex:
+		_, err = conversions.ConvertValueToComplex(operandToConvert)
+		if err != nil {
+			err = syncql.NewErrComplexConversionError(db.GetContext(), arg.Off, err)
+		}
+	case query_parser.TypFloat:
+		_, err = conversions.ConvertValueToFloat(operandToConvert)
+		if err != nil {
+			err = syncql.NewErrFloatConversionError(db.GetContext(), arg.Off, err)
+		}
+	case query_parser.TypInt:
+		_, err = conversions.ConvertValueToInt(operandToConvert)
+		if err != nil {
+			err = syncql.NewErrIntConversionError(db.GetContext(), arg.Off, err)
+		}
+	case query_parser.TypStr:
+		_, err = conversions.ConvertValueToString(operandToConvert)
+		if err != nil {
+			err = syncql.NewErrStringConversionError(db.GetContext(), arg.Off, err)
+		}
+	case query_parser.TypTime:
+		_, err = conversions.ConvertValueToTime(operandToConvert)
+		if err != nil {
+			err = syncql.NewErrTimeConversionError(db.GetContext(), arg.Off, err)
+		}
+	case query_parser.TypUint:
+		_, err = conversions.ConvertValueToUint(operandToConvert)
+		if err != nil {
+			err = syncql.NewErrUintConversionError(db.GetContext(), arg.Off, err)
 		}
 	}
-	return nil
+	return err
 }
 
-// If possible, check if arg is convertable to an int.  Fields and not yet computed
-// functions cannot be checked and will just return nil.
-func checkIfPossibleThatArgIsConvertableToInt(db query_db.Database, arg *query_parser.Operand) error {
-	// If arg is a literal or an already computed function,
-	// make sure it can be converted to a int.
-	switch arg.Type {
-	case query_parser.TypBigInt, query_parser.TypBigRat, query_parser.TypBool, query_parser.TypComplex, query_parser.TypFloat, query_parser.TypInt, query_parser.TypStr, query_parser.TypTime, query_parser.TypUint:
-		_, err := conversions.ConvertValueToInt(arg)
-		if err != nil {
-			return syncql.NewErrIntConversionError(db.GetContext(), arg.Off, err)
-		} else {
-			return nil
-		}
-	case query_parser.TypFunction:
-		if arg.Function.Computed {
-			_, err := conversions.ConvertValueToInt(arg.Function.RetValue)
-			if err != nil {
-				return syncql.NewErrIntConversionError(db.GetContext(), arg.Off, err)
-			} else {
-				return nil
-			}
+// Check types of fixed args.  For functions that take varargs, check that the type of
+// any varargs matches the type specified.
+func argsStandardCheck(db query_db.Database, off int64, f *function, args []*query_parser.Operand) error {
+	// Check types of required args.
+	for i := 0; i < len(f.argTypes); i++ {
+		if err := checkArg(db, off, f.argTypes[i], args[i]); err != nil {
+			return err
 		}
 	}
-	return nil
-}
-
-// If possible, check if arg is convertable to a float.  Fields and not yet computed
-// functions cannot be checked and will just return nil.
-func checkIfPossibleThatArgIsConvertableToFloat(db query_db.Database, arg *query_parser.Operand) error {
-	// If arg is a literal or an already computed function,
-	// make sure it can be converted to a float.
-	switch arg.Type {
-	case query_parser.TypBigInt, query_parser.TypBigRat, query_parser.TypBool, query_parser.TypComplex, query_parser.TypFloat, query_parser.TypInt, query_parser.TypStr, query_parser.TypTime, query_parser.TypUint:
-		_, err := conversions.ConvertValueToFloat(arg)
-		if err != nil {
-			return syncql.NewErrFloatConversionError(db.GetContext(), arg.Off, err)
-		} else {
-			return nil
-		}
-	case query_parser.TypFunction:
-		if arg.Function.Computed {
-			_, err := conversions.ConvertValueToFloat(arg.Function.RetValue)
-			if err != nil {
-				return syncql.NewErrFloatConversionError(db.GetContext(), arg.Off, err)
-			} else {
-				return nil
+	// Check types of varargs.
+	if f.hasVarArgs {
+		for i := len(f.argTypes); i < len(args); i++ {
+			if err := checkArg(db, off, f.varArgsType, args[i]); err != nil {
+				return err
 			}
 		}
 	}
diff --git a/v23/syncbase/nosql/internal/query/query_functions/query_functions_test.go b/v23/syncbase/nosql/internal/query/query_functions/query_functions_test.go
index 71df3ad..621f8d4 100644
--- a/v23/syncbase/nosql/internal/query/query_functions/query_functions_test.go
+++ b/v23/syncbase/nosql/internal/query/query_functions/query_functions_test.go
@@ -557,7 +557,7 @@
 				Int:  2,
 			},
 		},
-		// StrCat
+		// StrCat (2 args)
 		functionsTest{
 			&query_parser.Function{
 				Name: "StrCat",
@@ -594,6 +594,120 @@
 				Str:  "FooBar",
 			},
 		},
+		// StrCat (3 args)
+		functionsTest{
+			&query_parser.Function{
+				Name: "StrCat",
+				Args: []*query_parser.Operand{
+					&query_parser.Operand{
+						Type: query_parser.TypStr,
+						Str:  "Foo",
+					},
+					&query_parser.Operand{
+						Type: query_parser.TypStr,
+						Str:  ",",
+					},
+					&query_parser.Operand{
+						Type: query_parser.TypStr,
+						Str:  "Bar",
+					},
+				},
+				ArgTypes: []query_parser.OperandType{
+					query_parser.TypStr,
+					query_parser.TypStr,
+				},
+				RetType:  query_parser.TypStr,
+				Computed: false,
+				RetValue: nil,
+			},
+			[]*query_parser.Operand{
+				&query_parser.Operand{
+					Type: query_parser.TypStr,
+					Str:  "Foo",
+				},
+				&query_parser.Operand{
+					Type: query_parser.TypStr,
+					Str:  ",",
+				},
+				&query_parser.Operand{
+					Type: query_parser.TypStr,
+					Str:  "Bar",
+				},
+			},
+			&query_parser.Operand{
+				Type: query_parser.TypStr,
+				Str:  "Foo,Bar",
+			},
+		},
+		// StrCat (5 args)
+		functionsTest{
+			&query_parser.Function{
+				Name: "StrCat",
+				Args: []*query_parser.Operand{
+					&query_parser.Operand{
+						Type: query_parser.TypStr,
+						Str:  "[",
+					},
+					&query_parser.Operand{
+						Type: query_parser.TypStr,
+						Str:  "Foo",
+					},
+					&query_parser.Operand{
+						Type: query_parser.TypStr,
+						Str:  "]",
+					},
+					&query_parser.Operand{
+						Type: query_parser.TypStr,
+						Str:  "[",
+					},
+					&query_parser.Operand{
+						Type: query_parser.TypStr,
+						Str:  "Bar",
+					},
+					&query_parser.Operand{
+						Type: query_parser.TypStr,
+						Str:  "]",
+					},
+				},
+				ArgTypes: []query_parser.OperandType{
+					query_parser.TypStr,
+					query_parser.TypStr,
+				},
+				RetType:  query_parser.TypStr,
+				Computed: false,
+				RetValue: nil,
+			},
+			[]*query_parser.Operand{
+				&query_parser.Operand{
+					Type: query_parser.TypStr,
+					Str:  "[",
+				},
+				&query_parser.Operand{
+					Type: query_parser.TypStr,
+					Str:  "Foo",
+				},
+				&query_parser.Operand{
+					Type: query_parser.TypStr,
+					Str:  "]",
+				},
+				&query_parser.Operand{
+					Type: query_parser.TypStr,
+					Str:  "[",
+				},
+				&query_parser.Operand{
+					Type: query_parser.TypStr,
+					Str:  "Bar",
+				},
+				&query_parser.Operand{
+					Type: query_parser.TypStr,
+					Str:  "]",
+				},
+			},
+			&query_parser.Operand{
+				Type: query_parser.TypStr,
+				Str:  "[Foo][Bar]",
+			},
+		},
 		// StrIndex
 		functionsTest{
 			&query_parser.Function{
diff --git a/v23/syncbase/nosql/internal/query/query_functions/str_funcs.go b/v23/syncbase/nosql/internal/query/query_functions/str_funcs.go
index 9f00417..22d4734 100644
--- a/v23/syncbase/nosql/internal/query/query_functions/str_funcs.go
+++ b/v23/syncbase/nosql/internal/query/query_functions/str_funcs.go
@@ -39,6 +39,15 @@
 	return makeStrOp(off, args[0].Object.Type().Name()), nil
 }
 
+func typeFuncFieldCheck(db query_db.Database, off int64, args []*query_parser.Operand) error {
+	// At this point, it is known that there is one arg. Make sure it is of type field
+	// and is a value field (i.e., it must begin with a v segment).
+	if args[0].Type != query_parser.TypField || len(args[0].Column.Segments) < 1 || args[0].Column.Segments[0].Value != "v" {
+		return syncql.NewErrArgMustBeField(db.GetContext(), args[0].Off)
+	}
+	return nil
+}
+
 // Split splits str (arg[0]) into substrings separated by sep (arg[1]) and returns an
 // array of substrings between those separators. If sep is empty, Split splits after each
 // UTF-8 sequence.
@@ -60,20 +69,19 @@
 	return &o, nil
 }
 
-// StrCat(left, right string) string
-// StrCat returns the concatenation of two strings.
-// e.g., StrCat("abc", "def") returns "abcdef"
-// TODO(jkline): Allow a variable number of args?
+// StrCat(str1, str2,... string) string
+// StrCat returns the concatenation of all the string args.
+// e.g., StrCat("abc", ",", "def") returns "abc,def"
 func strCat(db query_db.Database, off int64, args []*query_parser.Operand) (*query_parser.Operand, error) {
-	left, err := conversions.ConvertValueToString(args[0])
-	if err != nil {
-		return nil, err
+	val := ""
+	for _, arg := range args {
+		str, err := conversions.ConvertValueToString(arg)
+		if err != nil {
+			return nil, err
+		}
+		val += str.Str
 	}
-	right, err := conversions.ConvertValueToString(args[1])
-	if err != nil {
-		return nil, err
-	}
-	return makeStrOp(off, left.Str+right.Str), nil
+	return makeStrOp(off, val), nil
 }
 
 // StrIndex(s, sep string) int
diff --git a/v23/syncbase/nosql/internal/query/test/query_test.go b/v23/syncbase/nosql/internal/query/test/query_test.go
index 10564da..b8d2abf 100644
--- a/v23/syncbase/nosql/internal/query/test/query_test.go
+++ b/v23/syncbase/nosql/internal/query/test/query_test.go
@@ -1945,6 +1945,22 @@
 			},
 		},
 		{
+			// StrCat
+			"select StrCat(v.Address.City, \", \", v.Address.State) from Customer where v.Name = \"John Smith\"",
+			[]string{"StrCat"},
+			[][]*vdl.Value{
+				[]*vdl.Value{vdl.ValueOf("Palo Alto, CA")},
+			},
+		},
+		{
+			// StrCat
+			"select StrCat(v.Address.City, 42) from Customer where v.Name = \"John Smith\"",
+			[]string{"StrCat"},
+			[][]*vdl.Value{
+				[]*vdl.Value{vdl.ValueOf("Palo Alto42")},
+			},
+		},
+		{
 			// StrIndex
 			"select StrIndex(v.Address.City, \"lo\") from Customer where v.Name = \"John Smith\"",
 			[]string{"StrIndex"},
@@ -2860,6 +2876,18 @@
 			"select len(\"foo\") from Customer",
 			syncql.NewErrDidYouMeanFunction(db.GetContext(), 7, "Len"),
 		},
+		{
+			"select StrRepeat(\"foo\", \"x\") from Customer",
+			syncql.NewErrIntConversionError(db.GetContext(), 24, errors.New("Cannot convert operand to int64.")),
+		},
+		{
+			"select Complex(23, \"foo\") from Customer",
+			syncql.NewErrFloatConversionError(db.GetContext(), 19, errors.New("Cannot convert operand to float64.")),
+		},
+		{
+			"select Complex(\"foo\", 42) from Customer",
+			syncql.NewErrFloatConversionError(db.GetContext(), 15, errors.New("Cannot convert operand to float64.")),
+		},
 	}
 
 	for _, test := range basic {
diff --git a/v23/syncbase/nosql/syncql/syncql.vdl b/v23/syncbase/nosql/syncql/syncql.vdl
index fa7ca31..7241b7b 100644
--- a/v23/syncbase/nosql/syncql/syncql.vdl
+++ b/v23/syncbase/nosql/syncql/syncql.vdl
@@ -45,6 +45,9 @@
 	FunctionArgCount(off int64, name string, expected int64, found int64) {
 		"en": "[{off}]Function '{name}' expects {expected} args, found: {found}.",
 	}
+	FunctionAtLeastArgCount(off int64, name string, expected int64, found int64) {
+		"en": "[{off}]Function '{name}' expects at least {expected} args, found: {found}.",
+	}
 	FunctionTypeInvalidArg(off int64) {
 		"en": "[{off}]Function 'Type()' cannot get type of argument -- expecting object.",
 	}
@@ -60,6 +63,21 @@
 	ArgMustBeField(off int64) {
 		"en": "[{off}]Argument must be a value field (i.e., must begin with 'v').",
 	}
+	BigIntConversionError(off int64, err error) {
+		"en": "[{off}]Can't convert to BigInt: {err}.",
+	}
+	BigRatConversionError(off int64, err error) {
+		"en": "[{off}]Can't convert to BigRat: {err}.",
+	}
+	BoolConversionError(off int64, err error) {
+		"en": "[{off}]Can't convert to Bool: {err}.",
+	}
+	ComplexConversionError(off int64, err error) {
+		"en": "[{off}]Can't convert to Complex: {err}.",
+	}
+	UintConversionError(off int64, err error) {
+		"en": "[{off}]Can't convert to Uint: {err}.",
+	}
 	TimeConversionError(off int64, err error) {
 		"en": "[{off}]Can't convert to time: {err}.",
 	}
diff --git a/v23/syncbase/nosql/syncql/syncql.vdl.go b/v23/syncbase/nosql/syncql/syncql.vdl.go
index fe7b6f0..801c3c1 100644
--- a/v23/syncbase/nosql/syncql/syncql.vdl.go
+++ b/v23/syncbase/nosql/syncql/syncql.vdl.go
@@ -28,11 +28,17 @@
 	ErrExpectedOperand                 = verror.Register("v.io/syncbase/v23/syncbase/nosql/syncql.ExpectedOperand", verror.NoRetry, "{1:}{2:} [{3}]Expected operand, found {4}.")
 	ErrExpectedOperator                = verror.Register("v.io/syncbase/v23/syncbase/nosql/syncql.ExpectedOperator", verror.NoRetry, "{1:}{2:} [{3}]Expected operator, found {4}.")
 	ErrFunctionArgCount                = verror.Register("v.io/syncbase/v23/syncbase/nosql/syncql.FunctionArgCount", verror.NoRetry, "{1:}{2:} [{3}]Function '{4}' expects {5} args, found: {6}.")
+	ErrFunctionAtLeastArgCount         = verror.Register("v.io/syncbase/v23/syncbase/nosql/syncql.FunctionAtLeastArgCount", verror.NoRetry, "{1:}{2:} [{3}]Function '{4}' expects at least {5} args, found: {6}.")
 	ErrFunctionTypeInvalidArg          = verror.Register("v.io/syncbase/v23/syncbase/nosql/syncql.FunctionTypeInvalidArg", verror.NoRetry, "{1:}{2:} [{3}]Function 'Type()' cannot get type of argument -- expecting object.")
 	ErrFunctionLenInvalidArg           = verror.Register("v.io/syncbase/v23/syncbase/nosql/syncql.FunctionLenInvalidArg", verror.NoRetry, "{1:}{2:} [{3}]Function 'Len()' expects array, list, set, map, string or nil.")
 	ErrFunctionArgBad                  = verror.Register("v.io/syncbase/v23/syncbase/nosql/syncql.FunctionArgBad", verror.NoRetry, "{1:}{2:} [{3}]Function '{4}' arg '{5}' could not be resolved.")
 	ErrFunctionNotFound                = verror.Register("v.io/syncbase/v23/syncbase/nosql/syncql.FunctionNotFound", verror.NoRetry, "{1:}{2:} [{3}]Function '{4}' not found.")
 	ErrArgMustBeField                  = verror.Register("v.io/syncbase/v23/syncbase/nosql/syncql.ArgMustBeField", verror.NoRetry, "{1:}{2:} [{3}]Argument must be a value field (i.e., must begin with 'v').")
+	ErrBigIntConversionError           = verror.Register("v.io/syncbase/v23/syncbase/nosql/syncql.BigIntConversionError", verror.NoRetry, "{1:}{2:} [{3}]Can't convert to BigInt: {4}.")
+	ErrBigRatConversionError           = verror.Register("v.io/syncbase/v23/syncbase/nosql/syncql.BigRatConversionError", verror.NoRetry, "{1:}{2:} [{3}]Can't convert to BigRat: {4}.")
+	ErrBoolConversionError             = verror.Register("v.io/syncbase/v23/syncbase/nosql/syncql.BoolConversionError", verror.NoRetry, "{1:}{2:} [{3}]Can't convert to Bool: {4}.")
+	ErrComplexConversionError          = verror.Register("v.io/syncbase/v23/syncbase/nosql/syncql.ComplexConversionError", verror.NoRetry, "{1:}{2:} [{3}]Can't convert to Complex: {4}.")
+	ErrUintConversionError             = verror.Register("v.io/syncbase/v23/syncbase/nosql/syncql.UintConversionError", verror.NoRetry, "{1:}{2:} [{3}]Can't convert to Uint: {4}.")
 	ErrTimeConversionError             = verror.Register("v.io/syncbase/v23/syncbase/nosql/syncql.TimeConversionError", verror.NoRetry, "{1:}{2:} [{3}]Can't convert to time: {4}.")
 	ErrLocationConversionError         = verror.Register("v.io/syncbase/v23/syncbase/nosql/syncql.LocationConversionError", verror.NoRetry, "{1:}{2:} [{3}]Can't convert to location: {4}.")
 	ErrStringConversionError           = verror.Register("v.io/syncbase/v23/syncbase/nosql/syncql.StringConversionError", verror.NoRetry, "{1:}{2:} [{3}]Can't convert to string: {4}.")
@@ -72,11 +78,17 @@
 	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrExpectedOperand.ID), "{1:}{2:} [{3}]Expected operand, found {4}.")
 	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrExpectedOperator.ID), "{1:}{2:} [{3}]Expected operator, found {4}.")
 	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrFunctionArgCount.ID), "{1:}{2:} [{3}]Function '{4}' expects {5} args, found: {6}.")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrFunctionAtLeastArgCount.ID), "{1:}{2:} [{3}]Function '{4}' expects at least {5} args, found: {6}.")
 	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrFunctionTypeInvalidArg.ID), "{1:}{2:} [{3}]Function 'Type()' cannot get type of argument -- expecting object.")
 	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrFunctionLenInvalidArg.ID), "{1:}{2:} [{3}]Function 'Len()' expects array, list, set, map, string or nil.")
 	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrFunctionArgBad.ID), "{1:}{2:} [{3}]Function '{4}' arg '{5}' could not be resolved.")
 	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrFunctionNotFound.ID), "{1:}{2:} [{3}]Function '{4}' not found.")
 	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrArgMustBeField.ID), "{1:}{2:} [{3}]Argument must be a value field (i.e., must begin with 'v').")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrBigIntConversionError.ID), "{1:}{2:} [{3}]Can't convert to BigInt: {4}.")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrBigRatConversionError.ID), "{1:}{2:} [{3}]Can't convert to BigRat: {4}.")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrBoolConversionError.ID), "{1:}{2:} [{3}]Can't convert to Bool: {4}.")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrComplexConversionError.ID), "{1:}{2:} [{3}]Can't convert to Complex: {4}.")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrUintConversionError.ID), "{1:}{2:} [{3}]Can't convert to Uint: {4}.")
 	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrTimeConversionError.ID), "{1:}{2:} [{3}]Can't convert to time: {4}.")
 	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrLocationConversionError.ID), "{1:}{2:} [{3}]Can't convert to location: {4}.")
 	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrStringConversionError.ID), "{1:}{2:} [{3}]Can't convert to string: {4}.")
@@ -167,6 +179,11 @@
 	return verror.New(ErrFunctionArgCount, ctx, off, name, expected, found)
 }
 
+// NewErrFunctionAtLeastArgCount returns an error with the ErrFunctionAtLeastArgCount ID.
+func NewErrFunctionAtLeastArgCount(ctx *context.T, off int64, name string, expected int64, found int64) error {
+	return verror.New(ErrFunctionAtLeastArgCount, ctx, off, name, expected, found)
+}
+
 // NewErrFunctionTypeInvalidArg returns an error with the ErrFunctionTypeInvalidArg ID.
 func NewErrFunctionTypeInvalidArg(ctx *context.T, off int64) error {
 	return verror.New(ErrFunctionTypeInvalidArg, ctx, off)
@@ -192,6 +209,31 @@
 	return verror.New(ErrArgMustBeField, ctx, off)
 }
 
+// NewErrBigIntConversionError returns an error with the ErrBigIntConversionError ID.
+func NewErrBigIntConversionError(ctx *context.T, off int64, err error) error {
+	return verror.New(ErrBigIntConversionError, ctx, off, err)
+}
+
+// NewErrBigRatConversionError returns an error with the ErrBigRatConversionError ID.
+func NewErrBigRatConversionError(ctx *context.T, off int64, err error) error {
+	return verror.New(ErrBigRatConversionError, ctx, off, err)
+}
+
+// NewErrBoolConversionError returns an error with the ErrBoolConversionError ID.
+func NewErrBoolConversionError(ctx *context.T, off int64, err error) error {
+	return verror.New(ErrBoolConversionError, ctx, off, err)
+}
+
+// NewErrComplexConversionError returns an error with the ErrComplexConversionError ID.
+func NewErrComplexConversionError(ctx *context.T, off int64, err error) error {
+	return verror.New(ErrComplexConversionError, ctx, off, err)
+}
+
+// NewErrUintConversionError returns an error with the ErrUintConversionError ID.
+func NewErrUintConversionError(ctx *context.T, off int64, err error) error {
+	return verror.New(ErrUintConversionError, ctx, off, err)
+}
+
 // NewErrTimeConversionError returns an error with the ErrTimeConversionError ID.
 func NewErrTimeConversionError(ctx *context.T, off int64, err error) error {
 	return verror.New(ErrTimeConversionError, ctx, off, err)
diff --git a/x/ref/services/syncbase/server/mojo_impl.go b/x/ref/services/syncbase/server/mojo_impl.go
index 973bf7d..52e2d90 100644
--- a/x/ref/services/syncbase/server/mojo_impl.go
+++ b/x/ref/services/syncbase/server/mojo_impl.go
@@ -16,8 +16,6 @@
 	"fmt"
 	"strings"
 
-	"mojo/public/go/bindings"
-
 	mojom "mojom/syncbase"
 	wire "v.io/syncbase/v23/services/syncbase"
 	nosqlwire "v.io/syncbase/v23/services/syncbase/nosql"
@@ -298,7 +296,7 @@
 	return toMojoError(err), exists, nil
 }
 
-func (m *mojoImpl) DbExec(name string, query string, stream mojom.ExecStream_Pointer) (mojom.Error, error) {
+func (m *mojoImpl) DbExec(name string, query string, stream mojom.ExecStream_Request) (mojom.Error, error) {
 	return mojom.Error{}, nil
 }
 
@@ -425,57 +423,7 @@
 	return mojom.Error{}, nil
 }
 
-type scanStreamImpl struct {
-	ctx   *context.T
-	proxy *mojom.ScanStream_Proxy
-}
-
-func (s *scanStreamImpl) Send(item interface{}) error {
-	kv, ok := item.(nosqlwire.KeyValue)
-	if !ok {
-		return verror.NewErrInternal(s.ctx)
-	}
-
-	return s.proxy.OnKeyValue(mojom.KeyValue{
-		Key:   kv.Key,
-		Value: kv.Value,
-	})
-}
-
-func (s *scanStreamImpl) Recv(_ interface{}) error {
-	// This should never be called.
-	return verror.NewErrInternal(s.ctx)
-}
-
-var _ rpc.Stream = (*scanStreamImpl)(nil)
-
-// TODO(nlacasse): Provide some way for the client to cancel the stream.
-func (m *mojoImpl) TableScan(name string, start, limit []byte, ptr mojom.ScanStream_Pointer) (mojom.Error, error) {
-	ctx, call := m.newCtxCall(name, methodDesc(nosqlwire.TableDesc, "Scan"))
-	stub, err := m.getTable(ctx, call, name)
-	if err != nil {
-		return toMojoError(err), nil
-	}
-
-	proxy := mojom.NewScanStreamProxy(ptr, bindings.GetAsyncWaiter())
-
-	tableScanServerCallStub := &nosqlwire.TableScanServerCallStub{struct {
-		rpc.Stream
-		rpc.ServerCall
-	}{
-		&scanStreamImpl{
-			ctx:   ctx,
-			proxy: proxy,
-		},
-		call,
-	}}
-
-	err = stub.Scan(ctx, tableScanServerCallStub, NoSchema, start, limit)
-
-	// NOTE(nlacasse): Since we are already streaming, we send any error back
-	// to the client on the stream.  The TableScan function itself should not
-	// return an error at this point.
-	proxy.OnDone(toMojoError(err))
+func (m *mojoImpl) TableScan(name string, start, limit []byte, stream mojom.ScanStream_Request) (mojom.Error, error) {
 	return mojom.Error{}, nil
 }
 
diff --git a/x/ref/services/syncbase/store/model.go b/x/ref/services/syncbase/store/model.go
index 504ae01..be7265d 100644
--- a/x/ref/services/syncbase/store/model.go
+++ b/x/ref/services/syncbase/store/model.go
@@ -6,6 +6,9 @@
 // Currently, this API and its implementations are meant to be internal.
 package store
 
+// TODO(sadovsky): Decide whether to defensively copy passed-in []byte's vs.
+// requiring clients not to modify passed-in []byte's.
+
 // StoreReader reads data from a CRUD-capable storage engine.
 type StoreReader interface {
 	// Get returns the value for the given key. The returned slice may be a
@@ -14,8 +17,6 @@
 	// nil valbuf.
 	// If the given key is unknown, valbuf is returned unchanged and the function
 	// fails with ErrUnknownKey.
-	//
-	// It is safe to modify the contents of the key after Get returns.
 	Get(key, valbuf []byte) ([]byte, error)
 
 	// Scan returns all rows with keys in range [start, limit). If limit is "",
@@ -23,26 +24,16 @@
 	// Concurrency semantics: It is legal to perform writes concurrently with
 	// Scan. The returned stream may or may not reflect subsequent writes to keys
 	// not yet reached by the stream.
-	//
-	// It is safe to modify the contents of the arguments after Scan returns.
 	Scan(start, limit []byte) Stream
 }
 
 // StoreWriter writes data to a CRUD-capable storage engine.
 type StoreWriter interface {
 	// Put writes the given value for the given key.
-	//
-	// WARNING: For performance reasons, a Put inside a transaction doesn't make
-	// a defensive copy of the value. The client MUST keep the value unchanged
-	// until the transaction commits or aborts.
-	//
-	// It is safe to modify the contents of the key after Put returns.
 	Put(key, value []byte) error
 
 	// Delete deletes the entry for the given key.
 	// Succeeds (no-op) if the given key is unknown.
-	//
-	// It is safe to modify the contents of the key after Delete returns.
 	Delete(key []byte) error
 }
 
diff --git a/x/ref/services/syncbase/syncbased/mojo_main.go b/x/ref/services/syncbase/syncbased/mojo_main.go
index 74a247f..b478d4e 100644
--- a/x/ref/services/syncbase/syncbased/mojo_main.go
+++ b/x/ref/services/syncbase/syncbased/mojo_main.go
@@ -8,7 +8,7 @@
 
 // To build:
 // cd $V23_ROOT/experimental/projects/ether
-// make build
+// make gen/mojo/syncbased.mojo
 
 import (
 	"log"