github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/testutils/sqlutils/name_resolution_testutils.go (about)

     1  // Copyright 2018 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package sqlutils
    12  
    13  import (
    14  	"context"
    15  	"fmt"
    16  	"testing"
    17  
    18  	"github.com/cockroachdb/cockroach/pkg/sql/parser"
    19  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    20  	"github.com/cockroachdb/cockroach/pkg/testutils"
    21  )
    22  
    23  // ColumnItemResolverTester is an interface that should be implemented by any
    24  // struct that also implements tree.ColumnItemResolver. It is used to test that
    25  // the implementation of tree.ColumnItemResolver is correct.
    26  type ColumnItemResolverTester interface {
    27  	// GetColumnItemResolver returns the tree.ColumnItemResolver. Since any
    28  	// struct implementing ColumnItemResolverTester should also implement
    29  	// tree.ColumnItemResolver, this is basically an identity function.
    30  	GetColumnItemResolver() tree.ColumnItemResolver
    31  
    32  	// AddTable adds a table with the given column names to the
    33  	// tree.ColumnItemResolver.
    34  	AddTable(tabName tree.TableName, colNames []tree.Name)
    35  
    36  	// ResolveQualifiedStarTestResults returns the results of running
    37  	// RunResolveQualifiedStarTest on the tree.ColumnItemResolver.
    38  	ResolveQualifiedStarTestResults(
    39  		srcName *tree.TableName, srcMeta tree.ColumnSourceMeta,
    40  	) (string, string, error)
    41  
    42  	// ResolveColumnItemTestResults returns the results of running
    43  	// RunResolveColumnItemTest on the tree.ColumnItemResolver.
    44  	ResolveColumnItemTestResults(colRes tree.ColumnResolutionResult) (string, error)
    45  }
    46  
    47  func initColumnItemResolverTester(t *testing.T, ct ColumnItemResolverTester) {
    48  	ct.AddTable(tree.MakeTableNameWithSchema("", "crdb_internal", "tables"), []tree.Name{"table_name"})
    49  	ct.AddTable(tree.MakeTableName("db1", "foo"), []tree.Name{"x"})
    50  	ct.AddTable(tree.MakeTableName("db2", "foo"), []tree.Name{"x"})
    51  	ct.AddTable(tree.MakeUnqualifiedTableName("bar"), []tree.Name{"x"})
    52  	ct.AddTable(tree.MakeTableName("db1", "kv"), []tree.Name{"k", "v"})
    53  }
    54  
    55  // RunResolveQualifiedStarTest tests that the given ColumnItemResolverTester
    56  // correctly resolves names of the form "<tableName>.*".
    57  func RunResolveQualifiedStarTest(t *testing.T, ct ColumnItemResolverTester) {
    58  	testCases := []struct {
    59  		in    string
    60  		tnout string
    61  		csout string
    62  		err   string
    63  	}{
    64  		{`a.*`, ``, ``, `no data source matches pattern: a.*`},
    65  		{`foo.*`, ``, ``, `ambiguous source name: "foo"`},
    66  		{`db1.public.foo.*`, `db1.public.foo`, `x`, ``},
    67  		{`db1.foo.*`, `db1.public.foo`, `x`, ``},
    68  		{`dbx.foo.*`, ``, ``, `no data source matches pattern: dbx.foo.*`},
    69  		{`kv.*`, `db1.public.kv`, `k, v`, ``},
    70  	}
    71  
    72  	initColumnItemResolverTester(t, ct)
    73  	resolver := ct.GetColumnItemResolver()
    74  	for _, tc := range testCases {
    75  		t.Run(tc.in, func(t *testing.T) {
    76  			tnout, csout, err := func() (string, string, error) {
    77  				stmt, err := parser.ParseOne(fmt.Sprintf("SELECT %s", tc.in))
    78  				if err != nil {
    79  					return "", "", err
    80  				}
    81  				v := stmt.AST.(*tree.Select).Select.(*tree.SelectClause).Exprs[0].Expr.(tree.VarName)
    82  				c, err := v.NormalizeVarName()
    83  				if err != nil {
    84  					return "", "", err
    85  				}
    86  				acs, ok := c.(*tree.AllColumnsSelector)
    87  				if !ok {
    88  					return "", "", fmt.Errorf("var name %s (%T) did not resolve to AllColumnsSelector, found %T instead",
    89  						v, v, c)
    90  				}
    91  				tn, res, err := acs.Resolve(context.Background(), resolver)
    92  				if err != nil {
    93  					return "", "", err
    94  				}
    95  				return ct.ResolveQualifiedStarTestResults(tn, res)
    96  			}()
    97  			if !testutils.IsError(err, tc.err) {
    98  				t.Fatalf("%s: expected %s, but found %v", tc.in, tc.err, err)
    99  			}
   100  			if tc.err != "" {
   101  				return
   102  			}
   103  
   104  			if tc.tnout != tnout {
   105  				t.Fatalf("%s: expected tn %s, but found %s", tc.in, tc.tnout, tnout)
   106  			}
   107  			if tc.csout != csout {
   108  				t.Fatalf("%s: expected cs %s, but found %s", tc.in, tc.csout, csout)
   109  			}
   110  		})
   111  	}
   112  }
   113  
   114  // RunResolveColumnItemTest tests that the given ColumnItemResolverTester
   115  // correctly resolves column names.
   116  func RunResolveColumnItemTest(t *testing.T, ct ColumnItemResolverTester) {
   117  	testCases := []struct {
   118  		in  string
   119  		out string
   120  		err string
   121  	}{
   122  		{`a`, ``, `column "a" does not exist`},
   123  		{`x`, ``, `column reference "x" is ambiguous \(candidates: db1.public.foo.x, db2.public.foo.x, bar.x\)`},
   124  		{`k`, `db1.public.kv.k`, ``},
   125  		{`v`, `db1.public.kv.v`, ``},
   126  		{`table_name`, `"".crdb_internal.tables.table_name`, ``},
   127  
   128  		{`blix.x`, ``, `no data source matches prefix: blix`},
   129  		{`"".x`, ``, `invalid column name: ""\.x`},
   130  		{`foo.x`, ``, `ambiguous source name`},
   131  		{`kv.k`, `db1.public.kv.k`, ``},
   132  		{`bar.x`, `bar.x`, ``},
   133  		{`tables.table_name`, `"".crdb_internal.tables.table_name`, ``},
   134  
   135  		{`a.b.x`, ``, `no data source matches prefix: a\.b`},
   136  		{`crdb_internal.tables.table_name`, `"".crdb_internal.tables.table_name`, ``},
   137  		{`public.foo.x`, ``, `ambiguous source name`},
   138  		{`public.kv.k`, `db1.public.kv.k`, ``},
   139  
   140  		// CockroachDB extension: d.t.x -> d.public.t.x
   141  		{`db1.foo.x`, `db1.public.foo.x`, ``},
   142  		{`db2.foo.x`, `db2.public.foo.x`, ``},
   143  
   144  		{`a.b.c.x`, ``, `no data source matches prefix: a\.b\.c`},
   145  		{`"".crdb_internal.tables.table_name`, `"".crdb_internal.tables.table_name`, ``},
   146  		{`db1.public.foo.x`, `db1.public.foo.x`, ``},
   147  		{`db2.public.foo.x`, `db2.public.foo.x`, ``},
   148  		{`db1.public.kv.v`, `db1.public.kv.v`, ``},
   149  	}
   150  
   151  	initColumnItemResolverTester(t, ct)
   152  	resolver := ct.GetColumnItemResolver()
   153  	for _, tc := range testCases {
   154  		t.Run(tc.in, func(t *testing.T) {
   155  			out, err := func() (string, error) {
   156  				stmt, err := parser.ParseOne(fmt.Sprintf("SELECT %s", tc.in))
   157  				if err != nil {
   158  					return "", err
   159  				}
   160  				v := stmt.AST.(*tree.Select).Select.(*tree.SelectClause).Exprs[0].Expr.(tree.VarName)
   161  				c, err := v.NormalizeVarName()
   162  				if err != nil {
   163  					return "", err
   164  				}
   165  				ci, ok := c.(*tree.ColumnItem)
   166  				if !ok {
   167  					return "", fmt.Errorf("var name %s (%T) did not resolve to ColumnItem, found %T instead",
   168  						v, v, c)
   169  				}
   170  				res, err := ci.Resolve(context.Background(), resolver)
   171  				if err != nil {
   172  					return "", err
   173  				}
   174  				return ct.ResolveColumnItemTestResults(res)
   175  			}()
   176  			if !testutils.IsError(err, tc.err) {
   177  				t.Fatalf("%s: expected %s, but found %v", tc.in, tc.err, err)
   178  			}
   179  			if tc.err != "" {
   180  				return
   181  			}
   182  
   183  			if tc.out != out {
   184  				t.Fatalf("%s: expected %s, but found %s", tc.in, tc.out, out)
   185  			}
   186  		})
   187  	}
   188  }