string_functions_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. package engine
  2. import (
  3. "context"
  4. "testing"
  5. "github.com/seaweedfs/seaweedfs/weed/pb/schema_pb"
  6. )
  7. func TestStringFunctions(t *testing.T) {
  8. engine := NewTestSQLEngine()
  9. t.Run("LENGTH function tests", func(t *testing.T) {
  10. tests := []struct {
  11. name string
  12. value *schema_pb.Value
  13. expected int64
  14. expectErr bool
  15. }{
  16. {
  17. name: "Length of string",
  18. value: &schema_pb.Value{Kind: &schema_pb.Value_StringValue{StringValue: "Hello World"}},
  19. expected: 11,
  20. expectErr: false,
  21. },
  22. {
  23. name: "Length of empty string",
  24. value: &schema_pb.Value{Kind: &schema_pb.Value_StringValue{StringValue: ""}},
  25. expected: 0,
  26. expectErr: false,
  27. },
  28. {
  29. name: "Length of number",
  30. value: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 12345}},
  31. expected: 5,
  32. expectErr: false,
  33. },
  34. {
  35. name: "Length of null value",
  36. value: nil,
  37. expected: 0,
  38. expectErr: true,
  39. },
  40. }
  41. for _, tt := range tests {
  42. t.Run(tt.name, func(t *testing.T) {
  43. result, err := engine.Length(tt.value)
  44. if tt.expectErr {
  45. if err == nil {
  46. t.Errorf("Expected error but got none")
  47. }
  48. return
  49. }
  50. if err != nil {
  51. t.Errorf("Unexpected error: %v", err)
  52. return
  53. }
  54. intVal, ok := result.Kind.(*schema_pb.Value_Int64Value)
  55. if !ok {
  56. t.Errorf("LENGTH should return int64 value, got %T", result.Kind)
  57. return
  58. }
  59. if intVal.Int64Value != tt.expected {
  60. t.Errorf("Expected %d, got %d", tt.expected, intVal.Int64Value)
  61. }
  62. })
  63. }
  64. })
  65. t.Run("UPPER/LOWER function tests", func(t *testing.T) {
  66. // Test UPPER
  67. result, err := engine.Upper(&schema_pb.Value{Kind: &schema_pb.Value_StringValue{StringValue: "Hello World"}})
  68. if err != nil {
  69. t.Errorf("UPPER failed: %v", err)
  70. }
  71. stringVal, _ := result.Kind.(*schema_pb.Value_StringValue)
  72. if stringVal.StringValue != "HELLO WORLD" {
  73. t.Errorf("Expected 'HELLO WORLD', got '%s'", stringVal.StringValue)
  74. }
  75. // Test LOWER
  76. result, err = engine.Lower(&schema_pb.Value{Kind: &schema_pb.Value_StringValue{StringValue: "Hello World"}})
  77. if err != nil {
  78. t.Errorf("LOWER failed: %v", err)
  79. }
  80. stringVal, _ = result.Kind.(*schema_pb.Value_StringValue)
  81. if stringVal.StringValue != "hello world" {
  82. t.Errorf("Expected 'hello world', got '%s'", stringVal.StringValue)
  83. }
  84. })
  85. t.Run("TRIM function tests", func(t *testing.T) {
  86. tests := []struct {
  87. name string
  88. function func(*schema_pb.Value) (*schema_pb.Value, error)
  89. input string
  90. expected string
  91. }{
  92. {"TRIM whitespace", engine.Trim, " Hello World ", "Hello World"},
  93. {"LTRIM whitespace", engine.LTrim, " Hello World ", "Hello World "},
  94. {"RTRIM whitespace", engine.RTrim, " Hello World ", " Hello World"},
  95. {"TRIM with tabs and newlines", engine.Trim, "\t\nHello\t\n", "Hello"},
  96. }
  97. for _, tt := range tests {
  98. t.Run(tt.name, func(t *testing.T) {
  99. result, err := tt.function(&schema_pb.Value{Kind: &schema_pb.Value_StringValue{StringValue: tt.input}})
  100. if err != nil {
  101. t.Errorf("Function failed: %v", err)
  102. return
  103. }
  104. stringVal, ok := result.Kind.(*schema_pb.Value_StringValue)
  105. if !ok {
  106. t.Errorf("Function should return string value, got %T", result.Kind)
  107. return
  108. }
  109. if stringVal.StringValue != tt.expected {
  110. t.Errorf("Expected '%s', got '%s'", tt.expected, stringVal.StringValue)
  111. }
  112. })
  113. }
  114. })
  115. t.Run("SUBSTRING function tests", func(t *testing.T) {
  116. testStr := &schema_pb.Value{Kind: &schema_pb.Value_StringValue{StringValue: "Hello World"}}
  117. // Test substring with start and length
  118. result, err := engine.Substring(testStr,
  119. &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 7}},
  120. &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 5}})
  121. if err != nil {
  122. t.Errorf("SUBSTRING failed: %v", err)
  123. }
  124. stringVal, _ := result.Kind.(*schema_pb.Value_StringValue)
  125. if stringVal.StringValue != "World" {
  126. t.Errorf("Expected 'World', got '%s'", stringVal.StringValue)
  127. }
  128. // Test substring with just start position
  129. result, err = engine.Substring(testStr,
  130. &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 7}})
  131. if err != nil {
  132. t.Errorf("SUBSTRING failed: %v", err)
  133. }
  134. stringVal, _ = result.Kind.(*schema_pb.Value_StringValue)
  135. if stringVal.StringValue != "World" {
  136. t.Errorf("Expected 'World', got '%s'", stringVal.StringValue)
  137. }
  138. })
  139. t.Run("CONCAT function tests", func(t *testing.T) {
  140. result, err := engine.Concat(
  141. &schema_pb.Value{Kind: &schema_pb.Value_StringValue{StringValue: "Hello"}},
  142. &schema_pb.Value{Kind: &schema_pb.Value_StringValue{StringValue: " "}},
  143. &schema_pb.Value{Kind: &schema_pb.Value_StringValue{StringValue: "World"}},
  144. )
  145. if err != nil {
  146. t.Errorf("CONCAT failed: %v", err)
  147. }
  148. stringVal, _ := result.Kind.(*schema_pb.Value_StringValue)
  149. if stringVal.StringValue != "Hello World" {
  150. t.Errorf("Expected 'Hello World', got '%s'", stringVal.StringValue)
  151. }
  152. // Test with mixed types
  153. result, err = engine.Concat(
  154. &schema_pb.Value{Kind: &schema_pb.Value_StringValue{StringValue: "Number: "}},
  155. &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 42}},
  156. )
  157. if err != nil {
  158. t.Errorf("CONCAT failed: %v", err)
  159. }
  160. stringVal, _ = result.Kind.(*schema_pb.Value_StringValue)
  161. if stringVal.StringValue != "Number: 42" {
  162. t.Errorf("Expected 'Number: 42', got '%s'", stringVal.StringValue)
  163. }
  164. })
  165. t.Run("REPLACE function tests", func(t *testing.T) {
  166. result, err := engine.Replace(
  167. &schema_pb.Value{Kind: &schema_pb.Value_StringValue{StringValue: "Hello World World"}},
  168. &schema_pb.Value{Kind: &schema_pb.Value_StringValue{StringValue: "World"}},
  169. &schema_pb.Value{Kind: &schema_pb.Value_StringValue{StringValue: "Universe"}},
  170. )
  171. if err != nil {
  172. t.Errorf("REPLACE failed: %v", err)
  173. }
  174. stringVal, _ := result.Kind.(*schema_pb.Value_StringValue)
  175. if stringVal.StringValue != "Hello Universe Universe" {
  176. t.Errorf("Expected 'Hello Universe Universe', got '%s'", stringVal.StringValue)
  177. }
  178. })
  179. t.Run("POSITION function tests", func(t *testing.T) {
  180. result, err := engine.Position(
  181. &schema_pb.Value{Kind: &schema_pb.Value_StringValue{StringValue: "World"}},
  182. &schema_pb.Value{Kind: &schema_pb.Value_StringValue{StringValue: "Hello World"}},
  183. )
  184. if err != nil {
  185. t.Errorf("POSITION failed: %v", err)
  186. }
  187. intVal, _ := result.Kind.(*schema_pb.Value_Int64Value)
  188. if intVal.Int64Value != 7 {
  189. t.Errorf("Expected 7, got %d", intVal.Int64Value)
  190. }
  191. // Test not found
  192. result, err = engine.Position(
  193. &schema_pb.Value{Kind: &schema_pb.Value_StringValue{StringValue: "NotFound"}},
  194. &schema_pb.Value{Kind: &schema_pb.Value_StringValue{StringValue: "Hello World"}},
  195. )
  196. if err != nil {
  197. t.Errorf("POSITION failed: %v", err)
  198. }
  199. intVal, _ = result.Kind.(*schema_pb.Value_Int64Value)
  200. if intVal.Int64Value != 0 {
  201. t.Errorf("Expected 0 for not found, got %d", intVal.Int64Value)
  202. }
  203. })
  204. t.Run("LEFT/RIGHT function tests", func(t *testing.T) {
  205. testStr := &schema_pb.Value{Kind: &schema_pb.Value_StringValue{StringValue: "Hello World"}}
  206. // Test LEFT
  207. result, err := engine.Left(testStr, &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 5}})
  208. if err != nil {
  209. t.Errorf("LEFT failed: %v", err)
  210. }
  211. stringVal, _ := result.Kind.(*schema_pb.Value_StringValue)
  212. if stringVal.StringValue != "Hello" {
  213. t.Errorf("Expected 'Hello', got '%s'", stringVal.StringValue)
  214. }
  215. // Test RIGHT
  216. result, err = engine.Right(testStr, &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 5}})
  217. if err != nil {
  218. t.Errorf("RIGHT failed: %v", err)
  219. }
  220. stringVal, _ = result.Kind.(*schema_pb.Value_StringValue)
  221. if stringVal.StringValue != "World" {
  222. t.Errorf("Expected 'World', got '%s'", stringVal.StringValue)
  223. }
  224. })
  225. t.Run("REVERSE function tests", func(t *testing.T) {
  226. result, err := engine.Reverse(&schema_pb.Value{Kind: &schema_pb.Value_StringValue{StringValue: "Hello"}})
  227. if err != nil {
  228. t.Errorf("REVERSE failed: %v", err)
  229. }
  230. stringVal, _ := result.Kind.(*schema_pb.Value_StringValue)
  231. if stringVal.StringValue != "olleH" {
  232. t.Errorf("Expected 'olleH', got '%s'", stringVal.StringValue)
  233. }
  234. // Test with Unicode
  235. result, err = engine.Reverse(&schema_pb.Value{Kind: &schema_pb.Value_StringValue{StringValue: "🙂👍"}})
  236. if err != nil {
  237. t.Errorf("REVERSE failed: %v", err)
  238. }
  239. stringVal, _ = result.Kind.(*schema_pb.Value_StringValue)
  240. if stringVal.StringValue != "👍🙂" {
  241. t.Errorf("Expected '👍🙂', got '%s'", stringVal.StringValue)
  242. }
  243. })
  244. }
  245. // TestStringFunctionsSQL tests string functions through SQL execution
  246. func TestStringFunctionsSQL(t *testing.T) {
  247. engine := NewTestSQLEngine()
  248. testCases := []struct {
  249. name string
  250. sql string
  251. expectError bool
  252. expectedVal string
  253. }{
  254. {
  255. name: "UPPER function",
  256. sql: "SELECT UPPER('hello world') AS upper_value FROM user_events LIMIT 1",
  257. expectError: false,
  258. expectedVal: "HELLO WORLD",
  259. },
  260. {
  261. name: "LOWER function",
  262. sql: "SELECT LOWER('HELLO WORLD') AS lower_value FROM user_events LIMIT 1",
  263. expectError: false,
  264. expectedVal: "hello world",
  265. },
  266. {
  267. name: "LENGTH function",
  268. sql: "SELECT LENGTH('hello') AS length_value FROM user_events LIMIT 1",
  269. expectError: false,
  270. expectedVal: "5",
  271. },
  272. {
  273. name: "TRIM function",
  274. sql: "SELECT TRIM(' hello world ') AS trimmed_value FROM user_events LIMIT 1",
  275. expectError: false,
  276. expectedVal: "hello world",
  277. },
  278. {
  279. name: "LTRIM function",
  280. sql: "SELECT LTRIM(' hello world ') AS ltrimmed_value FROM user_events LIMIT 1",
  281. expectError: false,
  282. expectedVal: "hello world ",
  283. },
  284. {
  285. name: "RTRIM function",
  286. sql: "SELECT RTRIM(' hello world ') AS rtrimmed_value FROM user_events LIMIT 1",
  287. expectError: false,
  288. expectedVal: " hello world",
  289. },
  290. {
  291. name: "Multiple string functions",
  292. sql: "SELECT UPPER('hello') AS up, LOWER('WORLD') AS low, LENGTH('test') AS len FROM user_events LIMIT 1",
  293. expectError: false,
  294. expectedVal: "", // We'll check this separately
  295. },
  296. {
  297. name: "String function with wrong argument count",
  298. sql: "SELECT UPPER('hello', 'extra') FROM user_events LIMIT 1",
  299. expectError: true,
  300. expectedVal: "",
  301. },
  302. {
  303. name: "String function with no arguments",
  304. sql: "SELECT UPPER() FROM user_events LIMIT 1",
  305. expectError: true,
  306. expectedVal: "",
  307. },
  308. }
  309. for _, tc := range testCases {
  310. t.Run(tc.name, func(t *testing.T) {
  311. result, err := engine.ExecuteSQL(context.Background(), tc.sql)
  312. if tc.expectError {
  313. if err == nil && result.Error == nil {
  314. t.Errorf("Expected error but got none")
  315. }
  316. return
  317. }
  318. if err != nil {
  319. t.Errorf("Unexpected error: %v", err)
  320. return
  321. }
  322. if result.Error != nil {
  323. t.Errorf("Query result has error: %v", result.Error)
  324. return
  325. }
  326. if len(result.Rows) == 0 {
  327. t.Fatal("Expected at least one row")
  328. }
  329. if tc.name == "Multiple string functions" {
  330. // Special case for multiple functions test
  331. if len(result.Rows[0]) != 3 {
  332. t.Fatalf("Expected 3 columns, got %d", len(result.Rows[0]))
  333. }
  334. // Check UPPER('hello') -> 'HELLO'
  335. if result.Rows[0][0].ToString() != "HELLO" {
  336. t.Errorf("Expected 'HELLO', got '%s'", result.Rows[0][0].ToString())
  337. }
  338. // Check LOWER('WORLD') -> 'world'
  339. if result.Rows[0][1].ToString() != "world" {
  340. t.Errorf("Expected 'world', got '%s'", result.Rows[0][1].ToString())
  341. }
  342. // Check LENGTH('test') -> '4'
  343. if result.Rows[0][2].ToString() != "4" {
  344. t.Errorf("Expected '4', got '%s'", result.Rows[0][2].ToString())
  345. }
  346. } else {
  347. actualVal := result.Rows[0][0].ToString()
  348. if actualVal != tc.expectedVal {
  349. t.Errorf("Expected '%s', got '%s'", tc.expectedVal, actualVal)
  350. }
  351. }
  352. })
  353. }
  354. }