syncbase: syncql: add query functions StrCat,StrIndex,StrLastIndex,StrRepeat,StrReplace,Trim,TrimLeft,TrimRight

StrCat(left, right string) string
StrCat returns the concatenation of two strings.
e.g., StrCat("abc", "def") returns "abcdef"

StrIndex(s, sep string) int
StrIndex returns the index of sep in s, or -1 is sep is not present in s.
e.g., StrIndex("abc", "bc") returns 1.

StrLastIndex(s, sep string) int
StrLastIndex returns the index of the last instance of sep in s, or -1 is sep is not present in s.
e.g., StrLastIndex("abcbc", "bc") returns 3.

StrRepeat(s string, count int) int
StrRepeat returns a new string consisting of count copies of the string s.
e.g., StrRepeat("abc", 3) returns "abcabcabc".

StrReplace(s, old, new string) string
StrReplace returns a copy of s with the first instance of old replaced by new.
e.g., StrReplace("abcdef", "bc", "zzzzz") returns "azzzzzdef".

Trim(s string) string
Trim returns a copy of s with all leading and trailing white space removed, as defined by Unicode.
e.g., Trim(" abc ") returns "abc".

TrimLeft(s string) string
TrimLeft returns a copy of s with all leading white space removed, as defined by Unicode.
e.g., TrimLeft(" abc ") returns "abc ".

TrimRight(s string) string
TrimRight returns a copy of s with all leading white space removed, as defined by Unicode.
e.g., TrimRight(" abc ") returns "abc ".

Change-Id: Ie843520e4107f93e6a1726e58f259246094439d5
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 903a842..9823140 100644
--- a/v23/syncbase/nosql/internal/query/query_functions/math_funcs.go
+++ b/v23/syncbase/nosql/internal/query/query_functions/math_funcs.go
@@ -28,27 +28,9 @@
 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(args[i]); err != nil {
+		if err := checkIfPossibleThatArgIsConvertableToFloat(db, args[i]); err != nil {
 			return syncql.NewErrFloatConversionError(db.GetContext(), args[i].Off, 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(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)
-		return err
-	case query_parser.TypFunction:
-		if arg.Function.Computed {
-			_, err := conversions.ConvertValueToFloat(arg.Function.RetValue)
-			return 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 cb7e40e..7207b19 100644
--- a/v23/syncbase/nosql/internal/query/query_functions/query_functions.go
+++ b/v23/syncbase/nosql/internal/query/query_functions/query_functions.go
@@ -40,10 +40,19 @@
 	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}
 
+	// 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["Complex"] = function{[]query_parser.OperandType{query_parser.TypFloat, query_parser.TypFloat}, query_parser.TypComplex, complexFunc, twoFloatsArgsCheck}
 
@@ -185,6 +194,14 @@
 	return &o
 }
 
+func makeBoolOp(off int64, b bool) *query_parser.Operand {
+	var o query_parser.Operand
+	o.Off = off
+	o.Type = query_parser.TypBool
+	o.Bool = b
+	return &o
+}
+
 func makeComplexOp(off int64, c complex128) *query_parser.Operand {
 	var o query_parser.Operand
 	o.Off = off
@@ -212,6 +229,23 @@
 	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.
@@ -246,3 +280,55 @@
 	}
 	return nil
 }
+
+// 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
+			}
+		}
+	}
+	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
+			}
+		}
+	}
+	return nil
+}
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 549cb9a..71df3ad 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
@@ -52,9 +52,9 @@
 }
 
 type functionsErrorTest struct {
-	f      *query_parser.Function
-	args   []*query_parser.Operand
-	err    error
+	f    *query_parser.Function
+	args []*query_parser.Operand
+	err  error
 }
 
 var t_2015 time.Time
@@ -367,7 +367,7 @@
 				ArgTypes: []query_parser.OperandType{
 					query_parser.TypStr,
 				},
-				RetType:  query_parser.TypTime,
+				RetType:  query_parser.TypStr,
 				Computed: false,
 				RetValue: nil,
 			},
@@ -395,7 +395,7 @@
 				ArgTypes: []query_parser.OperandType{
 					query_parser.TypStr,
 				},
-				RetType:  query_parser.TypTime,
+				RetType:  query_parser.TypStr,
 				Computed: false,
 				RetValue: nil,
 			},
@@ -557,6 +557,562 @@
 				Int:  2,
 			},
 		},
+		// StrCat
+		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:  "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:  "Bar",
+				},
+			},
+			&query_parser.Operand{
+				Type: query_parser.TypStr,
+				Str:  "FooBar",
+			},
+		},
+		// StrIndex
+		functionsTest{
+			&query_parser.Function{
+				Name: "StrIndex",
+				Args: []*query_parser.Operand{
+					&query_parser.Operand{
+						Type: query_parser.TypStr,
+						Str:  "FooBar",
+					},
+					&query_parser.Operand{
+						Type: query_parser.TypStr,
+						Str:  "Bar",
+					},
+				},
+				ArgTypes: []query_parser.OperandType{
+					query_parser.TypStr,
+					query_parser.TypStr,
+				},
+				RetType:  query_parser.TypInt,
+				Computed: false,
+				RetValue: nil,
+			},
+			[]*query_parser.Operand{
+				&query_parser.Operand{
+					Type: query_parser.TypStr,
+					Str:  "FooBar",
+				},
+				&query_parser.Operand{
+					Type: query_parser.TypStr,
+					Str:  "Bar",
+				},
+			},
+			&query_parser.Operand{
+				Type: query_parser.TypInt,
+				Int:  3,
+			},
+		},
+		// StrIndex
+		functionsTest{
+			&query_parser.Function{
+				Name: "StrIndex",
+				Args: []*query_parser.Operand{
+					&query_parser.Operand{
+						Type: query_parser.TypStr,
+						Str:  "FooBar",
+					},
+					&query_parser.Operand{
+						Type: query_parser.TypStr,
+						Str:  "Baz",
+					},
+				},
+				ArgTypes: []query_parser.OperandType{
+					query_parser.TypStr,
+					query_parser.TypStr,
+				},
+				RetType:  query_parser.TypInt,
+				Computed: false,
+				RetValue: nil,
+			},
+			[]*query_parser.Operand{
+				&query_parser.Operand{
+					Type: query_parser.TypStr,
+					Str:  "FooBar",
+				},
+				&query_parser.Operand{
+					Type: query_parser.TypStr,
+					Str:  "Baz",
+				},
+			},
+			&query_parser.Operand{
+				Type: query_parser.TypInt,
+				Int:  -1,
+			},
+		},
+		// StrLastIndex
+		functionsTest{
+			&query_parser.Function{
+				Name: "StrLastIndex",
+				Args: []*query_parser.Operand{
+					&query_parser.Operand{
+						Type: query_parser.TypStr,
+						Str:  "FooBarBar",
+					},
+					&query_parser.Operand{
+						Type: query_parser.TypStr,
+						Str:  "Bar",
+					},
+				},
+				ArgTypes: []query_parser.OperandType{
+					query_parser.TypStr,
+					query_parser.TypStr,
+				},
+				RetType:  query_parser.TypInt,
+				Computed: false,
+				RetValue: nil,
+			},
+			[]*query_parser.Operand{
+				&query_parser.Operand{
+					Type: query_parser.TypStr,
+					Str:  "FooBarBar",
+				},
+				&query_parser.Operand{
+					Type: query_parser.TypStr,
+					Str:  "Bar",
+				},
+			},
+			&query_parser.Operand{
+				Type: query_parser.TypInt,
+				Int:  6,
+			},
+		},
+		// StrLastIndex
+		functionsTest{
+			&query_parser.Function{
+				Name: "StrLastIndex",
+				Args: []*query_parser.Operand{
+					&query_parser.Operand{
+						Type: query_parser.TypStr,
+						Str:  "FooBar",
+					},
+					&query_parser.Operand{
+						Type: query_parser.TypStr,
+						Str:  "Baz",
+					},
+				},
+				ArgTypes: []query_parser.OperandType{
+					query_parser.TypStr,
+					query_parser.TypStr,
+				},
+				RetType:  query_parser.TypInt,
+				Computed: false,
+				RetValue: nil,
+			},
+			[]*query_parser.Operand{
+				&query_parser.Operand{
+					Type: query_parser.TypStr,
+					Str:  "FooBar",
+				},
+				&query_parser.Operand{
+					Type: query_parser.TypStr,
+					Str:  "Baz",
+				},
+			},
+			&query_parser.Operand{
+				Type: query_parser.TypInt,
+				Int:  -1,
+			},
+		},
+		// StrRepeat
+		functionsTest{
+			&query_parser.Function{
+				Name: "StrRepeat",
+				Args: []*query_parser.Operand{
+					&query_parser.Operand{
+						Type: query_parser.TypStr,
+						Str:  "FooBar",
+					},
+					&query_parser.Operand{
+						Type: query_parser.TypInt,
+						Int:  2,
+					},
+				},
+				ArgTypes: []query_parser.OperandType{
+					query_parser.TypStr,
+					query_parser.TypInt,
+				},
+				RetType:  query_parser.TypStr,
+				Computed: false,
+				RetValue: nil,
+			},
+			[]*query_parser.Operand{
+				&query_parser.Operand{
+					Type: query_parser.TypStr,
+					Str:  "FooBar",
+				},
+				&query_parser.Operand{
+					Type: query_parser.TypInt,
+					Int:  2,
+				},
+			},
+			&query_parser.Operand{
+				Type: query_parser.TypStr,
+				Str:  "FooBarFooBar",
+			},
+		},
+		// StrRepeat
+		functionsTest{
+			&query_parser.Function{
+				Name: "StrRepeat",
+				Args: []*query_parser.Operand{
+					&query_parser.Operand{
+						Type: query_parser.TypStr,
+						Str:  "FooBar",
+					},
+					&query_parser.Operand{
+						Type: query_parser.TypInt,
+						Int:  0,
+					},
+				},
+				ArgTypes: []query_parser.OperandType{
+					query_parser.TypStr,
+					query_parser.TypInt,
+				},
+				RetType:  query_parser.TypStr,
+				Computed: false,
+				RetValue: nil,
+			},
+			[]*query_parser.Operand{
+				&query_parser.Operand{
+					Type: query_parser.TypStr,
+					Str:  "FooBar",
+				},
+				&query_parser.Operand{
+					Type: query_parser.TypInt,
+					Int:  0,
+				},
+			},
+			&query_parser.Operand{
+				Type: query_parser.TypStr,
+				Str:  "",
+			},
+		},
+		// StrRepeat
+		functionsTest{
+			&query_parser.Function{
+				Name: "StrRepeat",
+				Args: []*query_parser.Operand{
+					&query_parser.Operand{
+						Type: query_parser.TypStr,
+						Str:  "FooBar",
+					},
+					&query_parser.Operand{
+						Type: query_parser.TypInt,
+						Int:  -1,
+					},
+				},
+				ArgTypes: []query_parser.OperandType{
+					query_parser.TypStr,
+					query_parser.TypInt,
+				},
+				RetType:  query_parser.TypStr,
+				Computed: false,
+				RetValue: nil,
+			},
+			[]*query_parser.Operand{
+				&query_parser.Operand{
+					Type: query_parser.TypStr,
+					Str:  "FooBar",
+				},
+				&query_parser.Operand{
+					Type: query_parser.TypInt,
+					Int:  -1,
+				},
+			},
+			&query_parser.Operand{
+				Type: query_parser.TypStr,
+				Str:  "",
+			},
+		},
+		// StrReplace
+		functionsTest{
+			&query_parser.Function{
+				Name: "StrReplace",
+				Args: []*query_parser.Operand{
+					&query_parser.Operand{
+						Type: query_parser.TypStr,
+						Str:  "FooBar",
+					},
+					&query_parser.Operand{
+						Type: query_parser.TypStr,
+						Str:  "B",
+					},
+					&query_parser.Operand{
+						Type: query_parser.TypStr,
+						Str:  "ZZZ",
+					},
+				},
+				ArgTypes: []query_parser.OperandType{
+					query_parser.TypStr,
+					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:  "FooBar",
+				},
+				&query_parser.Operand{
+					Type: query_parser.TypStr,
+					Str:  "B",
+				},
+				&query_parser.Operand{
+					Type: query_parser.TypStr,
+					Str:  "ZZZ",
+				},
+			},
+			&query_parser.Operand{
+				Type: query_parser.TypStr,
+				Str:  "FooZZZar",
+			},
+		},
+		// StrReplace
+		functionsTest{
+			&query_parser.Function{
+				Name: "StrReplace",
+				Args: []*query_parser.Operand{
+					&query_parser.Operand{
+						Type: query_parser.TypStr,
+						Str:  "FooBar",
+					},
+					&query_parser.Operand{
+						Type: query_parser.TypStr,
+						Str:  "X",
+					},
+					&query_parser.Operand{
+						Type: query_parser.TypStr,
+						Str:  "ZZZ",
+					},
+				},
+				ArgTypes: []query_parser.OperandType{
+					query_parser.TypStr,
+					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:  "FooBar",
+				},
+				&query_parser.Operand{
+					Type: query_parser.TypStr,
+					Str:  "X",
+				},
+				&query_parser.Operand{
+					Type: query_parser.TypStr,
+					Str:  "ZZZ",
+				},
+			},
+			&query_parser.Operand{
+				Type: query_parser.TypStr,
+				Str:  "FooBar",
+			},
+		},
+		// Trim
+		functionsTest{
+			&query_parser.Function{
+				Name: "Trim",
+				Args: []*query_parser.Operand{
+					&query_parser.Operand{
+						Type: query_parser.TypStr,
+						Str:  "     Foo  ",
+					},
+				},
+				ArgTypes: []query_parser.OperandType{
+					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:  "Foo",
+			},
+		},
+		// Trim
+		functionsTest{
+			&query_parser.Function{
+				Name: "Trim",
+				Args: []*query_parser.Operand{
+					&query_parser.Operand{
+						Type: query_parser.TypStr,
+						Str:  "Foo",
+					},
+				},
+				ArgTypes: []query_parser.OperandType{
+					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:  "Foo",
+			},
+		},
+		// TrimLeft
+		functionsTest{
+			&query_parser.Function{
+				Name: "TrimLeft",
+				Args: []*query_parser.Operand{
+					&query_parser.Operand{
+						Type: query_parser.TypStr,
+						Str:  "     Foo  ",
+					},
+				},
+				ArgTypes: []query_parser.OperandType{
+					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:  "Foo  ",
+			},
+		},
+		// TrimLeft
+		functionsTest{
+			&query_parser.Function{
+				Name: "TrimLeft",
+				Args: []*query_parser.Operand{
+					&query_parser.Operand{
+						Type: query_parser.TypStr,
+						Str:  "Foo",
+					},
+				},
+				ArgTypes: []query_parser.OperandType{
+					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:  "Foo",
+			},
+		},
+		// TrimRight
+		functionsTest{
+			&query_parser.Function{
+				Name: "TrimRight",
+				Args: []*query_parser.Operand{
+					&query_parser.Operand{
+						Type: query_parser.TypStr,
+						Str:  "     Foo  ",
+					},
+				},
+				ArgTypes: []query_parser.OperandType{
+					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:  "     Foo",
+			},
+		},
+		// TrimLeft
+		functionsTest{
+			&query_parser.Function{
+				Name: "TrimLeft",
+				Args: []*query_parser.Operand{
+					&query_parser.Operand{
+						Type: query_parser.TypStr,
+						Str:  "Foo",
+					},
+				},
+				ArgTypes: []query_parser.OperandType{
+					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:  "Foo",
+			},
+		},
 	}
 
 	for _, test := range tests {
@@ -588,7 +1144,7 @@
 				RetType:  query_parser.TypTime,
 				Computed: false,
 				RetValue: nil,
-				Node:  query_parser.Node{Off: 42},
+				Node:     query_parser.Node{Off: 42},
 			},
 			[]*query_parser.Operand{
 				&query_parser.Operand{
@@ -602,7 +1158,7 @@
 
 	for _, test := range tests {
 		_, err := query_functions.ExecFunction(&db, test.f, test.args)
-                if verror.ErrorID(err) != verror.ErrorID(test.err) || err.Error() != test.err.Error() {
+		if verror.ErrorID(err) != verror.ErrorID(test.err) || err.Error() != test.err.Error() {
 			t.Errorf("function: %v; got %v, want %v", test.f, err, test.err)
 		}
 	}
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 ec20b59..9f00417 100644
--- a/v23/syncbase/nosql/internal/query/query_functions/str_funcs.go
+++ b/v23/syncbase/nosql/internal/query/query_functions/str_funcs.go
@@ -6,6 +6,7 @@
 
 import (
 	"strings"
+	"unicode"
 
 	"v.io/syncbase/v23/syncbase/nosql/internal/query/conversions"
 	"v.io/syncbase/v23/syncbase/nosql/internal/query/query_parser"
@@ -41,7 +42,7 @@
 // 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.
-// e.g., Split("abc.def.ghi", ".") an list of "abc", "def", "ghi"
+// e.g., Split("abc.def.ghi", ".") returns a list of "abc", "def", "ghi"
 func split(db query_db.Database, off int64, args []*query_parser.Operand) (*query_parser.Operand, error) {
 	strArg, err := conversions.ConvertValueToString(args[0])
 	if err != nil {
@@ -58,3 +59,121 @@
 	o.Object = vdl.ValueOf(strings.Split(strArg.Str, sepArg.Str))
 	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?
+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
+	}
+	right, err := conversions.ConvertValueToString(args[1])
+	if err != nil {
+		return nil, err
+	}
+	return makeStrOp(off, left.Str+right.Str), nil
+}
+
+// StrIndex(s, sep string) int
+// StrIndex returns the index of sep in s, or -1 is sep is not present in s.
+// e.g., StrIndex("abc", "bc") returns 1.
+func strIndex(db query_db.Database, off int64, args []*query_parser.Operand) (*query_parser.Operand, error) {
+	s, err := conversions.ConvertValueToString(args[0])
+	if err != nil {
+		return nil, err
+	}
+	sep, err := conversions.ConvertValueToString(args[1])
+	if err != nil {
+		return nil, err
+	}
+	return makeIntOp(off, int64(strings.Index(s.Str, sep.Str))), nil
+}
+
+// StrLastIndex(s, sep string) int
+// StrLastIndex returns the index of the last instance of sep in s, or -1 is sep is not present in s.
+// e.g., StrLastIndex("abcbc", "bc") returns 3.
+func strLastIndex(db query_db.Database, off int64, args []*query_parser.Operand) (*query_parser.Operand, error) {
+	s, err := conversions.ConvertValueToString(args[0])
+	if err != nil {
+		return nil, err
+	}
+	sep, err := conversions.ConvertValueToString(args[1])
+	if err != nil {
+		return nil, err
+	}
+	return makeIntOp(off, int64(strings.LastIndex(s.Str, sep.Str))), nil
+}
+
+// StrRepeat(s string, count int) int
+// StrRepeat returns a new string consisting of count copies of the string s.
+// e.g., StrRepeat("abc", 3) returns "abcabcabc".
+func strRepeat(db query_db.Database, off int64, args []*query_parser.Operand) (*query_parser.Operand, error) {
+	s, err := conversions.ConvertValueToString(args[0])
+	if err != nil {
+		return nil, err
+	}
+	count, err := conversions.ConvertValueToInt(args[1])
+	if err != nil {
+		return nil, err
+	}
+	if count.Int >= 0 {
+		return makeStrOp(off, strings.Repeat(s.Str, int(count.Int))), nil
+	} else {
+		// golang strings.Repeat doesn't like count < 0
+		return makeStrOp(off, ""), nil
+	}
+}
+
+// StrReplace(s, old, new string) string
+// StrReplace returns a copy of s with the first instance of old replaced by new.
+// e.g., StrReplace("abcdef", "bc", "zzzzz") returns "azzzzzdef".
+func strReplace(db query_db.Database, off int64, args []*query_parser.Operand) (*query_parser.Operand, error) {
+	s, err := conversions.ConvertValueToString(args[0])
+	if err != nil {
+		return nil, err
+	}
+	old, err := conversions.ConvertValueToString(args[1])
+	if err != nil {
+		return nil, err
+	}
+	new, err := conversions.ConvertValueToString(args[2])
+	if err != nil {
+		return nil, err
+	}
+	return makeStrOp(off, strings.Replace(s.Str, old.Str, new.Str, 1)), nil
+}
+
+// Trim(s string) string
+// Trim returns a copy of s with all leading and trailing white space removed, as defined by Unicode.
+// e.g., Trim(" abc ") returns "abc".
+func trim(db query_db.Database, off int64, args []*query_parser.Operand) (*query_parser.Operand, error) {
+	s, err := conversions.ConvertValueToString(args[0])
+	if err != nil {
+		return nil, err
+	}
+	return makeStrOp(off, strings.TrimSpace(s.Str)), nil
+}
+
+// TrimLeft(s string) string
+// TrimLeft returns a copy of s with all leading white space removed, as defined by Unicode.
+// e.g., TrimLeft(" abc ") returns "abc ".
+func trimLeft(db query_db.Database, off int64, args []*query_parser.Operand) (*query_parser.Operand, error) {
+	s, err := conversions.ConvertValueToString(args[0])
+	if err != nil {
+		return nil, err
+	}
+	return makeStrOp(off, strings.TrimLeftFunc(s.Str, unicode.IsSpace)), nil
+}
+
+// TrimRight(s string) string
+// TrimRight returns a copy of s with all leading white space removed, as defined by Unicode.
+// e.g., TrimRight(" abc ") returns "abc ".
+func trimRight(db query_db.Database, off int64, args []*query_parser.Operand) (*query_parser.Operand, error) {
+	s, err := conversions.ConvertValueToString(args[0])
+	if err != nil {
+		return nil, err
+	}
+	return makeStrOp(off, strings.TrimRightFunc(s.Str, unicode.IsSpace)), nil
+}
diff --git a/v23/syncbase/nosql/internal/query/test/query_test.go b/v23/syncbase/nosql/internal/query/test/query_test.go
index 61c320a..10564da 100644
--- a/v23/syncbase/nosql/internal/query/test/query_test.go
+++ b/v23/syncbase/nosql/internal/query/test/query_test.go
@@ -1936,6 +1936,70 @@
 				[]*vdl.Value{vdl.ValueOf(int64(2))},
 			},
 		},
+		{
+			// 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 AltoCA")},
+			},
+		},
+		{
+			// StrIndex
+			"select StrIndex(v.Address.City, \"lo\") from Customer where v.Name = \"John Smith\"",
+			[]string{"StrIndex"},
+			[][]*vdl.Value{
+				[]*vdl.Value{vdl.ValueOf(int64(2))},
+			},
+		},
+		{
+			// StrLastIndex
+			"select StrLastIndex(v.Address.City, \"l\") from Customer where v.Name = \"John Smith\"",
+			[]string{"StrLastIndex"},
+			[][]*vdl.Value{
+				[]*vdl.Value{vdl.ValueOf(int64(6))},
+			},
+		},
+		{
+			// StrRepeat
+			"select StrRepeat(v.Address.City, 3) from Customer where v.Name = \"John Smith\"",
+			[]string{"StrRepeat"},
+			[][]*vdl.Value{
+				[]*vdl.Value{vdl.ValueOf("Palo AltoPalo AltoPalo Alto")},
+			},
+		},
+		{
+			// StrReplace
+			"select StrReplace(v.Address.City, \"Palo\", \"Shallow\") from Customer where v.Name = \"John Smith\"",
+			[]string{"StrReplace"},
+			[][]*vdl.Value{
+				[]*vdl.Value{vdl.ValueOf("Shallow Alto")},
+			},
+		},
+		{
+			// Trim
+			"select Trim(\"   Foo     \") from Customer where v.Name = \"John Smith\"",
+			[]string{"Trim"},
+			[][]*vdl.Value{
+				[]*vdl.Value{vdl.ValueOf("Foo")},
+			},
+		},
+		{
+			// TrimLeft
+			"select TrimLeft(\"   Foo     \") from Customer where v.Name = \"John Smith\"",
+			[]string{"TrimLeft"},
+			[][]*vdl.Value{
+				[]*vdl.Value{vdl.ValueOf("Foo     ")},
+			},
+		},
+		{
+			// TrimRight
+			"select TrimRight(\"   Foo     \") from Customer where v.Name = \"John Smith\"",
+			[]string{"TrimRight"},
+			[][]*vdl.Value{
+				[]*vdl.Value{vdl.ValueOf("   Foo")},
+			},
+		},
 	}
 
 	for _, test := range basic {
diff --git a/v23/syncbase/nosql/syncql/syncql.vdl b/v23/syncbase/nosql/syncql/syncql.vdl
index 4fda53f..fa7ca31 100644
--- a/v23/syncbase/nosql/syncql/syncql.vdl
+++ b/v23/syncbase/nosql/syncql/syncql.vdl
@@ -72,6 +72,9 @@
 	FloatConversionError(off int64, err error) {
 		"en": "[{off}]Can't convert to float: {err}.",
 	}
+	IntConversionError(off int64, err error) {
+		"en": "[{off}]Can't convert to int: {err}.",
+	}
 	IsIsNotRequireLhsValue(off int64) {
 		"en": "[{off}]'Is/is not' expressions require left operand to be a value operand.",
 	}
diff --git a/v23/syncbase/nosql/syncql/syncql.vdl.go b/v23/syncbase/nosql/syncql/syncql.vdl.go
index 0b5babc..fe7b6f0 100644
--- a/v23/syncbase/nosql/syncql/syncql.vdl.go
+++ b/v23/syncbase/nosql/syncql/syncql.vdl.go
@@ -37,6 +37,7 @@
 	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}.")
 	ErrFloatConversionError            = verror.Register("v.io/syncbase/v23/syncbase/nosql/syncql.FloatConversionError", verror.NoRetry, "{1:}{2:} [{3}]Can't convert to float: {4}.")
+	ErrIntConversionError              = verror.Register("v.io/syncbase/v23/syncbase/nosql/syncql.IntConversionError", verror.NoRetry, "{1:}{2:} [{3}]Can't convert to int: {4}.")
 	ErrIsIsNotRequireLhsValue          = verror.Register("v.io/syncbase/v23/syncbase/nosql/syncql.IsIsNotRequireLhsValue", verror.NoRetry, "{1:}{2:} [{3}]'Is/is not' expressions require left operand to be a value operand.")
 	ErrIsIsNotRequireRhsNil            = verror.Register("v.io/syncbase/v23/syncbase/nosql/syncql.IsIsNotRequireRhsNil", verror.NoRetry, "{1:}{2:} [{3}]'Is/is not' expressions require right operand to be nil.")
 	ErrInvalidEscapedChar              = verror.Register("v.io/syncbase/v23/syncbase/nosql/syncql.InvalidEscapedChar", verror.NoRetry, "{1:}{2:} [{3}Expected backslash, percent, or underscore after backslash.]")
@@ -80,6 +81,7 @@
 	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}.")
 	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrFloatConversionError.ID), "{1:}{2:} [{3}]Can't convert to float: {4}.")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrIntConversionError.ID), "{1:}{2:} [{3}]Can't convert to int: {4}.")
 	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrIsIsNotRequireLhsValue.ID), "{1:}{2:} [{3}]'Is/is not' expressions require left operand to be a value operand.")
 	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrIsIsNotRequireRhsNil.ID), "{1:}{2:} [{3}]'Is/is not' expressions require right operand to be nil.")
 	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrInvalidEscapedChar.ID), "{1:}{2:} [{3}Expected backslash, percent, or underscore after backslash.]")
@@ -210,6 +212,11 @@
 	return verror.New(ErrFloatConversionError, ctx, off, err)
 }
 
+// NewErrIntConversionError returns an error with the ErrIntConversionError ID.
+func NewErrIntConversionError(ctx *context.T, off int64, err error) error {
+	return verror.New(ErrIntConversionError, ctx, off, err)
+}
+
 // NewErrIsIsNotRequireLhsValue returns an error with the ErrIsIsNotRequireLhsValue ID.
 func NewErrIsIsNotRequireLhsValue(ctx *context.T, off int64) error {
 	return verror.New(ErrIsIsNotRequireLhsValue, ctx, off)