sql_alias_support_test.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. package engine
  2. import (
  3. "testing"
  4. "github.com/seaweedfs/seaweedfs/weed/pb/schema_pb"
  5. "github.com/stretchr/testify/assert"
  6. )
  7. // TestSQLAliasResolution tests the complete SQL alias resolution functionality
  8. func TestSQLAliasResolution(t *testing.T) {
  9. engine := NewTestSQLEngine()
  10. t.Run("ResolveColumnAlias", func(t *testing.T) {
  11. // Test the helper function for resolving aliases
  12. // Create SELECT expressions with aliases
  13. selectExprs := []SelectExpr{
  14. &AliasedExpr{
  15. Expr: &ColName{Name: stringValue("_timestamp_ns")},
  16. As: aliasValue("ts"),
  17. },
  18. &AliasedExpr{
  19. Expr: &ColName{Name: stringValue("id")},
  20. As: aliasValue("record_id"),
  21. },
  22. }
  23. // Test alias resolution
  24. resolved := engine.resolveColumnAlias("ts", selectExprs)
  25. assert.Equal(t, "_timestamp_ns", resolved, "Should resolve 'ts' alias to '_timestamp_ns'")
  26. resolved = engine.resolveColumnAlias("record_id", selectExprs)
  27. assert.Equal(t, "id", resolved, "Should resolve 'record_id' alias to 'id'")
  28. // Test non-aliased column (should return as-is)
  29. resolved = engine.resolveColumnAlias("some_other_column", selectExprs)
  30. assert.Equal(t, "some_other_column", resolved, "Non-aliased columns should return unchanged")
  31. })
  32. t.Run("SingleAliasInWhere", func(t *testing.T) {
  33. // Test using a single alias in WHERE clause
  34. testRecord := &schema_pb.RecordValue{
  35. Fields: map[string]*schema_pb.Value{
  36. "_timestamp_ns": {Kind: &schema_pb.Value_Int64Value{Int64Value: 1756947416566456262}},
  37. "id": {Kind: &schema_pb.Value_Int64Value{Int64Value: 12345}},
  38. },
  39. }
  40. // Parse SQL with alias in WHERE
  41. sql := "SELECT _timestamp_ns AS ts, id FROM test WHERE ts = 1756947416566456262"
  42. stmt, err := ParseSQL(sql)
  43. assert.NoError(t, err, "Should parse SQL with alias in WHERE")
  44. selectStmt := stmt.(*SelectStatement)
  45. // Build predicate with context (for alias resolution)
  46. predicate, err := engine.buildPredicateWithContext(selectStmt.Where.Expr, selectStmt.SelectExprs)
  47. assert.NoError(t, err, "Should build predicate with alias resolution")
  48. // Test the predicate
  49. result := predicate(testRecord)
  50. assert.True(t, result, "Predicate should match using alias 'ts' for '_timestamp_ns'")
  51. // Test with non-matching value
  52. sql2 := "SELECT _timestamp_ns AS ts, id FROM test WHERE ts = 999999"
  53. stmt2, err := ParseSQL(sql2)
  54. assert.NoError(t, err)
  55. selectStmt2 := stmt2.(*SelectStatement)
  56. predicate2, err := engine.buildPredicateWithContext(selectStmt2.Where.Expr, selectStmt2.SelectExprs)
  57. assert.NoError(t, err)
  58. result2 := predicate2(testRecord)
  59. assert.False(t, result2, "Predicate should not match different value")
  60. })
  61. t.Run("MultipleAliasesInWhere", func(t *testing.T) {
  62. // Test using multiple aliases in WHERE clause
  63. testRecord := &schema_pb.RecordValue{
  64. Fields: map[string]*schema_pb.Value{
  65. "_timestamp_ns": {Kind: &schema_pb.Value_Int64Value{Int64Value: 1756947416566456262}},
  66. "id": {Kind: &schema_pb.Value_Int64Value{Int64Value: 82460}},
  67. },
  68. }
  69. // Parse SQL with multiple aliases in WHERE
  70. sql := "SELECT _timestamp_ns AS ts, id AS record_id FROM test WHERE ts = 1756947416566456262 AND record_id = 82460"
  71. stmt, err := ParseSQL(sql)
  72. assert.NoError(t, err, "Should parse SQL with multiple aliases")
  73. selectStmt := stmt.(*SelectStatement)
  74. // Build predicate with context
  75. predicate, err := engine.buildPredicateWithContext(selectStmt.Where.Expr, selectStmt.SelectExprs)
  76. assert.NoError(t, err, "Should build predicate with multiple alias resolution")
  77. // Test the predicate - should match both conditions
  78. result := predicate(testRecord)
  79. assert.True(t, result, "Should match both aliased conditions")
  80. // Test with one condition not matching
  81. testRecord2 := &schema_pb.RecordValue{
  82. Fields: map[string]*schema_pb.Value{
  83. "_timestamp_ns": {Kind: &schema_pb.Value_Int64Value{Int64Value: 1756947416566456262}},
  84. "id": {Kind: &schema_pb.Value_Int64Value{Int64Value: 99999}}, // Different ID
  85. },
  86. }
  87. result2 := predicate(testRecord2)
  88. assert.False(t, result2, "Should not match when one alias condition fails")
  89. })
  90. t.Run("RangeQueryWithAliases", func(t *testing.T) {
  91. // Test range queries using aliases
  92. testRecords := []*schema_pb.RecordValue{
  93. {
  94. Fields: map[string]*schema_pb.Value{
  95. "_timestamp_ns": {Kind: &schema_pb.Value_Int64Value{Int64Value: 1756947416566456260}}, // Below range
  96. },
  97. },
  98. {
  99. Fields: map[string]*schema_pb.Value{
  100. "_timestamp_ns": {Kind: &schema_pb.Value_Int64Value{Int64Value: 1756947416566456262}}, // In range
  101. },
  102. },
  103. {
  104. Fields: map[string]*schema_pb.Value{
  105. "_timestamp_ns": {Kind: &schema_pb.Value_Int64Value{Int64Value: 1756947416566456265}}, // Above range
  106. },
  107. },
  108. }
  109. // Test range query with alias
  110. sql := "SELECT _timestamp_ns AS ts FROM test WHERE ts > 1756947416566456261 AND ts < 1756947416566456264"
  111. stmt, err := ParseSQL(sql)
  112. assert.NoError(t, err, "Should parse range query with alias")
  113. selectStmt := stmt.(*SelectStatement)
  114. predicate, err := engine.buildPredicateWithContext(selectStmt.Where.Expr, selectStmt.SelectExprs)
  115. assert.NoError(t, err, "Should build range predicate with alias")
  116. // Test each record
  117. assert.False(t, predicate(testRecords[0]), "Should not match record below range")
  118. assert.True(t, predicate(testRecords[1]), "Should match record in range")
  119. assert.False(t, predicate(testRecords[2]), "Should not match record above range")
  120. })
  121. t.Run("MixedAliasAndDirectColumn", func(t *testing.T) {
  122. // Test mixing aliased and non-aliased columns in WHERE
  123. testRecord := &schema_pb.RecordValue{
  124. Fields: map[string]*schema_pb.Value{
  125. "_timestamp_ns": {Kind: &schema_pb.Value_Int64Value{Int64Value: 1756947416566456262}},
  126. "id": {Kind: &schema_pb.Value_Int64Value{Int64Value: 82460}},
  127. "status": {Kind: &schema_pb.Value_StringValue{StringValue: "active"}},
  128. },
  129. }
  130. // Use alias for one column, direct name for another
  131. sql := "SELECT _timestamp_ns AS ts, id, status FROM test WHERE ts = 1756947416566456262 AND status = 'active'"
  132. stmt, err := ParseSQL(sql)
  133. assert.NoError(t, err, "Should parse mixed alias/direct query")
  134. selectStmt := stmt.(*SelectStatement)
  135. predicate, err := engine.buildPredicateWithContext(selectStmt.Where.Expr, selectStmt.SelectExprs)
  136. assert.NoError(t, err, "Should build mixed predicate")
  137. result := predicate(testRecord)
  138. assert.True(t, result, "Should match with mixed alias and direct column usage")
  139. })
  140. t.Run("AliasCompatibilityWithTimestampFixes", func(t *testing.T) {
  141. // Test that alias resolution works with the timestamp precision fixes
  142. largeTimestamp := int64(1756947416566456262) // Large nanosecond timestamp
  143. testRecord := &schema_pb.RecordValue{
  144. Fields: map[string]*schema_pb.Value{
  145. "_timestamp_ns": {Kind: &schema_pb.Value_Int64Value{Int64Value: largeTimestamp}},
  146. "id": {Kind: &schema_pb.Value_Int64Value{Int64Value: 897795}},
  147. },
  148. }
  149. // Test that large timestamp precision is maintained with aliases
  150. sql := "SELECT _timestamp_ns AS ts, id FROM test WHERE ts = 1756947416566456262"
  151. stmt, err := ParseSQL(sql)
  152. assert.NoError(t, err)
  153. selectStmt := stmt.(*SelectStatement)
  154. predicate, err := engine.buildPredicateWithContext(selectStmt.Where.Expr, selectStmt.SelectExprs)
  155. assert.NoError(t, err)
  156. result := predicate(testRecord)
  157. assert.True(t, result, "Large timestamp precision should be maintained with aliases")
  158. // Test precision with off-by-one (should not match)
  159. sql2 := "SELECT _timestamp_ns AS ts, id FROM test WHERE ts = 1756947416566456263" // +1
  160. stmt2, err := ParseSQL(sql2)
  161. assert.NoError(t, err)
  162. selectStmt2 := stmt2.(*SelectStatement)
  163. predicate2, err := engine.buildPredicateWithContext(selectStmt2.Where.Expr, selectStmt2.SelectExprs)
  164. assert.NoError(t, err)
  165. result2 := predicate2(testRecord)
  166. assert.False(t, result2, "Should not match timestamp differing by 1 nanosecond")
  167. })
  168. t.Run("EdgeCasesAndErrorHandling", func(t *testing.T) {
  169. // Test edge cases and error conditions
  170. // Test with nil SelectExprs
  171. predicate, err := engine.buildPredicateWithContext(&ComparisonExpr{
  172. Left: &ColName{Name: stringValue("test_col")},
  173. Operator: "=",
  174. Right: &SQLVal{Type: IntVal, Val: []byte("123")},
  175. }, nil)
  176. assert.NoError(t, err, "Should handle nil SelectExprs gracefully")
  177. assert.NotNil(t, predicate, "Should return valid predicate even without aliases")
  178. // Test alias resolution with empty SelectExprs
  179. resolved := engine.resolveColumnAlias("test_col", []SelectExpr{})
  180. assert.Equal(t, "test_col", resolved, "Should return original name with empty SelectExprs")
  181. // Test alias resolution with nil SelectExprs
  182. resolved = engine.resolveColumnAlias("test_col", nil)
  183. assert.Equal(t, "test_col", resolved, "Should return original name with nil SelectExprs")
  184. })
  185. t.Run("ComparisonOperators", func(t *testing.T) {
  186. // Test all comparison operators work with aliases
  187. testRecord := &schema_pb.RecordValue{
  188. Fields: map[string]*schema_pb.Value{
  189. "_timestamp_ns": {Kind: &schema_pb.Value_Int64Value{Int64Value: 1000}},
  190. },
  191. }
  192. operators := []struct {
  193. op string
  194. value string
  195. expected bool
  196. }{
  197. {"=", "1000", true},
  198. {"=", "999", false},
  199. {">", "999", true},
  200. {">", "1000", false},
  201. {">=", "1000", true},
  202. {">=", "1001", false},
  203. {"<", "1001", true},
  204. {"<", "1000", false},
  205. {"<=", "1000", true},
  206. {"<=", "999", false},
  207. }
  208. for _, test := range operators {
  209. t.Run(test.op+"_"+test.value, func(t *testing.T) {
  210. sql := "SELECT _timestamp_ns AS ts FROM test WHERE ts " + test.op + " " + test.value
  211. stmt, err := ParseSQL(sql)
  212. assert.NoError(t, err, "Should parse operator: %s", test.op)
  213. selectStmt := stmt.(*SelectStatement)
  214. predicate, err := engine.buildPredicateWithContext(selectStmt.Where.Expr, selectStmt.SelectExprs)
  215. assert.NoError(t, err, "Should build predicate for operator: %s", test.op)
  216. result := predicate(testRecord)
  217. assert.Equal(t, test.expected, result, "Operator %s with value %s should return %v", test.op, test.value, test.expected)
  218. })
  219. }
  220. })
  221. t.Run("BackwardCompatibility", func(t *testing.T) {
  222. // Ensure non-alias queries still work exactly as before
  223. testRecord := &schema_pb.RecordValue{
  224. Fields: map[string]*schema_pb.Value{
  225. "_timestamp_ns": {Kind: &schema_pb.Value_Int64Value{Int64Value: 1756947416566456262}},
  226. "id": {Kind: &schema_pb.Value_Int64Value{Int64Value: 12345}},
  227. },
  228. }
  229. // Test traditional query (no aliases)
  230. sql := "SELECT _timestamp_ns, id FROM test WHERE _timestamp_ns = 1756947416566456262"
  231. stmt, err := ParseSQL(sql)
  232. assert.NoError(t, err)
  233. selectStmt := stmt.(*SelectStatement)
  234. // Should work with both old and new predicate building methods
  235. predicateOld, err := engine.buildPredicate(selectStmt.Where.Expr)
  236. assert.NoError(t, err, "Old buildPredicate method should still work")
  237. predicateNew, err := engine.buildPredicateWithContext(selectStmt.Where.Expr, selectStmt.SelectExprs)
  238. assert.NoError(t, err, "New buildPredicateWithContext should work for non-alias queries")
  239. // Both should produce the same result
  240. resultOld := predicateOld(testRecord)
  241. resultNew := predicateNew(testRecord)
  242. assert.True(t, resultOld, "Old method should match")
  243. assert.True(t, resultNew, "New method should match")
  244. assert.Equal(t, resultOld, resultNew, "Both methods should produce identical results")
  245. })
  246. }
  247. // TestAliasIntegrationWithProductionScenarios tests real-world usage patterns
  248. func TestAliasIntegrationWithProductionScenarios(t *testing.T) {
  249. engine := NewTestSQLEngine()
  250. t.Run("OriginalFailingQuery", func(t *testing.T) {
  251. // Test the exact query pattern that was originally failing
  252. testRecord := &schema_pb.RecordValue{
  253. Fields: map[string]*schema_pb.Value{
  254. "_timestamp_ns": {Kind: &schema_pb.Value_Int64Value{Int64Value: 1756913789829292386}},
  255. "id": {Kind: &schema_pb.Value_Int64Value{Int64Value: 82460}},
  256. },
  257. }
  258. // This was the original failing pattern
  259. sql := "SELECT id, _timestamp_ns AS ts FROM ecommerce.user_events WHERE ts = 1756913789829292386"
  260. stmt, err := ParseSQL(sql)
  261. assert.NoError(t, err, "Should parse the originally failing query pattern")
  262. selectStmt := stmt.(*SelectStatement)
  263. predicate, err := engine.buildPredicateWithContext(selectStmt.Where.Expr, selectStmt.SelectExprs)
  264. assert.NoError(t, err, "Should build predicate for originally failing pattern")
  265. result := predicate(testRecord)
  266. assert.True(t, result, "Should now work for the originally failing query pattern")
  267. })
  268. t.Run("ComplexProductionQuery", func(t *testing.T) {
  269. // Test a more complex production-like query
  270. testRecord := &schema_pb.RecordValue{
  271. Fields: map[string]*schema_pb.Value{
  272. "_timestamp_ns": {Kind: &schema_pb.Value_Int64Value{Int64Value: 1756947416566456262}},
  273. "id": {Kind: &schema_pb.Value_Int64Value{Int64Value: 897795}},
  274. "user_id": {Kind: &schema_pb.Value_StringValue{StringValue: "user123"}},
  275. "event_type": {Kind: &schema_pb.Value_StringValue{StringValue: "click"}},
  276. },
  277. }
  278. sql := `SELECT
  279. id AS event_id,
  280. _timestamp_ns AS event_time,
  281. user_id AS uid,
  282. event_type AS action
  283. FROM ecommerce.user_events
  284. WHERE event_time = 1756947416566456262
  285. AND uid = 'user123'
  286. AND action = 'click'`
  287. stmt, err := ParseSQL(sql)
  288. assert.NoError(t, err, "Should parse complex production query")
  289. selectStmt := stmt.(*SelectStatement)
  290. predicate, err := engine.buildPredicateWithContext(selectStmt.Where.Expr, selectStmt.SelectExprs)
  291. assert.NoError(t, err, "Should build predicate for complex query")
  292. result := predicate(testRecord)
  293. assert.True(t, result, "Should match complex production query with multiple aliases")
  294. // Test partial match failure
  295. testRecord2 := &schema_pb.RecordValue{
  296. Fields: map[string]*schema_pb.Value{
  297. "_timestamp_ns": {Kind: &schema_pb.Value_Int64Value{Int64Value: 1756947416566456262}},
  298. "id": {Kind: &schema_pb.Value_Int64Value{Int64Value: 897795}},
  299. "user_id": {Kind: &schema_pb.Value_StringValue{StringValue: "user999"}}, // Different user
  300. "event_type": {Kind: &schema_pb.Value_StringValue{StringValue: "click"}},
  301. },
  302. }
  303. result2 := predicate(testRecord2)
  304. assert.False(t, result2, "Should not match when one aliased condition fails")
  305. })
  306. t.Run("PerformanceRegression", func(t *testing.T) {
  307. // Ensure alias resolution doesn't significantly impact performance
  308. testRecord := &schema_pb.RecordValue{
  309. Fields: map[string]*schema_pb.Value{
  310. "_timestamp_ns": {Kind: &schema_pb.Value_Int64Value{Int64Value: 1756947416566456262}},
  311. },
  312. }
  313. // Build predicates for comparison
  314. sqlWithAlias := "SELECT _timestamp_ns AS ts FROM test WHERE ts = 1756947416566456262"
  315. sqlWithoutAlias := "SELECT _timestamp_ns FROM test WHERE _timestamp_ns = 1756947416566456262"
  316. stmtWithAlias, err := ParseSQL(sqlWithAlias)
  317. assert.NoError(t, err)
  318. stmtWithoutAlias, err := ParseSQL(sqlWithoutAlias)
  319. assert.NoError(t, err)
  320. selectStmtWithAlias := stmtWithAlias.(*SelectStatement)
  321. selectStmtWithoutAlias := stmtWithoutAlias.(*SelectStatement)
  322. // Both should build successfully
  323. predicateWithAlias, err := engine.buildPredicateWithContext(selectStmtWithAlias.Where.Expr, selectStmtWithAlias.SelectExprs)
  324. assert.NoError(t, err)
  325. predicateWithoutAlias, err := engine.buildPredicateWithContext(selectStmtWithoutAlias.Where.Expr, selectStmtWithoutAlias.SelectExprs)
  326. assert.NoError(t, err)
  327. // Both should produce the same logical result
  328. resultWithAlias := predicateWithAlias(testRecord)
  329. resultWithoutAlias := predicateWithoutAlias(testRecord)
  330. assert.True(t, resultWithAlias, "Alias query should work")
  331. assert.True(t, resultWithoutAlias, "Non-alias query should work")
  332. assert.Equal(t, resultWithAlias, resultWithoutAlias, "Both should produce same result")
  333. })
  334. }