|
| 1 | +--- |
| 2 | +phase: 14-delete-with-limit |
| 3 | +plan: 01 |
| 4 | +type: execute |
| 5 | +wave: 1 |
| 6 | +depends_on: [] |
| 7 | +files_modified: |
| 8 | + - pkg/api/v2/options.go |
| 9 | + - pkg/api/v2/collection.go |
| 10 | + - pkg/api/v2/client_local_embedded.go |
| 11 | +autonomous: true |
| 12 | +requirements: [DEL-01, DEL-02, DEL-03, DEL-04] |
| 13 | + |
| 14 | +must_haves: |
| 15 | + truths: |
| 16 | + - "WithLimit(n) can be passed to Collection.Delete alongside a where or where_document filter" |
| 17 | + - "Delete with limit but no filter returns a clear validation error before any network call" |
| 18 | + - "Delete with limit <= 0 returns ErrInvalidLimit" |
| 19 | + - "Limit serializes in the HTTP JSON body when set, omitted when nil" |
| 20 | + - "Embedded path passes limit through to EmbeddedDeleteRecordsRequest as *uint32" |
| 21 | + artifacts: |
| 22 | + - path: "pkg/api/v2/options.go" |
| 23 | + provides: "ApplyToDelete method on limitOption" |
| 24 | + contains: "func (o *limitOption) ApplyToDelete" |
| 25 | + - path: "pkg/api/v2/collection.go" |
| 26 | + provides: "Limit field on CollectionDeleteOp and validation in PrepareAndValidate" |
| 27 | + contains: "Limit *int32" |
| 28 | + - path: "pkg/api/v2/client_local_embedded.go" |
| 29 | + provides: "Limit wiring to EmbeddedDeleteRecordsRequest" |
| 30 | + contains: "deleteObject.Limit" |
| 31 | + key_links: |
| 32 | + - from: "pkg/api/v2/options.go" |
| 33 | + to: "pkg/api/v2/collection.go" |
| 34 | + via: "ApplyToDelete sets CollectionDeleteOp.Limit" |
| 35 | + pattern: "op\\.Limit = &limit" |
| 36 | + - from: "pkg/api/v2/collection.go" |
| 37 | + to: "pkg/api/v2/client_local_embedded.go" |
| 38 | + via: "PrepareAndValidate runs before embedded call; Limit passed through" |
| 39 | + pattern: "deleteObject\\.Limit" |
| 40 | +--- |
| 41 | + |
| 42 | +<objective> |
| 43 | +Add delete-with-limit support to the V2 collection API. |
| 44 | + |
| 45 | +Purpose: Implement the limit parameter for delete operations matching upstream Chroma PRs #6573/#6582, enabling users to cap the number of records deleted by a filter-based delete. |
| 46 | +Output: Working limitOption.ApplyToDelete, CollectionDeleteOp.Limit field with validation, and embedded path wiring. |
| 47 | +</objective> |
| 48 | + |
| 49 | +<execution_context> |
| 50 | +@$HOME/.claude/get-shit-done/workflows/execute-plan.md |
| 51 | +@$HOME/.claude/get-shit-done/templates/summary.md |
| 52 | +</execution_context> |
| 53 | + |
| 54 | +<context> |
| 55 | +@.planning/PROJECT.md |
| 56 | +@.planning/ROADMAP.md |
| 57 | +@.planning/STATE.md |
| 58 | +@.planning/phases/14-delete-with-limit/14-CONTEXT.md |
| 59 | +@.planning/phases/14-delete-with-limit/14-RESEARCH.md |
| 60 | + |
| 61 | +<interfaces> |
| 62 | +<!-- Key types and contracts the executor needs. --> |
| 63 | + |
| 64 | +From pkg/api/v2/options.go: |
| 65 | +```go |
| 66 | +type limitOption struct { |
| 67 | + limit int |
| 68 | +} |
| 69 | + |
| 70 | +func WithLimit(limit int) *limitOption { |
| 71 | + return &limitOption{limit: limit} |
| 72 | +} |
| 73 | + |
| 74 | +func (o *limitOption) ApplyToGet(op *CollectionGetOp) error { ... } |
| 75 | +func (o *limitOption) ApplyToSearchRequest(req *SearchRequest) error { ... } |
| 76 | + |
| 77 | +// Error sentinel: |
| 78 | +var ErrInvalidLimit = errors.New("limit must be greater than 0") |
| 79 | +``` |
| 80 | + |
| 81 | +From pkg/api/v2/collection.go: |
| 82 | +```go |
| 83 | +type DeleteOption interface { |
| 84 | + ApplyToDelete(*CollectionDeleteOp) error |
| 85 | +} |
| 86 | + |
| 87 | +type CollectionDeleteOp struct { |
| 88 | + FilterOp // Where and WhereDocument filters |
| 89 | + FilterIDOp // ID filter |
| 90 | +} |
| 91 | + |
| 92 | +func (c *CollectionDeleteOp) PrepareAndValidate() error { ... } |
| 93 | +func (c *CollectionDeleteOp) MarshalJSON() ([]byte, error) { ... } |
| 94 | +``` |
| 95 | + |
| 96 | +From pkg/api/v2/client_local_embedded.go: |
| 97 | +```go |
| 98 | +func (c *embeddedCollection) Delete(ctx context.Context, opts ...CollectionDeleteOption) error { |
| 99 | + // ... builds deleteObject, calls PrepareAndValidate, then: |
| 100 | + return c.client.embedded.DeleteRecords(localchroma.EmbeddedDeleteRecordsRequest{ |
| 101 | + CollectionID: collectionID, |
| 102 | + IDs: documentIDsToStrings(deleteObject.Ids), |
| 103 | + Where: where, |
| 104 | + WhereDocument: whereDocument, |
| 105 | + TenantID: tenantName, |
| 106 | + DatabaseName: databaseName, |
| 107 | + }) |
| 108 | +} |
| 109 | +``` |
| 110 | +</interfaces> |
| 111 | +</context> |
| 112 | + |
| 113 | +<tasks> |
| 114 | + |
| 115 | +<task type="auto"> |
| 116 | + <name>Task 1: Add Limit field to CollectionDeleteOp and ApplyToDelete to limitOption</name> |
| 117 | + <read_first> |
| 118 | + - pkg/api/v2/collection.go (lines 1090-1150 for CollectionDeleteOp struct and PrepareAndValidate) |
| 119 | + - pkg/api/v2/options.go (lines 520-575 for limitOption type and existing Apply methods; lines 15-30 for option matrix comment) |
| 120 | + </read_first> |
| 121 | + <files>pkg/api/v2/collection.go, pkg/api/v2/options.go</files> |
| 122 | + <action> |
| 123 | +1. In `pkg/api/v2/collection.go`, add `Limit *int32` field to `CollectionDeleteOp` struct (per D-02): |
| 124 | +```go |
| 125 | +type CollectionDeleteOp struct { |
| 126 | + FilterOp // Where and WhereDocument filters |
| 127 | + FilterIDOp // ID filter |
| 128 | + Limit *int32 `json:"limit,omitempty"` |
| 129 | +} |
| 130 | +``` |
| 131 | + |
| 132 | +2. In `CollectionDeleteOp.PrepareAndValidate()` (line 1115), add limit validation AFTER the existing filter validation block (per D-05, D-06): |
| 133 | +```go |
| 134 | +if c.Limit != nil { |
| 135 | + if *c.Limit <= 0 { |
| 136 | + return errors.New("limit must be greater than 0") |
| 137 | + } |
| 138 | + if c.Where == nil && c.WhereDocument == nil { |
| 139 | + return errors.New("limit can only be specified when a where or where_document clause is provided") |
| 140 | + } |
| 141 | +} |
| 142 | +``` |
| 143 | + |
| 144 | +3. In `pkg/api/v2/options.go`, add `ApplyToDelete` method to `limitOption` (per D-01), after the existing `ApplyToSearchRequest` method (line 574): |
| 145 | +```go |
| 146 | +func (o *limitOption) ApplyToDelete(op *CollectionDeleteOp) error { |
| 147 | + if o.limit <= 0 { |
| 148 | + return ErrInvalidLimit |
| 149 | + } |
| 150 | + limit := int32(o.limit) |
| 151 | + op.Limit = &limit |
| 152 | + return nil |
| 153 | +} |
| 154 | +``` |
| 155 | + |
| 156 | +4. Update the option compatibility matrix comment in `options.go` (line 25) to show WithLimit works with Delete: |
| 157 | +``` |
| 158 | +WithLimit | ✓ | | ✓ | | | ✓ |
| 159 | +``` |
| 160 | + |
| 161 | +5. Update the `WithLimit` godoc (around line 525-531) to mention Delete support: |
| 162 | +```go |
| 163 | +// WithLimit sets the maximum number of results to return or delete. |
| 164 | +// |
| 165 | +// Works with [Collection.Get], [Collection.Search], and [Collection.Delete]. |
| 166 | +// For Delete, a where or where_document filter must also be specified. |
| 167 | +// Use with [WithOffset] for pagination (Get and Search only). |
| 168 | +// The limit must be greater than 0. |
| 169 | +// |
| 170 | +// For [Collection.Query], use [WithNResults] instead. |
| 171 | +``` |
| 172 | + |
| 173 | +6. Add a Delete example to the `WithLimit` godoc, after the existing Pagination Example: |
| 174 | +```go |
| 175 | +// # Delete Example |
| 176 | +// |
| 177 | +// err := collection.Delete(ctx, |
| 178 | +// WithWhere(EqString("status", "archived")), |
| 179 | +// WithLimit(100), |
| 180 | +// ) |
| 181 | +``` |
| 182 | + </action> |
| 183 | + <verify> |
| 184 | + <automated>cd /Users/tazarov/GolandProjects/chroma-go && go build ./pkg/api/v2/...</automated> |
| 185 | + </verify> |
| 186 | + <acceptance_criteria> |
| 187 | + - pkg/api/v2/collection.go contains `Limit *int32` inside CollectionDeleteOp struct |
| 188 | + - pkg/api/v2/collection.go PrepareAndValidate contains `"limit can only be specified when a where or where_document clause is provided"` |
| 189 | + - pkg/api/v2/collection.go PrepareAndValidate contains `"limit must be greater than 0"` |
| 190 | + - pkg/api/v2/options.go contains `func (o *limitOption) ApplyToDelete(op *CollectionDeleteOp) error` |
| 191 | + - pkg/api/v2/options.go option matrix row for WithLimit shows checkmark in Delete column |
| 192 | + - `go build ./pkg/api/v2/...` exits 0 |
| 193 | + </acceptance_criteria> |
| 194 | + <done>CollectionDeleteOp has Limit field with validation, limitOption implements ApplyToDelete, option matrix and godoc updated</done> |
| 195 | +</task> |
| 196 | + |
| 197 | +<task type="auto"> |
| 198 | + <name>Task 2: Wire limit through embedded path</name> |
| 199 | + <read_first> |
| 200 | + - pkg/api/v2/client_local_embedded.go (lines 965-1002 for embeddedCollection.Delete method) |
| 201 | + </read_first> |
| 202 | + <files>pkg/api/v2/client_local_embedded.go</files> |
| 203 | + <action> |
| 204 | +In `embeddedCollection.Delete()` (around line 994), add limit conversion before the `DeleteRecords` call and pass it through (per D-04): |
| 205 | + |
| 206 | +Replace the current `return c.client.embedded.DeleteRecords(...)` block with: |
| 207 | +```go |
| 208 | +var limit *uint32 |
| 209 | +if deleteObject.Limit != nil { |
| 210 | + l := uint32(*deleteObject.Limit) |
| 211 | + limit = &l |
| 212 | +} |
| 213 | + |
| 214 | +return c.client.embedded.DeleteRecords(localchroma.EmbeddedDeleteRecordsRequest{ |
| 215 | + CollectionID: collectionID, |
| 216 | + IDs: documentIDsToStrings(deleteObject.Ids), |
| 217 | + Where: where, |
| 218 | + WhereDocument: whereDocument, |
| 219 | + TenantID: tenantName, |
| 220 | + DatabaseName: databaseName, |
| 221 | + Limit: limit, |
| 222 | +}) |
| 223 | +``` |
| 224 | + |
| 225 | +The int32-to-uint32 conversion is safe because PrepareAndValidate has already validated `*Limit > 0`. |
| 226 | + </action> |
| 227 | + <verify> |
| 228 | + <automated>cd /Users/tazarov/GolandProjects/chroma-go && go build ./pkg/api/v2/...</automated> |
| 229 | + </verify> |
| 230 | + <acceptance_criteria> |
| 231 | + - pkg/api/v2/client_local_embedded.go contains `deleteObject.Limit` |
| 232 | + - pkg/api/v2/client_local_embedded.go contains `uint32(*deleteObject.Limit)` |
| 233 | + - pkg/api/v2/client_local_embedded.go EmbeddedDeleteRecordsRequest struct literal includes `Limit: limit` |
| 234 | + - `go build ./pkg/api/v2/...` exits 0 |
| 235 | + </acceptance_criteria> |
| 236 | + <done>Embedded delete path converts int32 limit to uint32 and passes to EmbeddedDeleteRecordsRequest</done> |
| 237 | +</task> |
| 238 | + |
| 239 | +</tasks> |
| 240 | + |
| 241 | +<verification> |
| 242 | +- `go build ./pkg/api/v2/...` compiles without errors |
| 243 | +- `go vet ./pkg/api/v2/...` passes |
| 244 | +</verification> |
| 245 | + |
| 246 | +<success_criteria> |
| 247 | +- CollectionDeleteOp has Limit *int32 field with JSON omitempty tag |
| 248 | +- PrepareAndValidate rejects limit without filter and limit <= 0 with exact upstream error messages |
| 249 | +- limitOption.ApplyToDelete sets Limit on CollectionDeleteOp |
| 250 | +- Embedded path converts and passes limit to EmbeddedDeleteRecordsRequest |
| 251 | +- All code compiles cleanly |
| 252 | +</success_criteria> |
| 253 | + |
| 254 | +<output> |
| 255 | +After completion, create `.planning/phases/14-delete-with-limit/14-01-SUMMARY.md` |
| 256 | +</output> |
0 commit comments