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)