aboutsummaryrefslogtreecommitdiff
path: root/weed/query/engine/sql_alias_support_test.go
blob: dbe91f821a32ee8c860c1c6f483097c331f39ba7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
package engine

import (
	"testing"

	"github.com/seaweedfs/seaweedfs/weed/pb/schema_pb"
	"github.com/stretchr/testify/assert"
)

// TestSQLAliasResolution tests the complete SQL alias resolution functionality
func TestSQLAliasResolution(t *testing.T) {
	engine := NewTestSQLEngine()

	t.Run("ResolveColumnAlias", func(t *testing.T) {
		// Test the helper function for resolving aliases

		// Create SELECT expressions with aliases
		selectExprs := []SelectExpr{
			&AliasedExpr{
				Expr: &ColName{Name: stringValue("_ts_ns")},
				As:   aliasValue("ts"),
			},
			&AliasedExpr{
				Expr: &ColName{Name: stringValue("id")},
				As:   aliasValue("record_id"),
			},
		}

		// Test alias resolution
		resolved := engine.resolveColumnAlias("ts", selectExprs)
		assert.Equal(t, "_ts_ns", resolved, "Should resolve 'ts' alias to '_ts_ns'")

		resolved = engine.resolveColumnAlias("record_id", selectExprs)
		assert.Equal(t, "id", resolved, "Should resolve 'record_id' alias to 'id'")

		// Test non-aliased column (should return as-is)
		resolved = engine.resolveColumnAlias("some_other_column", selectExprs)
		assert.Equal(t, "some_other_column", resolved, "Non-aliased columns should return unchanged")
	})

	t.Run("SingleAliasInWhere", func(t *testing.T) {
		// Test using a single alias in WHERE clause
		testRecord := &schema_pb.RecordValue{
			Fields: map[string]*schema_pb.Value{
				"_ts_ns": {Kind: &schema_pb.Value_Int64Value{Int64Value: 1756947416566456262}},
				"id":     {Kind: &schema_pb.Value_Int64Value{Int64Value: 12345}},
			},
		}

		// Parse SQL with alias in WHERE
		sql := "SELECT _ts_ns AS ts, id FROM test WHERE ts = 1756947416566456262"
		stmt, err := ParseSQL(sql)
		assert.NoError(t, err, "Should parse SQL with alias in WHERE")

		selectStmt := stmt.(*SelectStatement)

		// Build predicate with context (for alias resolution)
		predicate, err := engine.buildPredicateWithContext(selectStmt.Where.Expr, selectStmt.SelectExprs)
		assert.NoError(t, err, "Should build predicate with alias resolution")

		// Test the predicate
		result := predicate(testRecord)
		assert.True(t, result, "Predicate should match using alias 'ts' for '_ts_ns'")

		// Test with non-matching value
		sql2 := "SELECT _ts_ns AS ts, id FROM test WHERE ts = 999999"
		stmt2, err := ParseSQL(sql2)
		assert.NoError(t, err)
		selectStmt2 := stmt2.(*SelectStatement)

		predicate2, err := engine.buildPredicateWithContext(selectStmt2.Where.Expr, selectStmt2.SelectExprs)
		assert.NoError(t, err)

		result2 := predicate2(testRecord)
		assert.False(t, result2, "Predicate should not match different value")
	})

	t.Run("MultipleAliasesInWhere", func(t *testing.T) {
		// Test using multiple aliases in WHERE clause
		testRecord := &schema_pb.RecordValue{
			Fields: map[string]*schema_pb.Value{
				"_ts_ns": {Kind: &schema_pb.Value_Int64Value{Int64Value: 1756947416566456262}},
				"id":     {Kind: &schema_pb.Value_Int64Value{Int64Value: 82460}},
			},
		}

		// Parse SQL with multiple aliases in WHERE
		sql := "SELECT _ts_ns AS ts, id AS record_id FROM test WHERE ts = 1756947416566456262 AND record_id = 82460"
		stmt, err := ParseSQL(sql)
		assert.NoError(t, err, "Should parse SQL with multiple aliases")

		selectStmt := stmt.(*SelectStatement)

		// Build predicate with context
		predicate, err := engine.buildPredicateWithContext(selectStmt.Where.Expr, selectStmt.SelectExprs)
		assert.NoError(t, err, "Should build predicate with multiple alias resolution")

		// Test the predicate - should match both conditions
		result := predicate(testRecord)
		assert.True(t, result, "Should match both aliased conditions")

		// Test with one condition not matching
		testRecord2 := &schema_pb.RecordValue{
			Fields: map[string]*schema_pb.Value{
				"_ts_ns": {Kind: &schema_pb.Value_Int64Value{Int64Value: 1756947416566456262}},
				"id":     {Kind: &schema_pb.Value_Int64Value{Int64Value: 99999}}, // Different ID
			},
		}

		result2 := predicate(testRecord2)
		assert.False(t, result2, "Should not match when one alias condition fails")
	})

	t.Run("RangeQueryWithAliases", func(t *testing.T) {
		// Test range queries using aliases
		testRecords := []*schema_pb.RecordValue{
			{
				Fields: map[string]*schema_pb.Value{
					"_ts_ns": {Kind: &schema_pb.Value_Int64Value{Int64Value: 1756947416566456260}}, // Below range
				},
			},
			{
				Fields: map[string]*schema_pb.Value{
					"_ts_ns": {Kind: &schema_pb.Value_Int64Value{Int64Value: 1756947416566456262}}, // In range
				},
			},
			{
				Fields: map[string]*schema_pb.Value{
					"_ts_ns": {Kind: &schema_pb.Value_Int64Value{Int64Value: 1756947416566456265}}, // Above range
				},
			},
		}

		// Test range query with alias
		sql := "SELECT _ts_ns AS ts FROM test WHERE ts > 1756947416566456261 AND ts < 1756947416566456264"
		stmt, err := ParseSQL(sql)
		assert.NoError(t, err, "Should parse range query with alias")

		selectStmt := stmt.(*SelectStatement)
		predicate, err := engine.buildPredicateWithContext(selectStmt.Where.Expr, selectStmt.SelectExprs)
		assert.NoError(t, err, "Should build range predicate with alias")

		// Test each record
		assert.False(t, predicate(testRecords[0]), "Should not match record below range")
		assert.True(t, predicate(testRecords[1]), "Should match record in range")
		assert.False(t, predicate(testRecords[2]), "Should not match record above range")
	})

	t.Run("MixedAliasAndDirectColumn", func(t *testing.T) {
		// Test mixing aliased and non-aliased columns in WHERE
		testRecord := &schema_pb.RecordValue{
			Fields: map[string]*schema_pb.Value{
				"_ts_ns": {Kind: &schema_pb.Value_Int64Value{Int64Value: 1756947416566456262}},
				"id":     {Kind: &schema_pb.Value_Int64Value{Int64Value: 82460}},
				"status": {Kind: &schema_pb.Value_StringValue{StringValue: "active"}},
			},
		}

		// Use alias for one column, direct name for another
		sql := "SELECT _ts_ns AS ts, id, status FROM test WHERE ts = 1756947416566456262 AND status = 'active'"
		stmt, err := ParseSQL(sql)
		assert.NoError(t, err, "Should parse mixed alias/direct query")

		selectStmt := stmt.(*SelectStatement)
		predicate, err := engine.buildPredicateWithContext(selectStmt.Where.Expr, selectStmt.SelectExprs)
		assert.NoError(t, err, "Should build mixed predicate")

		result := predicate(testRecord)
		assert.True(t, result, "Should match with mixed alias and direct column usage")
	})

	t.Run("AliasCompatibilityWithTimestampFixes", func(t *testing.T) {
		// Test that alias resolution works with the timestamp precision fixes
		largeTimestamp := int64(1756947416566456262) // Large nanosecond timestamp

		testRecord := &schema_pb.RecordValue{
			Fields: map[string]*schema_pb.Value{
				"_ts_ns": {Kind: &schema_pb.Value_Int64Value{Int64Value: largeTimestamp}},
				"id":     {Kind: &schema_pb.Value_Int64Value{Int64Value: 897795}},
			},
		}

		// Test that large timestamp precision is maintained with aliases
		sql := "SELECT _ts_ns AS ts, id FROM test WHERE ts = 1756947416566456262"
		stmt, err := ParseSQL(sql)
		assert.NoError(t, err)

		selectStmt := stmt.(*SelectStatement)
		predicate, err := engine.buildPredicateWithContext(selectStmt.Where.Expr, selectStmt.SelectExprs)
		assert.NoError(t, err)

		result := predicate(testRecord)
		assert.True(t, result, "Large timestamp precision should be maintained with aliases")

		// Test precision with off-by-one (should not match)
		sql2 := "SELECT _ts_ns AS ts, id FROM test WHERE ts = 1756947416566456263" // +1
		stmt2, err := ParseSQL(sql2)
		assert.NoError(t, err)
		selectStmt2 := stmt2.(*SelectStatement)
		predicate2, err := engine.buildPredicateWithContext(selectStmt2.Where.Expr, selectStmt2.SelectExprs)
		assert.NoError(t, err)

		result2 := predicate2(testRecord)
		assert.False(t, result2, "Should not match timestamp differing by 1 nanosecond")
	})

	t.Run("EdgeCasesAndErrorHandling", func(t *testing.T) {
		// Test edge cases and error conditions

		// Test with nil SelectExprs
		predicate, err := engine.buildPredicateWithContext(&ComparisonExpr{
			Left:     &ColName{Name: stringValue("test_col")},
			Operator: "=",
			Right:    &SQLVal{Type: IntVal, Val: []byte("123")},
		}, nil)
		assert.NoError(t, err, "Should handle nil SelectExprs gracefully")
		assert.NotNil(t, predicate, "Should return valid predicate even without aliases")

		// Test alias resolution with empty SelectExprs
		resolved := engine.resolveColumnAlias("test_col", []SelectExpr{})
		assert.Equal(t, "test_col", resolved, "Should return original name with empty SelectExprs")

		// Test alias resolution with nil SelectExprs
		resolved = engine.resolveColumnAlias("test_col", nil)
		assert.Equal(t, "test_col", resolved, "Should return original name with nil SelectExprs")
	})

	t.Run("ComparisonOperators", func(t *testing.T) {
		// Test all comparison operators work with aliases
		testRecord := &schema_pb.RecordValue{
			Fields: map[string]*schema_pb.Value{
				"_ts_ns": {Kind: &schema_pb.Value_Int64Value{Int64Value: 1000}},
			},
		}

		operators := []struct {
			op       string
			value    string
			expected bool
		}{
			{"=", "1000", true},
			{"=", "999", false},
			{">", "999", true},
			{">", "1000", false},
			{">=", "1000", true},
			{">=", "1001", false},
			{"<", "1001", true},
			{"<", "1000", false},
			{"<=", "1000", true},
			{"<=", "999", false},
		}

		for _, test := range operators {
			t.Run(test.op+"_"+test.value, func(t *testing.T) {
				sql := "SELECT _ts_ns AS ts FROM test WHERE ts " + test.op + " " + test.value
				stmt, err := ParseSQL(sql)
				assert.NoError(t, err, "Should parse operator: %s", test.op)

				selectStmt := stmt.(*SelectStatement)
				predicate, err := engine.buildPredicateWithContext(selectStmt.Where.Expr, selectStmt.SelectExprs)
				assert.NoError(t, err, "Should build predicate for operator: %s", test.op)

				result := predicate(testRecord)
				assert.Equal(t, test.expected, result, "Operator %s with value %s should return %v", test.op, test.value, test.expected)
			})
		}
	})

	t.Run("BackwardCompatibility", func(t *testing.T) {
		// Ensure non-alias queries still work exactly as before
		testRecord := &schema_pb.RecordValue{
			Fields: map[string]*schema_pb.Value{
				"_ts_ns": {Kind: &schema_pb.Value_Int64Value{Int64Value: 1756947416566456262}},
				"id":     {Kind: &schema_pb.Value_Int64Value{Int64Value: 12345}},
			},
		}

		// Test traditional query (no aliases)
		sql := "SELECT _ts_ns, id FROM test WHERE _ts_ns = 1756947416566456262"
		stmt, err := ParseSQL(sql)
		assert.NoError(t, err)

		selectStmt := stmt.(*SelectStatement)

		// Should work with both old and new predicate building methods
		predicateOld, err := engine.buildPredicate(selectStmt.Where.Expr)
		assert.NoError(t, err, "Old buildPredicate method should still work")

		predicateNew, err := engine.buildPredicateWithContext(selectStmt.Where.Expr, selectStmt.SelectExprs)
		assert.NoError(t, err, "New buildPredicateWithContext should work for non-alias queries")

		// Both should produce the same result
		resultOld := predicateOld(testRecord)
		resultNew := predicateNew(testRecord)

		assert.True(t, resultOld, "Old method should match")
		assert.True(t, resultNew, "New method should match")
		assert.Equal(t, resultOld, resultNew, "Both methods should produce identical results")
	})
}

// TestAliasIntegrationWithProductionScenarios tests real-world usage patterns
func TestAliasIntegrationWithProductionScenarios(t *testing.T) {
	engine := NewTestSQLEngine()

	t.Run("OriginalFailingQuery", func(t *testing.T) {
		// Test the exact query pattern that was originally failing
		testRecord := &schema_pb.RecordValue{
			Fields: map[string]*schema_pb.Value{
				"_ts_ns": {Kind: &schema_pb.Value_Int64Value{Int64Value: 1756913789829292386}},
				"id":     {Kind: &schema_pb.Value_Int64Value{Int64Value: 82460}},
			},
		}

		// This was the original failing pattern
		sql := "SELECT id, _ts_ns AS ts FROM ecommerce.user_events WHERE ts = 1756913789829292386"
		stmt, err := ParseSQL(sql)
		assert.NoError(t, err, "Should parse the originally failing query pattern")

		selectStmt := stmt.(*SelectStatement)
		predicate, err := engine.buildPredicateWithContext(selectStmt.Where.Expr, selectStmt.SelectExprs)
		assert.NoError(t, err, "Should build predicate for originally failing pattern")

		result := predicate(testRecord)
		assert.True(t, result, "Should now work for the originally failing query pattern")
	})

	t.Run("ComplexProductionQuery", func(t *testing.T) {
		// Test a more complex production-like query
		testRecord := &schema_pb.RecordValue{
			Fields: map[string]*schema_pb.Value{
				"_ts_ns":     {Kind: &schema_pb.Value_Int64Value{Int64Value: 1756947416566456262}},
				"id":         {Kind: &schema_pb.Value_Int64Value{Int64Value: 897795}},
				"user_id":    {Kind: &schema_pb.Value_StringValue{StringValue: "user123"}},
				"event_type": {Kind: &schema_pb.Value_StringValue{StringValue: "click"}},
			},
		}

		sql := `SELECT 
					id AS event_id, 
					_ts_ns AS event_time, 
					user_id AS uid,
					event_type AS action
				FROM ecommerce.user_events 
				WHERE event_time = 1756947416566456262 
					AND uid = 'user123' 
					AND action = 'click'`

		stmt, err := ParseSQL(sql)
		assert.NoError(t, err, "Should parse complex production query")

		selectStmt := stmt.(*SelectStatement)
		predicate, err := engine.buildPredicateWithContext(selectStmt.Where.Expr, selectStmt.SelectExprs)
		assert.NoError(t, err, "Should build predicate for complex query")

		result := predicate(testRecord)
		assert.True(t, result, "Should match complex production query with multiple aliases")

		// Test partial match failure
		testRecord2 := &schema_pb.RecordValue{
			Fields: map[string]*schema_pb.Value{
				"_ts_ns":     {Kind: &schema_pb.Value_Int64Value{Int64Value: 1756947416566456262}},
				"id":         {Kind: &schema_pb.Value_Int64Value{Int64Value: 897795}},
				"user_id":    {Kind: &schema_pb.Value_StringValue{StringValue: "user999"}}, // Different user
				"event_type": {Kind: &schema_pb.Value_StringValue{StringValue: "click"}},
			},
		}

		result2 := predicate(testRecord2)
		assert.False(t, result2, "Should not match when one aliased condition fails")
	})

	t.Run("PerformanceRegression", func(t *testing.T) {
		// Ensure alias resolution doesn't significantly impact performance
		testRecord := &schema_pb.RecordValue{
			Fields: map[string]*schema_pb.Value{
				"_ts_ns": {Kind: &schema_pb.Value_Int64Value{Int64Value: 1756947416566456262}},
			},
		}

		// Build predicates for comparison
		sqlWithAlias := "SELECT _ts_ns AS ts FROM test WHERE ts = 1756947416566456262"
		sqlWithoutAlias := "SELECT _ts_ns FROM test WHERE _ts_ns = 1756947416566456262"

		stmtWithAlias, err := ParseSQL(sqlWithAlias)
		assert.NoError(t, err)
		stmtWithoutAlias, err := ParseSQL(sqlWithoutAlias)
		assert.NoError(t, err)

		selectStmtWithAlias := stmtWithAlias.(*SelectStatement)
		selectStmtWithoutAlias := stmtWithoutAlias.(*SelectStatement)

		// Both should build successfully
		predicateWithAlias, err := engine.buildPredicateWithContext(selectStmtWithAlias.Where.Expr, selectStmtWithAlias.SelectExprs)
		assert.NoError(t, err)

		predicateWithoutAlias, err := engine.buildPredicateWithContext(selectStmtWithoutAlias.Where.Expr, selectStmtWithoutAlias.SelectExprs)
		assert.NoError(t, err)

		// Both should produce the same logical result
		resultWithAlias := predicateWithAlias(testRecord)
		resultWithoutAlias := predicateWithoutAlias(testRecord)

		assert.True(t, resultWithAlias, "Alias query should work")
		assert.True(t, resultWithoutAlias, "Non-alias query should work")
		assert.Equal(t, resultWithAlias, resultWithoutAlias, "Both should produce same result")
	})
}