Skip to content

Commit b9e7928

Browse files
committed
Introduce DB#InsertObtainID() method
1 parent ae5ceaa commit b9e7928

File tree

3 files changed

+60
-0
lines changed

3 files changed

+60
-0
lines changed

database/contracts.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
package database
22

3+
import (
4+
"context"
5+
"github.com/jmoiron/sqlx"
6+
)
7+
38
// Entity is implemented by each type that works with the database package.
49
type Entity interface {
510
Fingerprinter
@@ -54,3 +59,10 @@ type PgsqlOnConflictConstrainter interface {
5459
// PgsqlOnConflictConstraint returns the primary or unique key constraint name of the PostgreSQL table.
5560
PgsqlOnConflictConstraint() string
5661
}
62+
63+
// TxOrDB is just a helper interface that can represent a *[sqlx.Tx] or *[DB] instance.
64+
type TxOrDB interface {
65+
sqlx.ExtContext
66+
67+
PrepareNamedContext(ctx context.Context, query string) (*sqlx.NamedStmt, error)
68+
}

database/db.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -713,3 +713,9 @@ func (db *DB) Log(ctx context.Context, query string, counter *com.Counter) perio
713713
db.logger.Debugf("Finished executing %q with %d rows in %s", query, counter.Total(), tick.Elapsed)
714714
}))
715715
}
716+
717+
var (
718+
// Assert txOrDB interface compliance of the DB and sqlx.Tx types.
719+
_ TxOrDB = (*DB)(nil)
720+
_ TxOrDB = (*sqlx.Tx)(nil)
721+
)

database/utils.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/icinga/icinga-go-library/com"
88
"github.com/icinga/icinga-go-library/strcase"
99
"github.com/icinga/icinga-go-library/types"
10+
"github.com/jmoiron/sqlx"
1011
"github.com/pkg/errors"
1112
)
1213

@@ -43,6 +44,47 @@ func SplitOnDupId[T IDer]() com.BulkChunkSplitPolicy[T] {
4344
}
4445
}
4546

47+
// InsertObtainID executes the given query and fetches the last inserted ID.
48+
//
49+
// Using this method for database tables that don't define an auto-incrementing ID, or none at all,
50+
// will not work. The only supported column that can be retrieved with this method is id.
51+
// This function expects [TxOrDB] as an executor of the provided query, and is usually a *[sqlx.Tx] or *[DB] instance.
52+
// Returns the retrieved ID on success and error on any database inserting/retrieving failure.
53+
func InsertObtainID(ctx context.Context, conn TxOrDB, stmt string, arg any) (int64, error) {
54+
var resultID int64
55+
switch conn.DriverName() {
56+
case PostgreSQL:
57+
query := stmt + " RETURNING id"
58+
ps, err := conn.PrepareNamedContext(ctx, query)
59+
if err != nil {
60+
return 0, errors.Wrapf(err, "cannot prepare %q", query)
61+
}
62+
// We're deferring the ps#Close invocation here just to be on the safe side, otherwise it's
63+
// closed manually later on and the error is handled gracefully (if any).
64+
defer func() { _ = ps.Close() }()
65+
66+
if err := ps.GetContext(ctx, &resultID, arg); err != nil {
67+
return 0, CantPerformQuery(err, query)
68+
}
69+
70+
if err := ps.Close(); err != nil {
71+
return 0, errors.Wrapf(err, "cannot close prepared statement %q", query)
72+
}
73+
default:
74+
result, err := sqlx.NamedExecContext(ctx, conn, stmt, arg)
75+
if err != nil {
76+
return 0, CantPerformQuery(err, stmt)
77+
}
78+
79+
resultID, err = result.LastInsertId()
80+
if err != nil {
81+
return 0, errors.Wrap(err, "cannot retrieve last inserted ID")
82+
}
83+
}
84+
85+
return resultID, nil
86+
}
87+
4688
// setGaleraOpts sets the "wsrep_sync_wait" variable for each session ensures that causality checks are performed
4789
// before execution and that each statement is executed on a fully synchronized node. Doing so prevents foreign key
4890
// violation when inserting into dependent tables on different MariaDB/MySQL nodes. When using MySQL single nodes,

0 commit comments

Comments
 (0)