Skip to content

Commit ba57b34

Browse files
necrophidiaPhillip Leonardo
andauthored
add InsertWithAutoID (#6)
Co-authored-by: Phillip Leonardo <phillip.leonardo@idexpress.com>
1 parent 49f1148 commit ba57b34

File tree

2 files changed

+103
-2
lines changed

2 files changed

+103
-2
lines changed

README.md

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,26 @@ A type-safe, generic wrapper for Google Cloud Datastore in Go. Provides a fluent
77
- **Type-safe generics** - Compile-time type checking for all operations
88
- **Fluent API** - Chainable methods for building queries
99
- **Pagination support** - Both offset and cursor-based pagination
10-
- **Batch operations** - Efficient multi-entity upsert and delete
10+
- **Batch operations** - Efficient multi-entity get, upsert, and delete
1111
- **Filter operators** - Type-safe enum for query operators
1212
- **Aggregation queries** - Efficient count operations without loading entities
13+
- **Auto-generated IDs** - Insert entities with Datastore-assigned IDs
1314

1415
## Installation
16+
1517
```bash
1618
go get github.com/yourusername/dsx
1719
```
1820

1921
## Quick Start
22+
2023
```go
2124
package main
2225

2326
import (
2427
"context"
2528
"log"
29+
"time"
2630

2731
"github.com/yourusername/dsx"
2832
)
@@ -62,6 +66,7 @@ func main() {
6266
## API Reference
6367

6468
### Connecting
69+
6570
```go
6671
// Using default credentials (GOOGLE_APPLICATION_CREDENTIALS)
6772
db, err := dsx.Connect(ctx, "project-id", "", "")
@@ -76,11 +81,13 @@ db, err := dsx.Connect(ctx, "project-id", "", credentialsJSON)
7681
### Querying
7782

7883
#### Basic Select
84+
7985
```go
8086
users, err := dsx.Query[User](db, ctx, "User").Select()
8187
```
8288

8389
#### With Filters
90+
8491
```go
8592
// Single filter
8693
users, err := dsx.Query[User](db, ctx, "User").
@@ -117,6 +124,7 @@ users, err := dsx.Query[User](db, ctx, "User").
117124
| `dsx.OpNotIn` | Not in list |
118125

119126
#### Ordering
127+
120128
```go
121129
// Ascending
122130
users, err := dsx.Query[User](db, ctx, "User").
@@ -136,6 +144,7 @@ users, err := dsx.Query[User](db, ctx, "User").
136144
```
137145

138146
#### Get Single Entity
147+
139148
```go
140149
user, err := dsx.Query[User](db, ctx, "User").
141150
WithFilter("Email", dsx.OpEqual, "john@example.com").
@@ -147,6 +156,14 @@ if user == nil {
147156
}
148157
```
149158

159+
#### Get Multiple Entities by ID
160+
161+
```go
162+
users, err := dsx.GetMulti[User](db, ctx, "User", []string{"user-1", "user-2", "user-3"})
163+
```
164+
165+
Entities that don't exist will be zero-valued in the result slice. The result slice maintains the same order as the input keys.
166+
150167
### Counting Entities
151168

152169
Use `Count()` to efficiently count entities matching a query without loading them into memory.
@@ -166,6 +183,7 @@ activeCount, err := dsx.Query[User](db, ctx, "User").
166183
### Pagination
167184

168185
#### Offset-based (Simple)
186+
169187
```go
170188
// Page 1
171189
users, err := dsx.Query[User](db, ctx, "User").
@@ -182,6 +200,7 @@ users, err := dsx.Query[User](db, ctx, "User").
182200
> **Note:** Datastore has a maximum offset of 1000. For deeper pagination, use cursors.
183201
184202
#### Cursor-based (Efficient)
203+
185204
```go
186205
// First page
187206
users, cursor, err := dsx.Query[User](db, ctx, "User").
@@ -218,6 +237,7 @@ for {
218237
### Upserting
219238

220239
#### Single Entity
240+
221241
```go
222242
user := User{
223243
Name: "John Doe",
@@ -230,6 +250,7 @@ err := dsx.Query[User](db, ctx, "User").Upsert("user-123", &user)
230250
```
231251

232252
#### Multiple Entities
253+
233254
```go
234255
users := map[string]*User{
235256
"user-1": {Name: "Alice", Email: "alice@example.com", Status: "active"},
@@ -242,7 +263,26 @@ err := dsx.Query[User](db, ctx, "User").UpsertMulti(users)
242263

243264
> **Note:** Datastore limits batch operations to 500 entities.
244265
266+
#### Insert with Auto-generated ID
267+
268+
Use `InsertWithAutoID` when you want Datastore to generate a unique numeric ID and need to know the ID after insertion.
269+
270+
```go
271+
order := Order{
272+
CustomerID: "cust-123",
273+
Total: 99.99,
274+
CreatedAt: time.Now(),
275+
}
276+
277+
key, err := dsx.Query[Order](db, ctx, "Order").InsertWithAutoID(&order)
278+
if err != nil {
279+
return err
280+
}
281+
fmt.Printf("Created order with ID: %d\n", key.ID)
282+
```
283+
245284
### Deleting
285+
246286
```go
247287
// Delete by filter
248288
err := dsx.Query[User](db, ctx, "User").
@@ -260,6 +300,7 @@ err := dsx.Query[User](db, ctx, "User").
260300
### Advanced Features
261301

262302
#### Ancestor Queries
303+
263304
```go
264305
companyKey := datastore.NameKey("Company", "acme", nil)
265306

@@ -269,21 +310,23 @@ employees, err := dsx.Query[Employee](db, ctx, "Employee").
269310
```
270311

271312
#### Distinct Results
313+
272314
```go
273315
users, err := dsx.Query[User](db, ctx, "User").
274316
WithDistinct().
275317
Select()
276318
```
277319

278320
#### Keys Only
321+
279322
```go
280-
// Get keys only (more efficient for counting)
281323
qb := dsx.Query[User](db, ctx, "User").
282324
WithFilter("Status", dsx.OpEqual, "active").
283325
KeysOnly()
284326
```
285327

286328
#### Access Underlying Client
329+
287330
```go
288331
// For operations not covered by dsx
289332
client := db.Client()
@@ -292,6 +335,7 @@ client := db.Client()
292335
## Indexing
293336

294337
Datastore requires indexes for queries. Simple single-property filters use built-in indexes, but composite queries need explicit indexes in `index.yaml`:
338+
295339
```yaml
296340
indexes:
297341
- kind: User
@@ -302,6 +346,7 @@ indexes:
302346
```
303347
304348
This index supports:
349+
305350
```go
306351
dsx.Query[User](db, ctx, "User").
307352
WithFilter("Status", dsx.OpEqual, "active").
@@ -312,6 +357,7 @@ dsx.Query[User](db, ctx, "User").
312357
## Best Practices
313358

314359
### Use Limit with Get()
360+
315361
```go
316362
// Good - efficient
317363
user, err := dsx.Query[User](db, ctx, "User").
@@ -326,6 +372,7 @@ user, err := dsx.Query[User](db, ctx, "User").
326372
```
327373

328374
### Use Count() Instead of Loading Entities
375+
329376
```go
330377
// Good - uses aggregation query, no data loaded
331378
count, err := dsx.Query[User](db, ctx, "User").
@@ -339,7 +386,22 @@ users, err := dsx.Query[User](db, ctx, "User").
339386
count := len(users)
340387
```
341388

389+
### Use GetMulti for Multiple Known IDs
390+
391+
```go
392+
// Good - single API call
393+
users, err := dsx.GetMulti[User](db, ctx, "User", []string{"user-1", "user-2", "user-3"})
394+
395+
// Bad - multiple API calls
396+
for _, id := range ids {
397+
user, err := dsx.Query[User](db, ctx, "User").
398+
WithFilter(dsx.FieldKey, dsx.OpEqual, id).
399+
Get()
400+
}
401+
```
402+
342403
### Use Cursors for Deep Pagination
404+
343405
```go
344406
// Good - efficient at any depth
345407
users, cursor, err := dsx.Query[User](db, ctx, "User").
@@ -355,6 +417,7 @@ users, err := dsx.Query[User](db, ctx, "User").
355417
```
356418

357419
### Batch Operations for Multiple Entities
420+
358421
```go
359422
// Good - single API call
360423
err := dsx.Query[User](db, ctx, "User").UpsertMulti(usersMap)
@@ -368,6 +431,7 @@ for id, user := range usersMap {
368431
### Use noindex for Non-Queryable Fields
369432

370433
In your struct, mark fields you don't query to save on index writes:
434+
371435
```go
372436
type User struct {
373437
Name string
@@ -380,13 +444,17 @@ type User struct {
380444
## Error Handling
381445

382446
The package logs errors with context before returning them:
447+
383448
```
384449
datastore User select-error <error details>
385450
datastore User upsert-error <error details>
386451
datastore User delete get-all error <error details>
452+
datastore get-multi User error <error details>
453+
datastore Order insert-with-auto-id-error <error details>
387454
```
388455

389456
Common errors:
457+
390458
- **"query defined to use offset instead of cursor"** - Can't use `SelectWithCursor()` after `WithOffset()`
391459
- **"query defined to use cursor"** - Can't use `Select()` after `WithCursor()`
392460

dsx.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,39 @@ func (qb *QueryBuilder[T]) Upsert(id string, data *T) error {
586586
return nil
587587
}
588588

589+
// InsertWithAutoID inserts a new entity with an auto-generated numeric ID and returns the complete key.
590+
// This always creates a new entity since Datastore assigns a unique ID.
591+
//
592+
// Use this when you don't need to control the entity's key but need to know
593+
// the generated ID after insertion (e.g., for returning the ID to a client or logging).
594+
//
595+
// Parameters:
596+
// - data: Pointer to the entity data
597+
//
598+
// Returns the complete key with the generated ID, or an error if insertion fails.
599+
//
600+
// Example:
601+
//
602+
// order := Order{
603+
// CustomerID: "cust-123",
604+
// Total: 99.99,
605+
// CreatedAt: time.Now(),
606+
// }
607+
// key, err := dsx.Query[Order](db, ctx, "Order").InsertWithAutoID(&order)
608+
// if err != nil {
609+
// return err
610+
// }
611+
// fmt.Printf("Created order with ID: %d\n", key.ID)
612+
func (qb *QueryBuilder[T]) InsertWithAutoID(data *T) (*datastore.Key, error) {
613+
key := datastore.IncompleteKey(qb.kind, nil)
614+
completeKey, err := qb.db.client.Put(qb.context, key, data)
615+
if err != nil {
616+
log.Println("datastore", qb.kind, "insert-with-auto-id-error", err)
617+
return nil, err
618+
}
619+
return completeKey, nil
620+
}
621+
589622
// UpsertMulti inserts or updates multiple entities in a single batch operation.
590623
// This is more efficient than calling Upsert multiple times.
591624
//

0 commit comments

Comments
 (0)