aboutsummaryrefslogtreecommitdiff
path: root/plan.go
diff options
context:
space:
mode:
authorStefan Majewsky <majewsky@gmx.net>2026-05-22 14:01:24 +0200
committerStefan Majewsky <majewsky@gmx.net>2026-05-22 14:01:24 +0200
commit764eaf643e323b92a616fc8e6a193855bb43d905 (patch)
tree935827e791480719a1cf63f806c7e21006a0fb19 /plan.go
parent091f9b68a70d617a38ddf7a662aaf351724be746 (diff)
downloadgo-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.go39
1 files changed, 38 insertions, 1 deletions
diff --git a/plan.go b/plan.go
index 830899e..dbcc012 100644
--- a/plan.go
+++ b/plan.go
@@ -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)