diff options
| author | Stefan Majewsky <majewsky@gmx.net> | 2026-05-22 14:01:24 +0200 |
|---|---|---|
| committer | Stefan Majewsky <majewsky@gmx.net> | 2026-05-22 14:01:24 +0200 |
| commit | 764eaf643e323b92a616fc8e6a193855bb43d905 (patch) | |
| tree | 935827e791480719a1cf63f806c7e21006a0fb19 /plan.go | |
| parent | 091f9b68a70d617a38ddf7a662aaf351724be746 (diff) | |
| download | go-oblast-764eaf643e323b92a616fc8e6a193855bb43d905.tar.gz | |
bring back support for LastInsertId-based INSERT
As the remaining TODO noted, this really is much more memory-efficient
than QueryRow when we can use it, since it does not allocate an
*sql.Rows instance inside the *sql.Row instance where we call Scan().
Diffstat (limited to 'plan.go')
| -rw-r--r-- | plan.go | 39 |
1 files changed, 38 insertions, 1 deletions
@@ -26,6 +26,16 @@ type plan struct { // Pointer-typed fields that need to be initialized before scanning into this type. TransparentPointerStructFields []fieldInfo + // Whether the INSERT query uses QueryRow or Exec. + // - When no auto-generated values are collected, or when a single value can be collected through LastInsertId(), + // this will be false because Exec() is more memory-efficient than QueryRow(); it does not have to allocate an *sql.Rows instance. + // - Otherwise, i.e. when auto-generated values are collected with a RETURNING clause, + // this will be true because Exec() does not support scanning result values. + InsertUsesQueryRow bool + // If InsertUsesQueryRow = false and a primary key is collected from LastInsertId(), + // this decides whether we write it with reflect.Value.SetInt() or reflect.Value.SetUint(). + LastInsertIdIsUnsigned bool + // Planned queries. Select plannedQuery // only `SELECT ... FROM ... WHERE `; user supplies the rest during Select{,One}Where() Insert plannedQuery @@ -187,6 +197,33 @@ func buildPlan(t reflect.Type, dialect Dialect, opts planOpts) (plan, error) { } } + // pick strategy for INSERT + if p.TableName != "" { + switch len(p.AutoColumnNames) { + case 0: + p.InsertUsesQueryRow = false + case 1: + if dialect.CanUseLastInsertId() { + columnName := p.AutoColumnNames[0] + field := t.FieldByIndex(p.IndexByColumnName[columnName]) + switch field.Type.Kind() { //nolint:exhaustive // false positive + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + p.InsertUsesQueryRow = false + p.LastInsertIdIsUnsigned = false + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + p.InsertUsesQueryRow = false + p.LastInsertIdIsUnsigned = true + default: + p.InsertUsesQueryRow = true + } + } else { + p.InsertUsesQueryRow = true + } + default: + p.InsertUsesQueryRow = true + } + } + // prepare query strings p.Select = p.buildSelectQueryIfPossible(dialect) p.Insert = p.buildInsertQueryIfPossible(dialect, false) @@ -282,7 +319,7 @@ func (p plan) buildInsertQueryIfPossible(dialect Dialect, isUpsert bool) planned if isUpsert { query += dialect.UpsertClause(p.PrimaryKeyColumnNames, p.getNonPrimaryKeyColumnNames()) } - if len(p.AutoColumnNames) > 0 { + if len(p.AutoColumnNames) > 0 && p.InsertUsesQueryRow { quotedAutoColumns := make([]string, len(p.AutoColumnNames)) for idx, name := range p.AutoColumnNames { quotedAutoColumns[idx] = dialect.QuoteIdentifier(name) |
