|
6 | 6 | "strings" |
7 | 7 | "testing" |
8 | 8 |
|
| 9 | + "github.com/sqlc-dev/sqlc/internal/sql/ast" |
| 10 | + "github.com/sqlc-dev/sqlc/internal/sql/catalog" |
9 | 11 | "github.com/sqlc-dev/sqlc/internal/sql/sqlerr" |
10 | 12 |
|
11 | 13 | "github.com/google/go-cmp/cmp" |
@@ -168,3 +170,119 @@ func TestUpdateErrors(t *testing.T) { |
168 | 170 | }) |
169 | 171 | } |
170 | 172 | } |
| 173 | + |
| 174 | +func TestDropTableCascadeViewRecreate(t *testing.T) { |
| 175 | + // Regression test for https://github.com/sqlc-dev/sqlc/issues/4416 |
| 176 | + // DROP TABLE CASCADE should remove dependent views from the catalog, |
| 177 | + // allowing a subsequent CREATE VIEW with the same name to succeed. |
| 178 | + p := NewParser() |
| 179 | + |
| 180 | + // First: create the table |
| 181 | + stmts1, err := p.Parse(strings.NewReader(` |
| 182 | + CREATE TABLE reference_rates (id BIGSERIAL PRIMARY KEY); |
| 183 | + `)) |
| 184 | + if err != nil { |
| 185 | + t.Fatalf("parse error: %v", err) |
| 186 | + } |
| 187 | + |
| 188 | + c := NewCatalog() |
| 189 | + if err := c.Build(stmts1); err != nil { |
| 190 | + t.Fatalf("create table error: %v", err) |
| 191 | + } |
| 192 | + |
| 193 | + // Manually add a view that depends on reference_rates to the catalog |
| 194 | + var schema *catalog.Schema |
| 195 | + for _, s := range c.Schemas { |
| 196 | + if s.Name == "public" { |
| 197 | + schema = s |
| 198 | + } |
| 199 | + } |
| 200 | + schema.Tables = append(schema.Tables, &catalog.Table{ |
| 201 | + Rel: &ast.TableName{Schema: "public", Name: "vw_reference_rates"}, |
| 202 | + Columns: []*catalog.Column{{Name: "id"}}, |
| 203 | + DependsOnTables: []*ast.TableName{ |
| 204 | + {Schema: "public", Name: "reference_rates"}, |
| 205 | + }, |
| 206 | + }) |
| 207 | + |
| 208 | + // Verify the view exists |
| 209 | + if !viewExists(schema, "vw_reference_rates") { |
| 210 | + t.Fatal("view not found in catalog before drop") |
| 211 | + } |
| 212 | + |
| 213 | + // Second: DROP TABLE CASCADE |
| 214 | + stmts2, err := p.Parse(strings.NewReader(` |
| 215 | + DROP TABLE reference_rates CASCADE; |
| 216 | + `)) |
| 217 | + if err != nil { |
| 218 | + t.Fatalf("parse error: %v", err) |
| 219 | + } |
| 220 | + if err := c.Build(stmts2); err != nil { |
| 221 | + t.Fatalf("DROP TABLE CASCADE error: %v", err) |
| 222 | + } |
| 223 | + |
| 224 | + // Verify the view was removed |
| 225 | + if viewExists(schema, "vw_reference_rates") { |
| 226 | + t.Fatal("expected view to be removed by CASCADE, but it still exists") |
| 227 | + } |
| 228 | +} |
| 229 | + |
| 230 | +func TestDropTableCascadeWithoutCascadeFails(t *testing.T) { |
| 231 | + // Without CASCADE, dropping a table that has a dependent view leaves the view |
| 232 | + // in the catalog (matching current sqlc behavior, though real PostgreSQL would |
| 233 | + // reject DROP TABLE without CASCADE when views depend on it). |
| 234 | + p := NewParser() |
| 235 | + |
| 236 | + // Create the table |
| 237 | + stmts1, err := p.Parse(strings.NewReader(` |
| 238 | + CREATE TABLE reference_rates (id BIGSERIAL PRIMARY KEY); |
| 239 | + `)) |
| 240 | + if err != nil { |
| 241 | + t.Fatalf("parse error: %v", err) |
| 242 | + } |
| 243 | + |
| 244 | + c := NewCatalog() |
| 245 | + if err := c.Build(stmts1); err != nil { |
| 246 | + t.Fatalf("create table error: %v", err) |
| 247 | + } |
| 248 | + |
| 249 | + // Manually add a view that depends on reference_rates |
| 250 | + schema := c.Schemas[0] |
| 251 | + for _, s := range c.Schemas { |
| 252 | + if s.Name == "public" { |
| 253 | + schema = s |
| 254 | + } |
| 255 | + } |
| 256 | + schema.Tables = append(schema.Tables, &catalog.Table{ |
| 257 | + Rel: &ast.TableName{Schema: "public", Name: "vw_reference_rates"}, |
| 258 | + Columns: []*catalog.Column{{Name: "id"}}, |
| 259 | + DependsOnTables: []*ast.TableName{ |
| 260 | + {Schema: "public", Name: "reference_rates"}, |
| 261 | + }, |
| 262 | + }) |
| 263 | + |
| 264 | + // DROP TABLE without CASCADE |
| 265 | + stmts2, err := p.Parse(strings.NewReader(` |
| 266 | + DROP TABLE reference_rates; |
| 267 | + `)) |
| 268 | + if err != nil { |
| 269 | + t.Fatalf("parse error: %v", err) |
| 270 | + } |
| 271 | + if err := c.Build(stmts2); err != nil { |
| 272 | + t.Fatalf("DROP TABLE error: %v", err) |
| 273 | + } |
| 274 | + |
| 275 | + // Without CASCADE, the view should still exist in the catalog |
| 276 | + if !viewExists(schema, "vw_reference_rates") { |
| 277 | + t.Fatal("expected view to still exist without CASCADE, but it was removed") |
| 278 | + } |
| 279 | +} |
| 280 | + |
| 281 | +func viewExists(schema *catalog.Schema, name string) bool { |
| 282 | + for _, tbl := range schema.Tables { |
| 283 | + if tbl.Rel.Name == name { |
| 284 | + return true |
| 285 | + } |
| 286 | + } |
| 287 | + return false |
| 288 | +} |
0 commit comments