| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408 |
- 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("_timestamp_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, "_timestamp_ns", resolved, "Should resolve 'ts' alias to '_timestamp_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{
- "_timestamp_ns": {Kind: &schema_pb.Value_Int64Value{Int64Value: 1756947416566456262}},
- "id": {Kind: &schema_pb.Value_Int64Value{Int64Value: 12345}},
- },
- }
- // Parse SQL with alias in WHERE
- sql := "SELECT _timestamp_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 '_timestamp_ns'")
- // Test with non-matching value
- sql2 := "SELECT _timestamp_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{
- "_timestamp_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 _timestamp_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{
- "_timestamp_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{
- "_timestamp_ns": {Kind: &schema_pb.Value_Int64Value{Int64Value: 1756947416566456260}}, // Below range
- },
- },
- {
- Fields: map[string]*schema_pb.Value{
- "_timestamp_ns": {Kind: &schema_pb.Value_Int64Value{Int64Value: 1756947416566456262}}, // In range
- },
- },
- {
- Fields: map[string]*schema_pb.Value{
- "_timestamp_ns": {Kind: &schema_pb.Value_Int64Value{Int64Value: 1756947416566456265}}, // Above range
- },
- },
- }
- // Test range query with alias
- sql := "SELECT _timestamp_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{
- "_timestamp_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 _timestamp_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{
- "_timestamp_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 _timestamp_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 _timestamp_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{
- "_timestamp_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 _timestamp_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{
- "_timestamp_ns": {Kind: &schema_pb.Value_Int64Value{Int64Value: 1756947416566456262}},
- "id": {Kind: &schema_pb.Value_Int64Value{Int64Value: 12345}},
- },
- }
- // Test traditional query (no aliases)
- sql := "SELECT _timestamp_ns, id FROM test WHERE _timestamp_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{
- "_timestamp_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, _timestamp_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{
- "_timestamp_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,
- _timestamp_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{
- "_timestamp_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{
- "_timestamp_ns": {Kind: &schema_pb.Value_Int64Value{Int64Value: 1756947416566456262}},
- },
- }
- // Build predicates for comparison
- sqlWithAlias := "SELECT _timestamp_ns AS ts FROM test WHERE ts = 1756947416566456262"
- sqlWithoutAlias := "SELECT _timestamp_ns FROM test WHERE _timestamp_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")
- })
- }
|