Skip to content

Commit f70a741

Browse files
akoclaude
andcommitted
fix: CREATE OR REPLACE IMAGE COLLECTION fails with "already exists" (issue #436)
Three layers were missing support for CreateOrReplace: - ast: added CreateOrReplace bool field to CreateImageCollectionStmt - visitor: read OR REPLACE from parent createStatement via findParentCreateStatement(), same pattern used by business events - executor: when CreateOrReplace is set and collection exists, delete it before creating; otherwise return the existing "already exists" error Added tests for both the plain-create conflict case and the create-or-replace delete+recreate path. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent ce24274 commit f70a741

4 files changed

Lines changed: 80 additions & 5 deletions

File tree

mdl/ast/ast_imagecollection.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@ type ImageItem struct {
1212
//
1313
// CREATE IMAGE COLLECTION Module.Name [EXPORT LEVEL 'Public'] [COMMENT '...'] [(IMAGE "name" FROM FILE 'path', ...)]
1414
type CreateImageCollectionStmt struct {
15-
Name QualifiedName
16-
ExportLevel string // "Hidden" (default) or "Public"
17-
Comment string
18-
Images []ImageItem
15+
Name QualifiedName
16+
CreateOrReplace bool
17+
ExportLevel string // "Hidden" (default) or "Public"
18+
Comment string
19+
Images []ImageItem
1920
}
2021

2122
func (s *CreateImageCollectionStmt) isStatement() {}

mdl/executor/cmd_imagecollections.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,12 @@ func execCreateImageCollection(ctx *ExecContext, s *ast.CreateImageCollectionStm
2929
// Check if image collection already exists
3030
existing := findImageCollection(ctx, s.Name.Module, s.Name.Name)
3131
if existing != nil {
32-
return mdlerrors.NewAlreadyExists("image collection", s.Name.Module+"."+s.Name.Name)
32+
if !s.CreateOrReplace {
33+
return mdlerrors.NewAlreadyExists("image collection", s.Name.Module+"."+s.Name.Name)
34+
}
35+
if err := ctx.Backend.DeleteImageCollection(string(existing.ID)); err != nil {
36+
return mdlerrors.NewBackend("delete existing image collection", err)
37+
}
3338
}
3439

3540
// Build ImageCollection

mdl/executor/cmd_imagecollections_mock_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,67 @@ func TestDescribeImageCollection_Mock(t *testing.T) {
105105
out := buf.String()
106106
assertContainsStr(t, out, "create or replace image collection")
107107
}
108+
109+
func TestCreateImageCollection_AlreadyExists_Error(t *testing.T) {
110+
mod := mkModule("MyModule")
111+
existing := &types.ImageCollection{
112+
BaseElement: model.BaseElement{ID: nextID("ic")},
113+
ContainerID: mod.ID,
114+
Name: "Icons",
115+
}
116+
h := mkHierarchy(mod)
117+
withContainer(h, existing.ContainerID, mod.ID)
118+
119+
mb := &mock.MockBackend{
120+
IsConnectedFunc: func() bool { return true },
121+
ListModulesFunc: func() ([]*model.Module, error) { return []*model.Module{mod}, nil },
122+
ListImageCollectionsFunc: func() ([]*types.ImageCollection, error) { return []*types.ImageCollection{existing}, nil },
123+
}
124+
ctx, _ := newMockCtx(t, withBackend(mb), withHierarchy(h))
125+
126+
err := execCreateImageCollection(ctx, &ast.CreateImageCollectionStmt{
127+
Name: ast.QualifiedName{Module: "MyModule", Name: "Icons"},
128+
})
129+
assertError(t, err)
130+
}
131+
132+
func TestCreateImageCollection_CreateOrReplace_DeletesAndRecreates(t *testing.T) {
133+
mod := mkModule("MyModule")
134+
existing := &types.ImageCollection{
135+
BaseElement: model.BaseElement{ID: nextID("ic")},
136+
ContainerID: mod.ID,
137+
Name: "Icons",
138+
}
139+
h := mkHierarchy(mod)
140+
withContainer(h, existing.ContainerID, mod.ID)
141+
142+
deleted := false
143+
created := false
144+
mb := &mock.MockBackend{
145+
IsConnectedFunc: func() bool { return true },
146+
ListModulesFunc: func() ([]*model.Module, error) { return []*model.Module{mod}, nil },
147+
ListImageCollectionsFunc: func() ([]*types.ImageCollection, error) { return []*types.ImageCollection{existing}, nil },
148+
DeleteImageCollectionFunc: func(id string) error {
149+
deleted = true
150+
return nil
151+
},
152+
CreateImageCollectionFunc: func(ic *types.ImageCollection) error {
153+
created = true
154+
return nil
155+
},
156+
}
157+
ctx, buf := newMockCtx(t, withBackend(mb), withHierarchy(h))
158+
159+
err := execCreateImageCollection(ctx, &ast.CreateImageCollectionStmt{
160+
Name: ast.QualifiedName{Module: "MyModule", Name: "Icons"},
161+
CreateOrReplace: true,
162+
})
163+
assertNoError(t, err)
164+
if !deleted {
165+
t.Error("expected existing collection to be deleted")
166+
}
167+
if !created {
168+
t.Error("expected new collection to be created")
169+
}
170+
assertContainsStr(t, buf.String(), "Created image collection")
171+
}

mdl/visitor/visitor_imagecollection.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,10 @@ func (b *Builder) ExitCreateImageCollectionStatement(ctx *parser.CreateImageColl
4646
}
4747
}
4848

49+
createStmt := findParentCreateStatement(ctx)
50+
if createStmt != nil && createStmt.OR() != nil && (createStmt.REPLACE() != nil || createStmt.MODIFY() != nil) {
51+
stmt.CreateOrReplace = true
52+
}
53+
4954
b.statements = append(b.statements, stmt)
5055
}

0 commit comments

Comments
 (0)