string_concatenation_test.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. package engine
  2. import (
  3. "context"
  4. "testing"
  5. )
  6. // TestSQLEngine_StringConcatenationWithLiterals tests string concatenation with || operator
  7. // This covers the user's reported issue where string literals were being lost
  8. func TestSQLEngine_StringConcatenationWithLiterals(t *testing.T) {
  9. engine := NewTestSQLEngine()
  10. tests := []struct {
  11. name string
  12. query string
  13. expectedCols []string
  14. validateFirst func(t *testing.T, row []string)
  15. }{
  16. {
  17. name: "Simple concatenation with literals",
  18. query: "SELECT 'test' || action || 'end' FROM user_events LIMIT 1",
  19. expectedCols: []string{"'test'||action||'end'"},
  20. validateFirst: func(t *testing.T, row []string) {
  21. expected := "testloginend" // action="login" from first row
  22. if row[0] != expected {
  23. t.Errorf("Expected %s, got %s", expected, row[0])
  24. }
  25. },
  26. },
  27. {
  28. name: "User's original complex concatenation",
  29. query: "SELECT 'test' || action || 'xxx' || action || ' ~~~ ' || status FROM user_events LIMIT 1",
  30. expectedCols: []string{"'test'||action||'xxx'||action||'~~~'||status"},
  31. validateFirst: func(t *testing.T, row []string) {
  32. // First row: action="login", status="active"
  33. expected := "testloginxxxlogin ~~~ active"
  34. if row[0] != expected {
  35. t.Errorf("Expected %s, got %s", expected, row[0])
  36. }
  37. },
  38. },
  39. {
  40. name: "Mixed columns and literals",
  41. query: "SELECT status || '=' || action, 'prefix:' || user_type FROM user_events LIMIT 1",
  42. expectedCols: []string{"status||'='||action", "'prefix:'||user_type"},
  43. validateFirst: func(t *testing.T, row []string) {
  44. // First row: status="active", action="login", user_type="premium"
  45. if row[0] != "active=login" {
  46. t.Errorf("Expected 'active=login', got %s", row[0])
  47. }
  48. if row[1] != "prefix:premium" {
  49. t.Errorf("Expected 'prefix:premium', got %s", row[1])
  50. }
  51. },
  52. },
  53. {
  54. name: "Concatenation with spaces in literals",
  55. query: "SELECT ' [ ' || status || ' ] ' FROM user_events LIMIT 2",
  56. expectedCols: []string{"'['||status||']'"},
  57. validateFirst: func(t *testing.T, row []string) {
  58. expected := " [ active ] " // status="active" from first row
  59. if row[0] != expected {
  60. t.Errorf("Expected '%s', got '%s'", expected, row[0])
  61. }
  62. },
  63. },
  64. }
  65. for _, tt := range tests {
  66. t.Run(tt.name, func(t *testing.T) {
  67. result, err := engine.ExecuteSQL(context.Background(), tt.query)
  68. if err != nil {
  69. t.Fatalf("Query failed: %v", err)
  70. }
  71. if result.Error != nil {
  72. t.Fatalf("Query returned error: %v", result.Error)
  73. }
  74. // Verify we got results
  75. if len(result.Rows) == 0 {
  76. t.Fatal("Query returned no rows")
  77. }
  78. // Verify column count
  79. if len(result.Columns) != len(tt.expectedCols) {
  80. t.Errorf("Expected %d columns, got %d", len(tt.expectedCols), len(result.Columns))
  81. }
  82. // Check column names
  83. for i, expectedCol := range tt.expectedCols {
  84. if i < len(result.Columns) && result.Columns[i] != expectedCol {
  85. t.Logf("Expected column %d to be '%s', got '%s'", i, expectedCol, result.Columns[i])
  86. // Don't fail on column name formatting differences, just log
  87. }
  88. }
  89. // Validate first row
  90. if tt.validateFirst != nil {
  91. firstRow := result.Rows[0]
  92. stringRow := make([]string, len(firstRow))
  93. for i, val := range firstRow {
  94. stringRow[i] = val.ToString()
  95. }
  96. tt.validateFirst(t, stringRow)
  97. }
  98. // Log results for debugging
  99. t.Logf("Query: %s", tt.query)
  100. t.Logf("Columns: %v", result.Columns)
  101. for i, row := range result.Rows {
  102. values := make([]string, len(row))
  103. for j, val := range row {
  104. values[j] = val.ToString()
  105. }
  106. t.Logf("Row %d: %v", i, values)
  107. }
  108. })
  109. }
  110. }
  111. // TestSQLEngine_StringConcatenationBugReproduction tests the exact user query that was failing
  112. func TestSQLEngine_StringConcatenationBugReproduction(t *testing.T) {
  113. engine := NewTestSQLEngine()
  114. // This is the EXACT query from the user that was showing incorrect results
  115. query := "SELECT UPPER(status), id*2, 'test' || action || 'xxx' || action || ' ~~~ ' || status FROM user_events LIMIT 2"
  116. result, err := engine.ExecuteSQL(context.Background(), query)
  117. if err != nil {
  118. t.Fatalf("Query failed: %v", err)
  119. }
  120. if result.Error != nil {
  121. t.Fatalf("Query returned error: %v", result.Error)
  122. }
  123. // Key assertions that would fail with the original bug:
  124. // 1. Must return rows
  125. if len(result.Rows) != 2 {
  126. t.Errorf("Expected 2 rows, got %d", len(result.Rows))
  127. }
  128. // 2. Must have 3 columns
  129. expectedColumns := 3
  130. if len(result.Columns) != expectedColumns {
  131. t.Errorf("Expected %d columns, got %d", expectedColumns, len(result.Columns))
  132. }
  133. // 3. Verify the complex concatenation works correctly
  134. if len(result.Rows) >= 1 {
  135. firstRow := result.Rows[0]
  136. // Column 0: UPPER(status) should be "ACTIVE"
  137. upperStatus := firstRow[0].ToString()
  138. if upperStatus != "ACTIVE" {
  139. t.Errorf("Expected UPPER(status)='ACTIVE', got '%s'", upperStatus)
  140. }
  141. // Column 1: id*2 should be calculated correctly
  142. idTimes2 := firstRow[1].ToString()
  143. if idTimes2 != "164920" { // id=82460 * 2
  144. t.Errorf("Expected id*2=164920, got '%s'", idTimes2)
  145. }
  146. // Column 2: Complex concatenation should include all parts
  147. concatenated := firstRow[2].ToString()
  148. // Should be: "test" + "login" + "xxx" + "login" + " ~~~ " + "active" = "testloginxxxlogin ~~~ active"
  149. expected := "testloginxxxlogin ~~~ active"
  150. if concatenated != expected {
  151. t.Errorf("String concatenation failed. Expected '%s', got '%s'", expected, concatenated)
  152. }
  153. // CRITICAL: Must not be the buggy result like "viewviewpending"
  154. if concatenated == "loginloginactive" || concatenated == "viewviewpending" || concatenated == "clickclickfailed" {
  155. t.Errorf("CRITICAL BUG: String concatenation returned buggy result '%s' - string literals are being lost!", concatenated)
  156. }
  157. }
  158. t.Logf("✅ SUCCESS: Complex string concatenation works correctly!")
  159. t.Logf("Query: %s", query)
  160. for i, row := range result.Rows {
  161. values := make([]string, len(row))
  162. for j, val := range row {
  163. values[j] = val.ToString()
  164. }
  165. t.Logf("Row %d: %v", i, values)
  166. }
  167. }