From d27c633e1dd6e20f708efa4f53a98111d19f1f7b Mon Sep 17 00:00:00 2001
From: Pijus Kamandulis <pkpjuklas@gmail.com>
Date: Tue, 18 Feb 2025 20:11:11 +0200
Subject: [PATCH] Better handling when passing null to string functions

---
 .../memory_executor/string_functions.go       | 111 ++++++++++++++----
 1 file changed, 86 insertions(+), 25 deletions(-)

diff --git a/query_executors/memory_executor/string_functions.go b/query_executors/memory_executor/string_functions.go
index ada9b76..93525e9 100644
--- a/query_executors/memory_executor/string_functions.go
+++ b/query_executors/memory_executor/string_functions.go
@@ -9,10 +9,14 @@ import (
 )
 
 func (r rowContext) strings_StringEquals(arguments []interface{}) bool {
-	str1 := r.parseString(arguments[0])
-	str2 := r.parseString(arguments[1])
+	str1, str1ok := r.parseString(arguments[0])
+	str2, str2ok := r.parseString(arguments[1])
 	ignoreCase := r.getBoolFlag(arguments)
 
+	if !str1ok || !str2ok {
+		return false
+	}
+
 	if ignoreCase {
 		return strings.EqualFold(str1, str2)
 	}
@@ -21,10 +25,14 @@ func (r rowContext) strings_StringEquals(arguments []interface{}) bool {
 }
 
 func (r rowContext) strings_Contains(arguments []interface{}) bool {
-	str1 := r.parseString(arguments[0])
-	str2 := r.parseString(arguments[1])
+	str1, str1ok := r.parseString(arguments[0])
+	str2, str2ok := r.parseString(arguments[1])
 	ignoreCase := r.getBoolFlag(arguments)
 
+	if !str1ok || !str2ok {
+		return false
+	}
+
 	if ignoreCase {
 		str1 = strings.ToLower(str1)
 		str2 = strings.ToLower(str2)
@@ -34,10 +42,14 @@ func (r rowContext) strings_Contains(arguments []interface{}) bool {
 }
 
 func (r rowContext) strings_EndsWith(arguments []interface{}) bool {
-	str1 := r.parseString(arguments[0])
-	str2 := r.parseString(arguments[1])
+	str1, str1ok := r.parseString(arguments[0])
+	str2, str2ok := r.parseString(arguments[1])
 	ignoreCase := r.getBoolFlag(arguments)
 
+	if !str1ok || !str2ok {
+		return false
+	}
+
 	if ignoreCase {
 		str1 = strings.ToLower(str1)
 		str2 = strings.ToLower(str2)
@@ -47,10 +59,14 @@ func (r rowContext) strings_EndsWith(arguments []interface{}) bool {
 }
 
 func (r rowContext) strings_StartsWith(arguments []interface{}) bool {
-	str1 := r.parseString(arguments[0])
-	str2 := r.parseString(arguments[1])
+	str1, str1ok := r.parseString(arguments[0])
+	str2, str2ok := r.parseString(arguments[1])
 	ignoreCase := r.getBoolFlag(arguments)
 
+	if !str1ok || !str2ok {
+		return false
+	}
+
 	if ignoreCase {
 		str1 = strings.ToLower(str1)
 		str2 = strings.ToLower(str2)
@@ -73,8 +89,12 @@ func (r rowContext) strings_Concat(arguments []interface{}) string {
 }
 
 func (r rowContext) strings_IndexOf(arguments []interface{}) int {
-	str1 := r.parseString(arguments[0])
-	str2 := r.parseString(arguments[1])
+	str1, str1ok := r.parseString(arguments[0])
+	str2, str2ok := r.parseString(arguments[1])
+
+	if !str1ok || !str2ok {
+		return -1
+	}
 
 	start := 0
 	if len(arguments) > 2 && arguments[2] != nil {
@@ -115,9 +135,13 @@ func (r rowContext) strings_Lower(arguments []interface{}) string {
 func (r rowContext) strings_Left(arguments []interface{}) string {
 	var ok bool
 	var length int
-	str := r.parseString(arguments[0])
+	str, strOk := r.parseString(arguments[0])
 	lengthEx := r.resolveSelectItem(arguments[1].(parsers.SelectItem))
 
+	if !strOk {
+		return ""
+	}
+
 	if length, ok = lengthEx.(int); !ok {
 		logger.ErrorLn("strings_Left - got parameters of wrong type")
 		return ""
@@ -135,28 +159,45 @@ func (r rowContext) strings_Left(arguments []interface{}) string {
 }
 
 func (r rowContext) strings_Length(arguments []interface{}) int {
-	str := r.parseString(arguments[0])
+	str, strOk := r.parseString(arguments[0])
+	if !strOk {
+		return 0
+	}
+
 	return len(str)
 }
 
 func (r rowContext) strings_LTrim(arguments []interface{}) string {
-	str := r.parseString(arguments[0])
+	str, strOk := r.parseString(arguments[0])
+	if !strOk {
+		return ""
+	}
+
 	return strings.TrimLeft(str, " ")
 }
 
 func (r rowContext) strings_Replace(arguments []interface{}) string {
-	str := r.parseString(arguments[0])
-	oldStr := r.parseString(arguments[1])
-	newStr := r.parseString(arguments[2])
+	str, strOk := r.parseString(arguments[0])
+	oldStr, oldStrOk := r.parseString(arguments[1])
+	newStr, newStrOk := r.parseString(arguments[2])
+
+	if !strOk || !oldStrOk || !newStrOk {
+		return ""
+	}
+
 	return strings.Replace(str, oldStr, newStr, -1)
 }
 
 func (r rowContext) strings_Replicate(arguments []interface{}) string {
 	var ok bool
 	var times int
-	str := r.parseString(arguments[0])
+	str, strOk := r.parseString(arguments[0])
 	timesEx := r.resolveSelectItem(arguments[1].(parsers.SelectItem))
 
+	if !strOk {
+		return ""
+	}
+
 	if times, ok = timesEx.(int); !ok {
 		logger.ErrorLn("strings_Replicate - got parameters of wrong type")
 		return ""
@@ -174,9 +215,13 @@ func (r rowContext) strings_Replicate(arguments []interface{}) string {
 }
 
 func (r rowContext) strings_Reverse(arguments []interface{}) string {
-	str := r.parseString(arguments[0])
+	str, strOk := r.parseString(arguments[0])
 	runes := []rune(str)
 
+	if !strOk {
+		return ""
+	}
+
 	for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
 		runes[i], runes[j] = runes[j], runes[i]
 	}
@@ -187,9 +232,13 @@ func (r rowContext) strings_Reverse(arguments []interface{}) string {
 func (r rowContext) strings_Right(arguments []interface{}) string {
 	var ok bool
 	var length int
-	str := r.parseString(arguments[0])
+	str, strOk := r.parseString(arguments[0])
 	lengthEx := r.resolveSelectItem(arguments[1].(parsers.SelectItem))
 
+	if !strOk {
+		return ""
+	}
+
 	if length, ok = lengthEx.(int); !ok {
 		logger.ErrorLn("strings_Right - got parameters of wrong type")
 		return ""
@@ -207,7 +256,11 @@ func (r rowContext) strings_Right(arguments []interface{}) string {
 }
 
 func (r rowContext) strings_RTrim(arguments []interface{}) string {
-	str := r.parseString(arguments[0])
+	str, strOk := r.parseString(arguments[0])
+	if !strOk {
+		return ""
+	}
+
 	return strings.TrimRight(str, " ")
 }
 
@@ -215,10 +268,14 @@ func (r rowContext) strings_Substring(arguments []interface{}) string {
 	var ok bool
 	var startPos int
 	var length int
-	str := r.parseString(arguments[0])
+	str, strOk := r.parseString(arguments[0])
 	startPosEx := r.resolveSelectItem(arguments[1].(parsers.SelectItem))
 	lengthEx := r.resolveSelectItem(arguments[2].(parsers.SelectItem))
 
+	if !strOk {
+		return ""
+	}
+
 	if startPos, ok = startPosEx.(int); !ok {
 		logger.ErrorLn("strings_Substring - got start parameters of wrong type")
 		return ""
@@ -241,7 +298,11 @@ func (r rowContext) strings_Substring(arguments []interface{}) string {
 }
 
 func (r rowContext) strings_Trim(arguments []interface{}) string {
-	str := r.parseString(arguments[0])
+	str, strOk := r.parseString(arguments[0])
+	if !strOk {
+		return ""
+	}
+
 	return strings.TrimSpace(str)
 }
 
@@ -257,15 +318,15 @@ func (r rowContext) getBoolFlag(arguments []interface{}) bool {
 	return ignoreCase
 }
 
-func (r rowContext) parseString(argument interface{}) string {
+func (r rowContext) parseString(argument interface{}) (value string, ok bool) {
 	exItem := argument.(parsers.SelectItem)
 	ex := r.resolveSelectItem(exItem)
 	if str1, ok := ex.(string); ok {
-		return str1
+		return str1, true
 	}
 
 	logger.ErrorLn("StringEquals got parameters of wrong type")
-	return ""
+	return "", false
 }
 
 func convertToString(value interface{}) string {