Skip to content

Commit abcdaed

Browse files
committed
Feat: Allow Pre-save hooks on the create operations
1- Add beforeCreate hook to CreateMany and CreateManyAndReturn wrappers to allow mutation on the struct before it's serialized to SQL rows 2- Add ToRowMap() on {Model}Create as the single exit point from the struct representation, with per field nil checks, default injection, and CUID/UUID generation (if present) 3- establish a flow of create api takes the FieldAssignment spread and generates a struct via assignmentsTo{Model}Create, and the pre-save hook mutates that struct if preset, then, the base struct is converted to to a map via ToRowMap() that's used to generate the SQL rows in create operations 4- Remove RecordsToRowMaps generator function because ToRowMap() now owns the struct to map conversion with proper defaults, eliminating the duplicated default/CUID logic between RecordsToRowMaps and the single-create path 5- Remove Assignments() on {Model}Create because ToRowMap() replaces it as the single back-conversion path structs no longer round-trip through Field assignment 6- Remove validateFn, rowMapFn, and singleCreateFn params from executeCreateMany/AndReturn since validation is handled at FieldAssignment level in the wrapper and map conversion is inline via ToRowMap(), the fallback per-row path uses mapToColsVals + executeInsert instead of re-entering singleCreateFn (which would double-fire hooks, and it did)
1 parent ce22dd6 commit abcdaed

12 files changed

Lines changed: 496 additions & 331 deletions

File tree

generator/generator_test.go

Lines changed: 0 additions & 63 deletions
This file was deleted.

generator/templates/builders_create.gotpl

Lines changed: 12 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -178,27 +178,18 @@ func executeInsert[M any](
178178
}
179179

180180

181-
func executeCreateMany[M any](
181+
func executeCreateMany(
182182
ctx context.Context,
183183
q *Queries,
184-
records []RecordInput,
184+
rowMaps []map[string]any,
185185
tableName string,
186186
colOrder []string,
187-
validateFn func([]FieldAssignment) error,
188-
rowMapFn func([]RecordInput) []map[string]any,
189-
singleCreateFn func(context.Context, []FieldAssignment) (*M, error),
190187
) (int64, error) {
191-
if len(records) == 0 {
188+
if len(rowMaps) == 0 {
192189
return 0, nil
193190
}
194-
for i, rec := range records {
195-
if err := validateFn(rec.Assignments); err != nil {
196-
return 0, fmt.Errorf("validation failed at index %d: %w", i, err)
197-
}
198-
}
199191

200192
if q.dialect.SupportsBulkInsert() {
201-
rowMaps := rowMapFn(records)
202193
query, vals := buildBulkInsertSQL(q.dialect, tableName, rowMaps, colOrder, nil)
203194
res, err := q.exec(ctx, query, vals...)
204195
if err != nil {
@@ -209,8 +200,9 @@ func executeCreateMany[M any](
209200

210201
var count int64
211202
err := q.transaction(ctx, func(txQ *Queries) error {
212-
for _, rec := range records {
213-
_, err := singleCreateFn(ctx, rec.Assignments)
203+
for _, rowMap := range rowMaps {
204+
query, vals := buildBulkInsertSQL(txQ.dialect, tableName, []map[string]any{rowMap}, colOrder, nil)
205+
_, err := txQ.exec(ctx, query, vals...)
214206
if err != nil {
215207
return err
216208
}
@@ -224,33 +216,25 @@ func executeCreateMany[M any](
224216
func executeCreateManyAndReturn[M any, S any, O any](
225217
ctx context.Context,
226218
q *Queries,
227-
records []RecordInput,
219+
rowMaps []map[string]any,
228220
tableName string,
229221
colOrder []string,
230222
selects *S,
231223
omits *O,
232-
validateFn func([]FieldAssignment) error,
233-
rowMapFn func([]RecordInput) []map[string]any,
234224
selectColsFn func(*S, *O, ...string) []string,
235225
loadRelationsFn func(context.Context, []*M, *S) error,
236226
scanFunc func(*M, []string) []any,
237-
singleCreateFn func(context.Context, []FieldAssignment) (*M, error),
238227
hasRelationsFn func(*S) bool,
228+
idCol string,
239229
) ([]*M, error) {
240-
if len(records) == 0 {
230+
if len(rowMaps) == 0 {
241231
return nil, nil
242232
}
243-
for i, rec := range records {
244-
if err := validateFn(rec.Assignments); err != nil {
245-
return nil, fmt.Errorf("validation failed at index %d: %w", i, err)
246-
}
247-
}
248233

249234
hasRelations := selects != nil && hasRelationsFn(selects)
250235
returningCols := selectColsFn(selects, omits)
251236

252237
if q.dialect.SupportsBulkInsert() {
253-
rowMaps := rowMapFn(records)
254238
query, vals := buildBulkInsertSQL(q.dialect, tableName, rowMaps, colOrder, returningCols)
255239
recordsOut := make([]*M, 0)
256240
err := q.transaction(ctx, func(txQ *Queries) error {
@@ -282,8 +266,9 @@ func executeCreateManyAndReturn[M any, S any, O any](
282266

283267
recordsOut := make([]*M, 0)
284268
err := q.transaction(ctx, func(txQ *Queries) error {
285-
for _, rec := range records {
286-
res, err := singleCreateFn(ctx, rec.Assignments)
269+
for _, rowMap := range rowMaps {
270+
cols, vals := mapToColsVals(rowMap, colOrder)
271+
res, err := executeInsert(ctx, txQ, tableName, cols, vals, returningCols, idCol, scanFunc)
287272
if err != nil {
288273
return err
289274
}

generator/templates/model_create.gotpl

Lines changed: 88 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,64 @@ func assignmentsTo{{ .Model.Name }}Create(assignments []FieldAssignment) {{ .Mod
148148
return input
149149
}
150150

151+
func (s *{{ .Model.Name }}Create) ToRowMap() map[string]any {
152+
m := make(map[string]any, {{ len .Model.ScalarFields }})
153+
{{- range $field := .Model.ScalarFields }}
154+
{{- $col := $field.EffectiveColName }}
155+
{{- $fieldName := capitalize $field.Name }}
156+
{{- if $field.EnumRef }}
157+
{{- if $field.IsArray }}
158+
if s.{{ $fieldName }} != nil {
159+
m["{{ $col }}"] = s.{{ $fieldName }}
160+
}
161+
{{- else }}
162+
if s.{{ $fieldName }} != nil {
163+
m["{{ $col }}"] = *s.{{ $fieldName }}
164+
}
165+
{{- end }}
166+
{{- else if $field.IsArray }}
167+
if s.{{ $fieldName }} != nil {
168+
m["{{ $col }}"] = s.{{ $fieldName }}
169+
}
170+
{{- else }}
171+
{{- if and $field.Default (eq $field.Default.Kind.String "Func") }}
172+
{{- if eq $field.Default.FuncName "autoincrement" }}
173+
if s.{{ $fieldName }} != nil {
174+
m["{{ $col }}"] = *s.{{ $fieldName }}
175+
}
176+
{{- else }}
177+
if s.{{ $fieldName }} != nil {
178+
m["{{ $col }}"] = *s.{{ $fieldName }}
179+
} else {
180+
{{- if eq $field.Default.FuncName "cuid" }}
181+
m["{{ $col }}"] = generateCUID()
182+
{{- else if eq $field.Default.FuncName "uuid" }}
183+
m["{{ $col }}"] = generateUUID()
184+
{{- else if eq $field.Default.FuncName "now" }}
185+
m["{{ $col }}"] = time.Now()
186+
{{- end }}
187+
}
188+
{{- end }}
189+
{{- else if or $field.Optional (ne $field.Default nil) }}
190+
if s.{{ $fieldName }} != nil {
191+
m["{{ $col }}"] = *s.{{ $fieldName }}
192+
}
193+
{{- else }}
194+
{{- if and $field.IsID (eq $field.GoType "string") }}
195+
if s.{{ $fieldName }} != "" {
196+
m["{{ $col }}"] = s.{{ $fieldName }}
197+
} else {
198+
m["{{ $col }}"] = generateCUID()
199+
}
200+
{{- else }}
201+
m["{{ $col }}"] = s.{{ $fieldName }}
202+
{{- end }}
203+
{{- end }}
204+
{{- end }}
205+
{{- end }}
206+
return m
207+
}
208+
151209
func (q *Queries) execute{{ .Model.Name }}Create(ctx context.Context, assignments []FieldAssignment, selects *{{ .Model.Name }}Select, omits *{{ .Model.Name }}Omit) (*{{ .Model.Name }}, error) {
152210
input := assignmentsTo{{ .Model.Name }}Create(assignments)
153211

@@ -266,36 +324,6 @@ func (q *Queries) execute{{ .Model.Name }}Create(ctx context.Context, assignment
266324
return res, nil
267325
}
268326

269-
func {{ lowercase .Model.Name }}RecordsToRowMaps(records []RecordInput) []map[string]any {
270-
rowMaps := make([]map[string]any, len(records))
271-
for i, rec := range records {
272-
m := make(map[string]any, len(rec.Assignments))
273-
for _, a := range rec.Assignments {
274-
m[a.Col] = a.Val
275-
}
276-
{{- range $field := .Model.ScalarFields }}
277-
{{- $col := $field.EffectiveColName }}
278-
{{- if and $field.Default (eq $field.Default.Kind.String "Func") }}
279-
if _, ok := m["{{ $col }}"]; !ok {
280-
{{- if eq $field.Default.FuncName "cuid" }}
281-
m["{{ $col }}"] = generateCUID()
282-
{{- else if eq $field.Default.FuncName "uuid" }}
283-
m["{{ $col }}"] = generateUUID()
284-
{{- else if eq $field.Default.FuncName "now" }}
285-
m["{{ $col }}"] = time.Now()
286-
{{- end }}
287-
}
288-
{{- else if and $field.IsID (eq $field.GoType "string") }}
289-
if _, ok := m["{{ $col }}"]; !ok {
290-
m["{{ $col }}"] = generateCUID()
291-
}
292-
{{- end }}
293-
{{- end }}
294-
rowMaps[i] = m
295-
}
296-
return rowMaps
297-
}
298-
299327
func (d *{{ .Model.Name }}Delegate) CreateMany(records ...RecordInput) *CreateManyBuilder[{{ .Model.Name }}] {
300328
return &CreateManyBuilder[{{ .Model.Name }}]{
301329
client: d.client,
@@ -313,25 +341,42 @@ func (d *{{ .Model.Name }}Delegate) CreateManyAndReturn(records ...RecordInput)
313341
}
314342

315343
func (q *Queries) execute{{ .Model.Name }}CreateMany(ctx context.Context, records []RecordInput) (int64, error) {
316-
return executeCreateMany(ctx, q, records, "{{ .Model.EffectiveTableName }}", {{ .Model.Name }}ColOrder,
317-
validate{{ .Model.Name }}Create,
318-
{{ lowercase .Model.Name }}RecordsToRowMaps,
319-
func(ctx context.Context, assignments []FieldAssignment) (*{{ .Model.Name }}, error) {
320-
return q.execute{{ .Model.Name }}Create(ctx, assignments, nil, nil)
321-
},
322-
)
344+
rowMaps := make([]map[string]any, len(records))
345+
for i, rec := range records {
346+
if err := validate{{ .Model.Name }}Create(rec.Assignments); err != nil {
347+
return 0, fmt.Errorf("validation failed at index %d: %w", i, err)
348+
}
349+
input := assignmentsTo{{ .Model.Name }}Create(rec.Assignments)
350+
if q.{{ .Model.Name }}.beforeCreate != nil {
351+
if err := q.{{ .Model.Name }}.beforeCreate(ctx, &input); err != nil {
352+
return 0, err
353+
}
354+
}
355+
rowMaps[i] = input.ToRowMap()
356+
}
357+
return executeCreateMany(ctx, q, rowMaps, "{{ .Model.EffectiveTableName }}", {{ .Model.Name }}ColOrder)
323358
}
324359

325360
func (q *Queries) execute{{ .Model.Name }}CreateManyAndReturn(ctx context.Context, records []RecordInput, selects *{{ .Model.Name }}Select, omits *{{ .Model.Name }}Omit) ([]*{{ .Model.Name }}, error) {
326-
return executeCreateManyAndReturn(ctx, q, records, "{{ .Model.EffectiveTableName }}", {{ .Model.Name }}ColOrder, selects, omits,
327-
validate{{ .Model.Name }}Create,
328-
{{ lowercase .Model.Name }}RecordsToRowMaps,
361+
rowMaps := make([]map[string]any, len(records))
362+
idCol := "{{ range $field := .Model.ScalarFields }}{{ if $field.IsID }}{{ $field.EffectiveColName }}{{ end }}{{ end }}"
363+
for i, rec := range records {
364+
if err := validate{{ .Model.Name }}Create(rec.Assignments); err != nil {
365+
return nil, fmt.Errorf("validation failed at index %d: %w", i, err)
366+
}
367+
input := assignmentsTo{{ .Model.Name }}Create(rec.Assignments)
368+
if q.{{ .Model.Name }}.beforeCreate != nil {
369+
if err := q.{{ .Model.Name }}.beforeCreate(ctx, &input); err != nil {
370+
return nil, err
371+
}
372+
}
373+
rowMaps[i] = input.ToRowMap()
374+
}
375+
return executeCreateManyAndReturn(ctx, q, rowMaps, "{{ .Model.EffectiveTableName }}", {{ .Model.Name }}ColOrder, selects, omits,
329376
q.select{{ .Model.Name }}Cols,
330377
q.load{{ .Model.Name }}Relations,
331378
(*{{ .Model.Name }}).ScanFields,
332-
func(ctx context.Context, assignments []FieldAssignment) (*{{ .Model.Name }}, error) {
333-
return q.execute{{ .Model.Name }}Create(ctx, assignments, nil, nil)
334-
},
335379
(*{{ .Model.Name }}Select).hasAnyRelation,
380+
idCol,
336381
)
337382
}

0 commit comments

Comments
 (0)