|
| 1 | +package compiler |
| 2 | + |
| 3 | +import ( |
| 4 | + "fmt" |
| 5 | + "reflect" |
| 6 | + |
| 7 | + "github.com/sqlc-dev/sqlc/internal/sql/ast" |
| 8 | + "github.com/sqlc-dev/sqlc/internal/sql/sqlerr" |
| 9 | +) |
| 10 | + |
| 11 | +/* |
| 12 | +This file implements SQLite-specific validation for qualified column references. |
| 13 | +
|
| 14 | +Problem: |
| 15 | + SQLite allows invalid correlated references to pass parsing, e.g. |
| 16 | +
|
| 17 | + SELECT * |
| 18 | + FROM locations l |
| 19 | + WHERE EXISTS ( |
| 20 | + SELECT 1 |
| 21 | + FROM projects p |
| 22 | + WHERE p.id = location.project_id -- invalid: "location" not in scope |
| 23 | + ) |
| 24 | +
|
| 25 | +SQLite itself errors at runtime, but sqlc historically accepted this query. |
| 26 | +This validator rejects such queries during compilation, matching SQLite behavior. |
| 27 | +*/ |
| 28 | + |
| 29 | +// scope represents the set of visible table names and aliases at a SELECT level. |
| 30 | +// parent enables correlated subqueries to see outer query tables. |
| 31 | +type scope struct { |
| 32 | + parent *scope |
| 33 | + names map[string]struct{} |
| 34 | +} |
| 35 | + |
| 36 | +func newScope(parent *scope) *scope { |
| 37 | + return &scope{ |
| 38 | + parent: parent, |
| 39 | + names: map[string]struct{}{}, |
| 40 | + } |
| 41 | +} |
| 42 | + |
| 43 | +// add registers a visible table name or alias. |
| 44 | +func (s *scope) add(name string) { |
| 45 | + if name == "" { |
| 46 | + return |
| 47 | + } |
| 48 | + s.names[name] = struct{}{} |
| 49 | +} |
| 50 | + |
| 51 | +// has checks whether a name is visible in this scope or any parent scope. |
| 52 | +func (s *scope) has(name string) bool { |
| 53 | + for cur := s; cur != nil; cur = cur.parent { |
| 54 | + if _, ok := cur.names[name]; ok { |
| 55 | + return true |
| 56 | + } |
| 57 | + } |
| 58 | + return false |
| 59 | +} |
| 60 | + |
| 61 | +// qualifierFromColumnRef extracts the table/alias portion of a qualified ref. |
| 62 | +// |
| 63 | +// Examples: |
| 64 | +// |
| 65 | +// a.b -> "a" |
| 66 | +// s.a.b -> "a" (schema.table.column) |
| 67 | +// b -> "" (unqualified) |
| 68 | +func qualifierFromColumnRef(ref *ast.ColumnRef) (string, bool) { |
| 69 | + if ref == nil || ref.Fields == nil { |
| 70 | + return "", false |
| 71 | + } |
| 72 | + items := stringSlice(ref.Fields) |
| 73 | + switch len(items) { |
| 74 | + case 2: |
| 75 | + return items[0], true |
| 76 | + case 3: |
| 77 | + return items[1], true |
| 78 | + default: |
| 79 | + return "", false |
| 80 | + } |
| 81 | +} |
| 82 | + |
| 83 | +// addFromItemToScope records tables and aliases introduced by FROM/JOIN items. |
| 84 | +func addFromItemToScope(sc *scope, n ast.Node) { |
| 85 | + switch t := n.(type) { |
| 86 | + case *ast.RangeVar: |
| 87 | + if t.Relname != nil { |
| 88 | + sc.add(*t.Relname) |
| 89 | + } |
| 90 | + if t.Alias != nil && t.Alias.Aliasname != nil { |
| 91 | + sc.add(*t.Alias.Aliasname) |
| 92 | + } |
| 93 | + |
| 94 | + case *ast.JoinExpr: |
| 95 | + addFromItemToScope(sc, t.Larg) |
| 96 | + addFromItemToScope(sc, t.Rarg) |
| 97 | + |
| 98 | + case *ast.RangeSubselect: |
| 99 | + if t.Alias != nil && t.Alias.Aliasname != nil { |
| 100 | + sc.add(*t.Alias.Aliasname) |
| 101 | + } |
| 102 | + |
| 103 | + case *ast.RangeFunction: |
| 104 | + if t.Alias != nil && t.Alias.Aliasname != nil { |
| 105 | + sc.add(*t.Alias.Aliasname) |
| 106 | + } |
| 107 | + } |
| 108 | +} |
| 109 | + |
| 110 | +// validateSQLiteQualifiedColumnRefs is the public entry point. |
| 111 | +// It validates that qualified column references only use visible tables/aliases. |
| 112 | +func validateSQLiteQualifiedColumnRefs(root ast.Node) error { |
| 113 | + return validateNodeSQLite(root, nil) |
| 114 | +} |
| 115 | + |
| 116 | +// validateNodeSQLite validates a SELECT node and establishes a new scope. |
| 117 | +// Nested SELECTs receive the current scope as their parent. |
| 118 | +func validateNodeSQLite(node ast.Node, parent *scope) error { |
| 119 | + switch n := node.(type) { |
| 120 | + case *ast.SelectStmt: |
| 121 | + sc := newScope(parent) |
| 122 | + |
| 123 | + // Collect visible names from FROM clause |
| 124 | + if n.FromClause != nil { |
| 125 | + for _, item := range n.FromClause.Items { |
| 126 | + addFromItemToScope(sc, item) |
| 127 | + } |
| 128 | + } |
| 129 | + |
| 130 | + // Walk this SELECT subtree with the new scope |
| 131 | + return walkSQLite(n, sc) |
| 132 | + |
| 133 | + default: |
| 134 | + // Only SELECTs introduce scopes; other nodes are irrelevant here. |
| 135 | + return nil |
| 136 | + } |
| 137 | +} |
| 138 | + |
| 139 | +// walkSQLite recursively walks an AST node, validating ColumnRef qualifiers. |
| 140 | +func walkSQLite(node ast.Node, sc *scope) error { |
| 141 | + if node == nil { |
| 142 | + return nil |
| 143 | + } |
| 144 | + |
| 145 | + // Pre-order validation: check ColumnRef immediately |
| 146 | + if ref, ok := node.(*ast.ColumnRef); ok { |
| 147 | + if qual, ok := qualifierFromColumnRef(ref); ok && !sc.has(qual) { |
| 148 | + return &sqlerr.Error{ |
| 149 | + Code: "42703", |
| 150 | + Message: fmt.Sprintf("table alias %q does not exist", qual), |
| 151 | + Location: ref.Location, |
| 152 | + } |
| 153 | + } |
| 154 | + } |
| 155 | + |
| 156 | + // Explicit handling of subquery boundaries |
| 157 | + switch n := node.(type) { |
| 158 | + case *ast.SubLink: |
| 159 | + if n.Subselect != nil { |
| 160 | + return validateNodeSQLite(n.Subselect, sc) |
| 161 | + } |
| 162 | + return nil |
| 163 | + |
| 164 | + case *ast.RangeSubselect: |
| 165 | + if n.Subquery != nil { |
| 166 | + return validateNodeSQLite(n.Subquery, sc) |
| 167 | + } |
| 168 | + return nil |
| 169 | + } |
| 170 | + |
| 171 | + // Generic recursion for all other node types |
| 172 | + return walkSQLiteReflect(node, sc) |
| 173 | +} |
| 174 | + |
| 175 | +// walkSQLiteReflect traverses AST nodes via reflection. |
| 176 | +// This avoids dependency on astutils.Walk, whose signature varies. |
| 177 | +func walkSQLiteReflect(node ast.Node, sc *scope) error { |
| 178 | + v := reflect.ValueOf(node) |
| 179 | + if v.Kind() == reflect.Pointer { |
| 180 | + if v.IsNil() { |
| 181 | + return nil |
| 182 | + } |
| 183 | + v = v.Elem() |
| 184 | + } |
| 185 | + if v.Kind() != reflect.Struct { |
| 186 | + return nil |
| 187 | + } |
| 188 | + |
| 189 | + t := v.Type() |
| 190 | + for i := 0; i < v.NumField(); i++ { |
| 191 | + // Skip unexported fields |
| 192 | + if t.Field(i).PkgPath != "" { |
| 193 | + continue |
| 194 | + } |
| 195 | + |
| 196 | + f := v.Field(i) |
| 197 | + if !f.IsValid() { |
| 198 | + continue |
| 199 | + } |
| 200 | + |
| 201 | + // Dereference pointers |
| 202 | + for f.Kind() == reflect.Pointer { |
| 203 | + if f.IsNil() { |
| 204 | + goto next |
| 205 | + } |
| 206 | + f = f.Elem() |
| 207 | + } |
| 208 | + |
| 209 | + // Handle ast.List |
| 210 | + if f.Type() == reflect.TypeOf(ast.List{}) { |
| 211 | + list := f.Addr().Interface().(*ast.List) |
| 212 | + for _, n := range list.Items { |
| 213 | + if err := walkSQLite(n, sc); err != nil { |
| 214 | + return err |
| 215 | + } |
| 216 | + } |
| 217 | + continue |
| 218 | + } |
| 219 | + |
| 220 | + // Handle *ast.List |
| 221 | + if f.CanAddr() { |
| 222 | + if pl, ok := f.Addr().Interface().(**ast.List); ok && *pl != nil { |
| 223 | + for _, n := range (*pl).Items { |
| 224 | + if err := walkSQLite(n, sc); err != nil { |
| 225 | + return err |
| 226 | + } |
| 227 | + } |
| 228 | + continue |
| 229 | + } |
| 230 | + } |
| 231 | + |
| 232 | + // Handle single ast.Node |
| 233 | + if f.CanInterface() { |
| 234 | + if n, ok := f.Interface().(ast.Node); ok { |
| 235 | + if err := walkSQLite(n, sc); err != nil { |
| 236 | + return err |
| 237 | + } |
| 238 | + continue |
| 239 | + } |
| 240 | + } |
| 241 | + |
| 242 | + // Handle slices of ast.Node |
| 243 | + if f.Kind() == reflect.Slice { |
| 244 | + for j := 0; j < f.Len(); j++ { |
| 245 | + elem := f.Index(j) |
| 246 | + if elem.Kind() == reflect.Pointer && elem.IsNil() { |
| 247 | + continue |
| 248 | + } |
| 249 | + if elem.CanInterface() { |
| 250 | + if n, ok := elem.Interface().(ast.Node); ok { |
| 251 | + if err := walkSQLite(n, sc); err != nil { |
| 252 | + return err |
| 253 | + } |
| 254 | + } |
| 255 | + } |
| 256 | + } |
| 257 | + } |
| 258 | + |
| 259 | + next: |
| 260 | + } |
| 261 | + return nil |
| 262 | +} |
0 commit comments