-
Notifications
You must be signed in to change notification settings - Fork 1k
Expand file tree
/
Copy pathresolve.go
More file actions
274 lines (245 loc) · 7.54 KB
/
resolve.go
File metadata and controls
274 lines (245 loc) · 7.54 KB
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
package scope
import "fmt"
// ResolutionError describes why name resolution failed with full provenance.
type ResolutionError struct {
Name string
Qualifier string // Table/alias qualifier, if any
Kind ResolutionErrorKind
Scope *Scope // The scope where resolution was attempted
Candidates []string // For ambiguity errors, the competing names
Location int // Source position of the reference
}
type ResolutionErrorKind int
const (
ErrNotFound ResolutionErrorKind = iota
ErrAmbiguous
ErrQualifierNotFound // e.g., "u.name" but "u" doesn't exist
)
func (e *ResolutionError) Error() string {
switch e.Kind {
case ErrNotFound:
if e.Qualifier != "" {
return fmt.Sprintf("column %q not found in %q", e.Name, e.Qualifier)
}
return fmt.Sprintf("column %q does not exist", e.Name)
case ErrAmbiguous:
return fmt.Sprintf("column reference %q is ambiguous", e.Name)
case ErrQualifierNotFound:
return fmt.Sprintf("table or alias %q does not exist", e.Qualifier)
default:
return fmt.Sprintf("resolution error for %q", e.Name)
}
}
// ResolutionPath records the edges traversed during successful resolution.
// This is the provenance — it tells you exactly how a name was resolved.
type ResolutionPath struct {
Steps []ResolutionStep
}
type ResolutionStep struct {
Edge *Edge // nil for the final lookup step
Scope *Scope // The scope where this step occurred
}
// ResolvedName is the result of successful name resolution.
type ResolvedName struct {
Declaration *Declaration
Path ResolutionPath
}
// Resolve looks up an unqualified column name in this scope.
// It searches local declarations first, then follows parent edges.
// Returns an error if the name is not found or is ambiguous.
func (s *Scope) Resolve(name string) (*ResolvedName, error) {
return s.resolve(name, nil, 0)
}
// ResolveQualified looks up a qualified name like "u.name".
// First resolves the qualifier (table/alias), then looks up the column
// in that table's scope.
func (s *Scope) ResolveQualified(qualifier, name string) (*ResolvedName, error) {
// First, find the qualifier (table name or alias)
qualScope, err := s.resolveQualifier(qualifier, 0)
if err != nil {
return nil, &ResolutionError{
Name: name,
Qualifier: qualifier,
Kind: ErrQualifierNotFound,
Scope: s,
}
}
// Then resolve the column within that scope
var matches []*Declaration
for _, d := range qualScope.Declarations {
if d.Name == name {
matches = append(matches, d)
}
}
if len(matches) == 0 {
return nil, &ResolutionError{
Name: name,
Qualifier: qualifier,
Kind: ErrNotFound,
Scope: qualScope,
}
}
if len(matches) > 1 {
return nil, &ResolutionError{
Name: name,
Qualifier: qualifier,
Kind: ErrAmbiguous,
Scope: qualScope,
}
}
return &ResolvedName{
Declaration: matches[0],
Path: ResolutionPath{
Steps: []ResolutionStep{
{Scope: s},
{Scope: qualScope},
},
},
}, nil
}
const maxResolutionDepth = 20
// resolve performs recursive name resolution with cycle detection via depth limit.
func (s *Scope) resolve(name string, visited map[*Scope]bool, depth int) (*ResolvedName, error) {
if depth > maxResolutionDepth {
return nil, &ResolutionError{Name: name, Kind: ErrNotFound, Scope: s}
}
if visited == nil {
visited = make(map[*Scope]bool)
}
if visited[s] {
return nil, &ResolutionError{Name: name, Kind: ErrNotFound, Scope: s}
}
visited[s] = true
// Search local declarations first
var matches []*Declaration
for _, d := range s.Declarations {
if d.Name == name && d.Kind == DeclColumn {
matches = append(matches, d)
}
}
// Also search table/alias declarations to find columns inside their scopes
for _, d := range s.Declarations {
if (d.Kind == DeclTable || d.Kind == DeclAlias || d.Kind == DeclCTE) && d.Scope != nil {
for _, cd := range d.Scope.Declarations {
if cd.Name == name && cd.Kind == DeclColumn {
matches = append(matches, cd)
}
}
}
}
if len(matches) == 1 {
return &ResolvedName{
Declaration: matches[0],
Path: ResolutionPath{
Steps: []ResolutionStep{{Scope: s}},
},
}, nil
}
if len(matches) > 1 {
return nil, &ResolutionError{Name: name, Kind: ErrAmbiguous, Scope: s}
}
// Follow parent, lateral, and outer edges
for _, edge := range s.Edges {
switch edge.Kind {
case EdgeParent, EdgeLateral, EdgeOuter:
result, err := edge.Target.resolve(name, visited, depth+1)
if err == nil {
result.Path.Steps = append([]ResolutionStep{{Edge: edge, Scope: s}}, result.Path.Steps...)
return result, nil
}
// Propagate ambiguity errors — don't swallow them
if resErr, ok := err.(*ResolutionError); ok && resErr.Kind == ErrAmbiguous {
return nil, resErr
}
}
}
return nil, &ResolutionError{Name: name, Kind: ErrNotFound, Scope: s}
}
// resolveQualifier finds the scope associated with a table name or alias.
func (s *Scope) resolveQualifier(qualifier string, depth int) (*Scope, error) {
if depth > maxResolutionDepth {
return nil, fmt.Errorf("qualifier %q not found", qualifier)
}
// Check alias edges first (higher priority)
for _, edge := range s.Edges {
if edge.Kind == EdgeAlias && edge.Label == qualifier {
return edge.Target, nil
}
}
// Check local table/alias declarations
for _, d := range s.Declarations {
if d.Name == qualifier && (d.Kind == DeclTable || d.Kind == DeclAlias || d.Kind == DeclCTE) && d.Scope != nil {
return d.Scope, nil
}
}
// Follow parent edges
for _, edge := range s.Edges {
if edge.Kind == EdgeParent || edge.Kind == EdgeLateral || edge.Kind == EdgeOuter {
result, err := edge.Target.resolveQualifier(qualifier, depth+1)
if err == nil {
return result, nil
}
}
}
return nil, fmt.Errorf("qualifier %q not found", qualifier)
}
// ResolveColumnRef resolves a column reference that may have 1, 2, or 3 parts:
// - ["name"] -> unqualified column
// - ["alias", "name"] -> table-qualified column
// - ["schema", "table", "name"] -> schema-qualified column (treated as qualifier="table")
func (s *Scope) ResolveColumnRef(parts []string) (*ResolvedName, error) {
switch len(parts) {
case 1:
return s.Resolve(parts[0])
case 2:
return s.ResolveQualified(parts[0], parts[1])
case 3:
// For now, ignore schema and use table.column
return s.ResolveQualified(parts[1], parts[2])
default:
return nil, fmt.Errorf("invalid column reference with %d parts", len(parts))
}
}
// AllColumns returns all column declarations visible from this scope,
// optionally filtered by a qualifier. This is used for SELECT * expansion.
func (s *Scope) AllColumns(qualifier string) []*Declaration {
if qualifier != "" {
qualScope, err := s.resolveQualifier(qualifier, 0)
if err != nil {
return nil
}
var cols []*Declaration
for _, d := range qualScope.Declarations {
if d.Kind == DeclColumn {
cols = append(cols, d)
}
}
return cols
}
// Collect from all table/alias declarations in this scope
var cols []*Declaration
seen := make(map[string]bool)
var collect func(sc *Scope, depth int)
collect = func(sc *Scope, depth int) {
if depth > maxResolutionDepth {
return
}
for _, d := range sc.Declarations {
if (d.Kind == DeclTable || d.Kind == DeclAlias || d.Kind == DeclCTE) && d.Scope != nil {
for _, cd := range d.Scope.Declarations {
if cd.Kind == DeclColumn && !seen[d.Name+"."+cd.Name] {
seen[d.Name+"."+cd.Name] = true
cols = append(cols, cd)
}
}
}
}
for _, edge := range sc.Edges {
if edge.Kind == EdgeParent {
collect(edge.Target, depth+1)
}
}
}
collect(s, 0)
return cols
}