It's borpin' time! (#6982)

This change replaces [gorp] with [borp].

The changes consist of a mass renaming of the import and comments / doc
fixups, plus modifications of many call sites to provide a
context.Context everywhere, since gorp newly requires this (this was one
of the motivating factors for the borp fork).

This also refactors `github.com/letsencrypt/boulder/db.WrappedMap` and
`github.com/letsencrypt/boulder/db.Transaction` to not embed their
underlying gorp/borp objects, but to have them as plain fields. This
ensures that we can only call methods on them that are specifically
implemented in `github.com/letsencrypt/boulder/db`, so we don't miss
wrapping any. This required introducing a `NewWrappedMap` method along
with accessors `SQLDb()` and `BorpDB()` to get at the internal fields
during metrics and logging setup.

Fixes #6944
This commit is contained in:
Jacob Hoffman-Andrews 2023-07-17 14:38:29 -07:00 committed by GitHub
parent 0981768793
commit 7d66d67054
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
70 changed files with 1207 additions and 1837 deletions

View File

@ -167,7 +167,7 @@ func (r *revoker) revokeCertificate(ctx context.Context, certObj core.Certificat
}
func (r *revoker) revokeBySerial(ctx context.Context, serial string, reasonCode revocation.Reason, skipBlockKey bool) error {
certObj, err := sa.SelectPrecertificate(r.dbMap, serial)
certObj, err := sa.SelectPrecertificate(ctx, r.dbMap, serial)
if err != nil {
if db.IsNoRows(err) {
return berrors.NotFoundError("precertificate with serial %q not found", serial)
@ -224,7 +224,7 @@ func (r *revoker) revokeSerialBatchFile(ctx context.Context, serialPath string,
// Finding relevant accounts will be very slow because it does not use an index.
func (r *revoker) clearEmailAddress(ctx context.Context, email string) error {
r.log.AuditInfof("Scanning database for accounts with email addresses matching %q in order to clear the email addresses.", email)
regIDs, err := r.getRegIDsMatchingEmail(email)
regIDs, err := r.getRegIDsMatchingEmail(ctx, email)
if err != nil {
return err
}
@ -233,7 +233,7 @@ func (r *revoker) clearEmailAddress(ctx context.Context, email string) error {
failures := 0
for _, regID := range regIDs {
err := sa.ClearEmail(r.dbMap, ctx, regID, email)
err := sa.ClearEmail(ctx, r.dbMap, regID, email)
if err != nil {
// Log, but don't fail, because it took a long time to find the relevant registration IDs
// and we don't want to have to redo that work.
@ -297,7 +297,7 @@ func (r *revoker) revokeByReg(ctx context.Context, regID int64, reasonCode revoc
return fmt.Errorf("couldn't fetch registration: %w", err)
}
certObjs, err := sa.SelectPrecertificates(r.dbMap, "WHERE registrationID = :regID", map[string]interface{}{"regID": regID})
certObjs, err := sa.SelectPrecertificates(ctx, r.dbMap, "WHERE registrationID = :regID", map[string]interface{}{"regID": regID})
if err != nil {
return err
}
@ -372,7 +372,7 @@ func (r *revoker) revokeByPrivateKey(ctx context.Context, privateKey string) err
return err
}
matches, err := r.getCertsMatchingSPKIHash(spkiHash)
matches, err := r.getCertsMatchingSPKIHash(ctx, spkiHash)
if err != nil {
return err
}
@ -408,9 +408,9 @@ func (r *revoker) revokeByPrivateKey(ctx context.Context, privateKey string) err
return nil
}
func (r *revoker) spkiHashInBlockedKeys(spkiHash []byte) (bool, error) {
func (r *revoker) spkiHashInBlockedKeys(ctx context.Context, spkiHash []byte) (bool, error) {
var count int
err := r.dbMap.SelectOne(&count, "SELECT COUNT(*) as count FROM blockedKeys WHERE keyHash = ?", spkiHash)
err := r.dbMap.SelectOne(ctx, &count, "SELECT COUNT(*) as count FROM blockedKeys WHERE keyHash = ?", spkiHash)
if err != nil {
return false, err
}
@ -421,9 +421,9 @@ func (r *revoker) spkiHashInBlockedKeys(spkiHash []byte) (bool, error) {
return false, nil
}
func (r *revoker) countCertsMatchingSPKIHash(spkiHash []byte) (int, error) {
func (r *revoker) countCertsMatchingSPKIHash(ctx context.Context, spkiHash []byte) (int, error) {
var count int
err := r.dbMap.SelectOne(&count, "SELECT COUNT(*) as count FROM keyHashToSerial WHERE keyHash = ?", spkiHash)
err := r.dbMap.SelectOne(ctx, &count, "SELECT COUNT(*) as count FROM keyHashToSerial WHERE keyHash = ?", spkiHash)
if err != nil {
return 0, err
}
@ -434,11 +434,11 @@ func (r *revoker) countCertsMatchingSPKIHash(spkiHash []byte) (int, error) {
// contains the given email address. Since this uses a substring match, it is important
// to subsequently parse the JSON list of addresses and look for exact matches.
// Note: Since this does not use an index, it is very slow.
func (r *revoker) getRegIDsMatchingEmail(email string) ([]int64, error) {
func (r *revoker) getRegIDsMatchingEmail(ctx context.Context, email string) ([]int64, error) {
// We use SQL `CONCAT` rather than interpolating with `+` or `%s` because we want to
// use a `?` placeholder for the email, which prevents SQL injection.
var regIDs []int64
_, err := r.dbMap.Select(&regIDs, "SELECT id FROM registrations WHERE contact LIKE CONCAT('%\"mailto:', ?, '\"%')", email)
_, err := r.dbMap.Select(ctx, &regIDs, "SELECT id FROM registrations WHERE contact LIKE CONCAT('%\"mailto:', ?, '\"%')", email)
if err != nil {
return nil, err
}
@ -447,9 +447,9 @@ func (r *revoker) getRegIDsMatchingEmail(email string) ([]int64, error) {
// TODO(#5899) Use an non-wrapped sql.Db client to iterate over results and
// return them on a channel.
func (r *revoker) getCertsMatchingSPKIHash(spkiHash []byte) ([]string, error) {
func (r *revoker) getCertsMatchingSPKIHash(ctx context.Context, spkiHash []byte) ([]string, error) {
var h []string
_, err := r.dbMap.Select(&h, "SELECT certSerial FROM keyHashToSerial WHERE keyHash = ?", spkiHash)
_, err := r.dbMap.Select(ctx, &h, "SELECT certSerial FROM keyHashToSerial WHERE keyHash = ?", spkiHash)
if err != nil {
if db.IsNoRows(err) {
return nil, berrors.NotFoundError("no certificates with a matching SPKI hash were found")
@ -466,8 +466,8 @@ func (rc revocationCodes) Len() int { return len(rc) }
func (rc revocationCodes) Less(i, j int) bool { return rc[i] < rc[j] }
func (rc revocationCodes) Swap(i, j int) { rc[i], rc[j] = rc[j], rc[i] }
func privateKeyBlock(r *revoker, dryRun bool, comment string, count int, spkiHash []byte, keyPath string) error {
keyExists, err := r.spkiHashInBlockedKeys(spkiHash)
func privateKeyBlock(ctx context.Context, r *revoker, dryRun bool, comment string, count int, spkiHash []byte, keyPath string) error {
keyExists, err := r.spkiHashInBlockedKeys(ctx, spkiHash)
if err != nil {
return fmt.Errorf("while checking if the provided key already exists in the 'blockedKeys' table: %s", err)
}
@ -640,12 +640,12 @@ func main() {
spkiHash, err := getPublicKeySPKIHash(publicKey)
cmd.FailOnError(err, "While obtaining the SPKI hash for the provided key")
count, err := r.countCertsMatchingSPKIHash(spkiHash)
count, err := r.countCertsMatchingSPKIHash(ctx, spkiHash)
cmd.FailOnError(err, "While retrieving a count of certificates matching the provided key")
r.log.AuditInfof("Found %d certificates matching the provided key", count)
if command == "private-key-block" {
err := privateKeyBlock(r, *dryRun, *comment, count, spkiHash, keyPath)
err := privateKeyBlock(ctx, r, *dryRun, *comment, count, spkiHash, keyPath)
cmd.FailOnError(err, "")
}

View File

@ -93,7 +93,10 @@ func TestRevokeIncidentTableSerials(t *testing.T) {
test.Assert(t, len(testCtx.log.GetAllMatching("No serials found in incident table")) > 0, "Expected log output not found")
testCtx.log.Clear()
_, err = testIncidentsDbMap.Exec(
ctx := context.Background()
_, err = testIncidentsDbMap.ExecContext(
ctx,
fmt.Sprintf("INSERT INTO incident_foo (%s) VALUES ('%s', %d, %d, '%s')",
"serial, registrationID, orderID, lastNoticeSent",
core.SerialToString(entries[0].serial),
@ -104,14 +107,14 @@ func TestRevokeIncidentTableSerials(t *testing.T) {
)
test.AssertNotError(t, err, "while inserting row into incident table")
err = testCtx.revoker.revokeIncidentTableSerials(context.Background(), "incident_foo", 0, 1)
err = testCtx.revoker.revokeIncidentTableSerials(ctx, "incident_foo", 0, 1)
test.AssertNotError(t, err, "revokeIncidentTableSerials failed")
// Ensure that a populated incident table results in the expected log output.
test.AssertNotError(t, err, "revokeIncidentTableSerials failed")
test.Assert(t, len(testCtx.log.GetAllMatching("No serials found in incident table")) <= 0, "Expected log output not found")
status, err := testCtx.ssa.GetCertificateStatus(context.Background(), &sapb.Serial{Serial: core.SerialToString(entries[0].serial)})
status, err := testCtx.ssa.GetCertificateStatus(ctx, &sapb.Serial{Serial: core.SerialToString(entries[0].serial)})
test.AssertNotError(t, err, "failed to retrieve certificate status")
test.AssertEquals(t, core.OCSPStatus(status.Status), core.OCSPStatusRevoked)
}
@ -143,9 +146,11 @@ func TestBlockAndRevokeByPrivateKey(t *testing.T) {
spkiHash, err := getPublicKeySPKIHash(&duplicateEntry.testKey.PublicKey)
test.AssertNotError(t, err, "Failed to get SPKI hash for dupe.")
ctx := context.Background()
// Ensure that the SPKI hash hasn't already been added to the blockedKeys
// table.
keyExists, err := testCtx.revoker.spkiHashInBlockedKeys(spkiHash)
keyExists, err := testCtx.revoker.spkiHashInBlockedKeys(ctx, spkiHash)
test.AssertNotError(t, err, "countCertsMatchingSPKIHash for dupe failed")
test.Assert(t, !keyExists, "SPKI hash should not be in blockedKeys")
@ -155,19 +160,19 @@ func TestBlockAndRevokeByPrivateKey(t *testing.T) {
switch e.names[0] {
case uniqueEntries[0].names[0]:
// example-1337.com
count, err := testCtx.revoker.countCertsMatchingSPKIHash(e.spkiHash)
count, err := testCtx.revoker.countCertsMatchingSPKIHash(ctx, e.spkiHash)
test.AssertNotError(t, err, "countCertsMatchingSPKIHash for entry failed")
test.AssertEquals(t, count, 2)
case uniqueEntries[1].names[0]:
// example-1338.com
count, err := testCtx.revoker.countCertsMatchingSPKIHash(e.spkiHash)
count, err := testCtx.revoker.countCertsMatchingSPKIHash(ctx, e.spkiHash)
test.AssertNotError(t, err, "countCertsMatchingSPKIHash for entry failed")
test.AssertEquals(t, count, 1)
case uniqueEntries[2].names[0]:
// example-1339.com
count, err := testCtx.revoker.countCertsMatchingSPKIHash(e.spkiHash)
count, err := testCtx.revoker.countCertsMatchingSPKIHash(ctx, e.spkiHash)
test.AssertNotError(t, err, "countCertsMatchingSPKIHash for entry failed")
test.AssertEquals(t, count, 1)
}
@ -184,7 +189,7 @@ func TestBlockAndRevokeByPrivateKey(t *testing.T) {
test.AssertNotError(t, err, "While attempting to revoke certificates for the provided key")
// Ensure that the key is not blocked, yet.
keyExists, err = testCtx.revoker.spkiHashInBlockedKeys(spkiHash)
keyExists, err = testCtx.revoker.spkiHashInBlockedKeys(ctx, spkiHash)
test.AssertNotError(t, err, "countCertsMatchingSPKIHash for dupe failed")
test.Assert(t, !keyExists, "SPKI hash should not be in blockedKeys")
@ -193,7 +198,7 @@ func TestBlockAndRevokeByPrivateKey(t *testing.T) {
test.AssertNotError(t, err, "While attempting to block issuance for the provided key")
// Ensure that the key is now blocked.
keyExists, err = testCtx.revoker.spkiHashInBlockedKeys(spkiHash)
keyExists, err = testCtx.revoker.spkiHashInBlockedKeys(ctx, spkiHash)
test.AssertNotError(t, err, "countCertsMatchingSPKIHash for dupe failed")
test.Assert(t, keyExists, "SPKI hash should not be in blockedKeys")
@ -203,6 +208,7 @@ func TestBlockAndRevokeByPrivateKey(t *testing.T) {
}
func TestPrivateKeyBlock(t *testing.T) {
ctx := context.Background()
testCtx := setup(t)
defer testCtx.cleanUp()
@ -231,35 +237,35 @@ func TestPrivateKeyBlock(t *testing.T) {
// Query the 'keyHashToSerial' table for certificates with a matching SPKI
// hash. We expect that since this key was re-used we'll find 2 matches.
count, err := testCtx.revoker.countCertsMatchingSPKIHash(duplicateKeySPKI)
count, err := testCtx.revoker.countCertsMatchingSPKIHash(ctx, duplicateKeySPKI)
test.AssertNotError(t, err, "countCertsMatchingSPKIHash for dupe failed")
test.AssertEquals(t, count, 2)
// With dryRun=true this should not block the key.
err = privateKeyBlock(&testCtx.revoker, true, "", count, duplicateKeySPKI, duplicateKeyFile.Name())
err = privateKeyBlock(ctx, &testCtx.revoker, true, "", count, duplicateKeySPKI, duplicateKeyFile.Name())
test.AssertNotError(t, err, "While attempting to block issuance for the provided key")
// Ensure that the key is not blocked, yet.
keyExists, err := testCtx.revoker.spkiHashInBlockedKeys(duplicateKeySPKI)
keyExists, err := testCtx.revoker.spkiHashInBlockedKeys(ctx, duplicateKeySPKI)
test.AssertNotError(t, err, "countCertsMatchingSPKIHash for dupe failed")
test.Assert(t, !keyExists, "SPKI hash should not be in blockedKeys")
// With dryRun=false this should block the key.
comment := "key blocked as part of test"
err = privateKeyBlock(&testCtx.revoker, false, comment, count, duplicateKeySPKI, duplicateKeyFile.Name())
err = privateKeyBlock(ctx, &testCtx.revoker, false, comment, count, duplicateKeySPKI, duplicateKeyFile.Name())
test.AssertNotError(t, err, "While attempting to block issuance for the provided key")
// With dryRun=false this should result in an error as the key is already blocked.
err = privateKeyBlock(&testCtx.revoker, false, "", count, duplicateKeySPKI, duplicateKeyFile.Name())
err = privateKeyBlock(ctx, &testCtx.revoker, false, "", count, duplicateKeySPKI, duplicateKeyFile.Name())
test.AssertError(t, err, "Attempting to block a key which is already blocked should have failed.")
// Ensure that the key is now blocked.
keyExists, err = testCtx.revoker.spkiHashInBlockedKeys(duplicateKeySPKI)
keyExists, err = testCtx.revoker.spkiHashInBlockedKeys(ctx, duplicateKeySPKI)
test.AssertNotError(t, err, "countCertsMatchingSPKIHash for dupe failed")
test.Assert(t, keyExists, "SPKI hash should not be in blockedKeys")
// Ensure that the comment was set as expected
commentFromDB, err := testCtx.dbMap.SelectStr("SELECT comment from blockedKeys WHERE keyHash = ?", duplicateKeySPKI)
commentFromDB, err := testCtx.dbMap.SelectStr(ctx, "SELECT comment from blockedKeys WHERE keyHash = ?", duplicateKeySPKI)
test.AssertNotError(t, err, "Failed to get comment from database")
u, err := user.Current()
test.AssertNotError(t, err, "Failed to get current user")
@ -268,6 +274,7 @@ func TestPrivateKeyBlock(t *testing.T) {
}
func TestPrivateKeyRevoke(t *testing.T) {
ctx := context.Background()
testCtx := setup(t)
defer testCtx.cleanUp()
@ -296,7 +303,7 @@ func TestPrivateKeyRevoke(t *testing.T) {
// Query the 'keyHashToSerial' table for certificates with a matching SPKI
// hash. We expect that since this key was re-used we'll find 2 matches.
count, err := testCtx.revoker.countCertsMatchingSPKIHash(duplicateKeySPKI)
count, err := testCtx.revoker.countCertsMatchingSPKIHash(ctx, duplicateKeySPKI)
test.AssertNotError(t, err, "countCertsMatchingSPKIHash for dupe failed")
test.AssertEquals(t, count, 2)
@ -305,7 +312,7 @@ func TestPrivateKeyRevoke(t *testing.T) {
test.AssertNotError(t, err, "While attempting to block issuance for the provided key")
// Ensure that the key is not blocked, yet.
keyExists, err := testCtx.revoker.spkiHashInBlockedKeys(duplicateKeySPKI)
keyExists, err := testCtx.revoker.spkiHashInBlockedKeys(ctx, duplicateKeySPKI)
test.AssertNotError(t, err, "spkiHashInBlockedKeys failed for key that shouldn't be blocked yet")
test.Assert(t, !keyExists, "SPKI hash should not be in blockedKeys")
@ -315,12 +322,12 @@ func TestPrivateKeyRevoke(t *testing.T) {
test.AssertNotError(t, err, "While attempting to block issuance for the provided key")
// Ensure that the key is now blocked.
keyExists, err = testCtx.revoker.spkiHashInBlockedKeys(duplicateKeySPKI)
keyExists, err = testCtx.revoker.spkiHashInBlockedKeys(ctx, duplicateKeySPKI)
test.AssertNotError(t, err, "spkiHashInBlockedKeys failed for key that should now be blocked")
test.Assert(t, keyExists, "SPKI hash should not be in blockedKeys")
// Ensure that the comment was set as expected
commentFromDB, err := testCtx.dbMap.SelectStr("SELECT comment from blockedKeys WHERE keyHash = ?", duplicateKeySPKI)
commentFromDB, err := testCtx.dbMap.SelectStr(ctx, "SELECT comment from blockedKeys WHERE keyHash = ?", duplicateKeySPKI)
test.AssertNotError(t, err, "Failed to get comment from database")
u, err := user.Current()
test.AssertNotError(t, err, "Failed to get current user")

View File

@ -82,9 +82,10 @@ func (ubk uncheckedBlockedKey) String() string {
ubk.RevokedBy, ubk.KeyHash)
}
func (bkr *badKeyRevoker) countUncheckedKeys() (int, error) {
func (bkr *badKeyRevoker) countUncheckedKeys(ctx context.Context) (int, error) {
var count int
err := bkr.dbMap.SelectOne(
ctx,
&count,
`SELECT COUNT(*)
FROM (SELECT 1 FROM blockedKeys
@ -95,9 +96,10 @@ func (bkr *badKeyRevoker) countUncheckedKeys() (int, error) {
return count, err
}
func (bkr *badKeyRevoker) selectUncheckedKey() (uncheckedBlockedKey, error) {
func (bkr *badKeyRevoker) selectUncheckedKey(ctx context.Context) (uncheckedBlockedKey, error) {
var row uncheckedBlockedKey
err := bkr.dbMap.SelectOne(
ctx,
&row,
`SELECT keyHash, revokedBy
FROM blockedKeys
@ -125,7 +127,7 @@ func (uc unrevokedCertificate) String() string {
// findUnrevoked looks for all unexpired, currently valid certificates which have a specific SPKI hash,
// by looking first at the keyHashToSerial table and then the certificateStatus and certificates tables.
// If the number of certificates it finds is larger than bkr.maxRevocations it'll error out.
func (bkr *badKeyRevoker) findUnrevoked(unchecked uncheckedBlockedKey) ([]unrevokedCertificate, error) {
func (bkr *badKeyRevoker) findUnrevoked(ctx context.Context, unchecked uncheckedBlockedKey) ([]unrevokedCertificate, error) {
var unrevokedCerts []unrevokedCertificate
initialID := 0
for {
@ -134,6 +136,7 @@ func (bkr *badKeyRevoker) findUnrevoked(unchecked uncheckedBlockedKey) ([]unrevo
CertSerial string
}
_, err := bkr.dbMap.Select(
ctx,
&batch,
"SELECT id, certSerial FROM keyHashToSerial WHERE keyHash = ? AND id > ? AND certNotAfter > ? ORDER BY id LIMIT ?",
unchecked.KeyHash,
@ -155,6 +158,7 @@ func (bkr *badKeyRevoker) findUnrevoked(unchecked uncheckedBlockedKey) ([]unrevo
// possible we could get multiple results for a single serial number, but they
// would be duplicates.
err = bkr.dbMap.SelectOne(
ctx,
&unrevokedCert,
`SELECT cs.id, cs.serial, c.registrationID, c.der, cs.status, cs.isExpired
FROM certificateStatus AS cs
@ -181,19 +185,19 @@ func (bkr *badKeyRevoker) findUnrevoked(unchecked uncheckedBlockedKey) ([]unrevo
// markRowChecked updates a row in the blockedKeys table to mark a keyHash
// as having been checked for extant unrevoked certificates.
func (bkr *badKeyRevoker) markRowChecked(unchecked uncheckedBlockedKey) error {
_, err := bkr.dbMap.Exec("UPDATE blockedKeys SET extantCertificatesChecked = true WHERE keyHash = ?", unchecked.KeyHash)
func (bkr *badKeyRevoker) markRowChecked(ctx context.Context, unchecked uncheckedBlockedKey) error {
_, err := bkr.dbMap.ExecContext(ctx, "UPDATE blockedKeys SET extantCertificatesChecked = true WHERE keyHash = ?", unchecked.KeyHash)
return err
}
// resolveContacts builds a map of id -> email addresses
func (bkr *badKeyRevoker) resolveContacts(ids []int64) (map[int64][]string, error) {
func (bkr *badKeyRevoker) resolveContacts(ctx context.Context, ids []int64) (map[int64][]string, error) {
idToEmail := map[int64][]string{}
for _, id := range ids {
var emails struct {
Contact []string
}
err := bkr.dbMap.SelectOne(&emails, "SELECT contact FROM registrations WHERE id = ?", id)
err := bkr.dbMap.SelectOne(ctx, &emails, "SELECT contact FROM registrations WHERE id = ?", id)
if err != nil {
// ErrNoRows is not acceptable here since there should always be a
// row for the registration, even if there are no contacts
@ -289,9 +293,9 @@ func (bkr *badKeyRevoker) revokeCerts(revokerEmails []string, emailToCerts map[s
// invoke processes a single key in the blockedKeys table and returns whether
// there were any rows to process or not.
func (bkr *badKeyRevoker) invoke() (bool, error) {
func (bkr *badKeyRevoker) invoke(ctx context.Context) (bool, error) {
// Gather a count of rows to be processed.
uncheckedCount, err := bkr.countUncheckedKeys()
uncheckedCount, err := bkr.countUncheckedKeys(ctx)
if err != nil {
return false, err
}
@ -307,7 +311,7 @@ func (bkr *badKeyRevoker) invoke() (bool, error) {
}
// select a row to process
unchecked, err := bkr.selectUncheckedKey()
unchecked, err := bkr.selectUncheckedKey(ctx)
if err != nil {
if db.IsNoRows(err) {
return true, nil
@ -317,7 +321,7 @@ func (bkr *badKeyRevoker) invoke() (bool, error) {
bkr.logger.AuditInfo(fmt.Sprintf("found unchecked block key to work on: %s", unchecked))
// select all unrevoked, unexpired serials associated with the blocked key hash
unrevokedCerts, err := bkr.findUnrevoked(unchecked)
unrevokedCerts, err := bkr.findUnrevoked(ctx, unchecked)
if err != nil {
bkr.logger.AuditInfo(fmt.Sprintf("finding unrevoked certificates related to %s: %s",
unchecked, err))
@ -326,7 +330,7 @@ func (bkr *badKeyRevoker) invoke() (bool, error) {
if len(unrevokedCerts) == 0 {
bkr.logger.AuditInfo(fmt.Sprintf("found no certificates that need revoking related to %s, marking row as checked", unchecked))
// mark row as checked
err = bkr.markRowChecked(unchecked)
err = bkr.markRowChecked(ctx, unchecked)
if err != nil {
return false, err
}
@ -352,7 +356,7 @@ func (bkr *badKeyRevoker) invoke() (bool, error) {
ids = append(ids, unchecked.RevokedBy)
}
// get contact addresses for the list of IDs
idToEmails, err := bkr.resolveContacts(ids)
idToEmails, err := bkr.resolveContacts(ctx, ids)
if err != nil {
return false, err
}
@ -377,7 +381,7 @@ func (bkr *badKeyRevoker) invoke() (bool, error) {
}
// mark the key as checked
err = bkr.markRowChecked(unchecked)
err = bkr.markRowChecked(ctx, unchecked)
if err != nil {
return false, err
}
@ -523,7 +527,7 @@ func main() {
// Run bad-key-revoker in a loop. Backoff if no work or errors.
for {
noWork, err := bkr.invoke()
noWork, err := bkr.invoke(context.Background())
if err != nil {
keysProcessed.WithLabelValues("error").Inc()
logger.AuditErrf("failed to process blockedKeys row: %s", err)

View File

@ -34,7 +34,7 @@ func randHash(t *testing.T) []byte {
func insertBlockedRow(t *testing.T, dbMap *db.WrappedMap, fc clock.Clock, hash []byte, by int64, checked bool) {
t.Helper()
_, err := dbMap.Exec(`INSERT INTO blockedKeys
_, err := dbMap.ExecContext(context.Background(), `INSERT INTO blockedKeys
(keyHash, added, source, revokedBy, extantCertificatesChecked)
VALUES
(?, ?, ?, ?, ?)`,
@ -48,6 +48,8 @@ func insertBlockedRow(t *testing.T, dbMap *db.WrappedMap, fc clock.Clock, hash [
}
func TestSelectUncheckedRows(t *testing.T) {
ctx := context.Background()
dbMap, err := sa.DBMapForTest(vars.DBConnSAFullPerms)
test.AssertNotError(t, err, "failed setting up db client")
defer test.ResetBoulderTestDatabase(t)()
@ -62,18 +64,18 @@ func TestSelectUncheckedRows(t *testing.T) {
hashA, hashB, hashC := randHash(t), randHash(t), randHash(t)
insertBlockedRow(t, dbMap, fc, hashA, 1, true)
count, err := bkr.countUncheckedKeys()
count, err := bkr.countUncheckedKeys(ctx)
test.AssertNotError(t, err, "countUncheckedKeys failed")
test.AssertEquals(t, count, 0)
_, err = bkr.selectUncheckedKey()
_, err = bkr.selectUncheckedKey(ctx)
test.AssertError(t, err, "selectUncheckedKey didn't fail with no rows to process")
test.Assert(t, db.IsNoRows(err), "returned error is not sql.ErrNoRows")
insertBlockedRow(t, dbMap, fc, hashB, 1, false)
insertBlockedRow(t, dbMap, fc, hashC, 1, false)
count, err = bkr.countUncheckedKeys()
count, err = bkr.countUncheckedKeys(ctx)
test.AssertNotError(t, err, "countUncheckedKeys failed")
test.AssertEquals(t, count, 2)
row, err := bkr.selectUncheckedKey()
row, err := bkr.selectUncheckedKey(ctx)
test.AssertNotError(t, err, "selectUncheckKey failed")
test.AssertByteEquals(t, row.KeyHash, hashB)
test.AssertEquals(t, row.RevokedBy, int64(1))
@ -92,7 +94,8 @@ func insertRegistration(t *testing.T, dbMap *db.WrappedMap, fc clock.Clock, addr
}
contactStr = fmt.Sprintf("[%s]", strings.Join(contacts, ","))
}
res, err := dbMap.Exec(
res, err := dbMap.ExecContext(
context.Background(),
"INSERT INTO registrations (jwk, jwk_sha256, contact, agreement, initialIP, createdAt, status, LockCol) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
[]byte{},
fmt.Sprintf("%x", jwkHash),
@ -124,13 +127,15 @@ func insertGoodCert(t *testing.T, dbMap *db.WrappedMap, fc clock.Clock, keyHash
func insertCert(t *testing.T, dbMap *db.WrappedMap, fc clock.Clock, keyHash []byte, serial string, regID int64, expiredStatus ExpiredStatus, status core.OCSPStatus) {
t.Helper()
ctx := context.Background()
expiresOffset := 0 * time.Second
if !expiredStatus {
expiresOffset = 90*24*time.Hour - 1*time.Second // 90 days exclusive
}
_, err := dbMap.Exec(
_, err := dbMap.ExecContext(
ctx,
`INSERT IGNORE INTO keyHashToSerial
(keyHash, certNotAfter, certSerial) VALUES
(?, ?, ?)`,
@ -140,7 +145,8 @@ func insertCert(t *testing.T, dbMap *db.WrappedMap, fc clock.Clock, keyHash []by
)
test.AssertNotError(t, err, "failed to insert test keyHashToSerial row")
_, err = dbMap.Exec(
_, err = dbMap.ExecContext(
ctx,
"INSERT INTO certificateStatus (serial, status, isExpired, ocspLastUpdated, revokedDate, revokedReason, lastExpirationNagSent) VALUES (?, ?, ?, ?, ?, ?, ?)",
serial,
status,
@ -152,7 +158,8 @@ func insertCert(t *testing.T, dbMap *db.WrappedMap, fc clock.Clock, keyHash []by
)
test.AssertNotError(t, err, "failed to insert test certificateStatus row")
_, err = dbMap.Exec(
_, err = dbMap.ExecContext(
ctx,
"INSERT INTO precertificates (serial, registrationID, der, issued, expires) VALUES (?, ?, ?, ?, ?)",
serial,
regID,
@ -162,7 +169,8 @@ func insertCert(t *testing.T, dbMap *db.WrappedMap, fc clock.Clock, keyHash []by
)
test.AssertNotError(t, err, "failed to insert test certificateStatus row")
_, err = dbMap.Exec(
_, err = dbMap.ExecContext(
ctx,
"INSERT INTO certificates (serial, registrationID, der, digest, issued, expires) VALUES (?, ?, ?, ?, ?, ?)",
serial,
regID,
@ -178,6 +186,8 @@ func insertCert(t *testing.T, dbMap *db.WrappedMap, fc clock.Clock, keyHash []by
// does not have a corresponding entry in the certificateStatus and
// precertificates table.
func TestFindUnrevokedNoRows(t *testing.T) {
ctx := context.Background()
dbMap, err := sa.DBMapForTest(vars.DBConnSAFullPerms)
test.AssertNotError(t, err, "failed setting up db client")
defer test.ResetBoulderTestDatabase(t)()
@ -185,7 +195,8 @@ func TestFindUnrevokedNoRows(t *testing.T) {
fc := clock.NewFake()
hashA := randHash(t)
_, err = dbMap.Exec(
_, err = dbMap.ExecContext(
ctx,
"INSERT INTO keyHashToSerial (keyHash, certNotAfter, certSerial) VALUES (?, ?, ?)",
hashA,
fc.Now().Add(90*24*time.Hour-1*time.Second), // 90 days exclusive
@ -194,11 +205,13 @@ func TestFindUnrevokedNoRows(t *testing.T) {
test.AssertNotError(t, err, "failed to insert test keyHashToSerial row")
bkr := &badKeyRevoker{dbMap: dbMap, serialBatchSize: 1, maxRevocations: 10, clk: fc}
_, err = bkr.findUnrevoked(uncheckedBlockedKey{KeyHash: hashA})
_, err = bkr.findUnrevoked(ctx, uncheckedBlockedKey{KeyHash: hashA})
test.Assert(t, db.IsNoRows(err), "expected NoRows error")
}
func TestFindUnrevoked(t *testing.T) {
ctx := context.Background()
dbMap, err := sa.DBMapForTest(vars.DBConnSAFullPerms)
test.AssertNotError(t, err, "failed setting up db client")
defer test.ResetBoulderTestDatabase(t)()
@ -219,7 +232,7 @@ func TestFindUnrevoked(t *testing.T) {
// insert revoked
insertCert(t, dbMap, fc, hashA, "dd", regID, Unexpired, Revoked)
rows, err := bkr.findUnrevoked(uncheckedBlockedKey{KeyHash: hashA})
rows, err := bkr.findUnrevoked(ctx, uncheckedBlockedKey{KeyHash: hashA})
test.AssertNotError(t, err, "findUnrevoked failed")
test.AssertEquals(t, len(rows), 1)
test.AssertEquals(t, rows[0].Serial, "ff")
@ -227,7 +240,7 @@ func TestFindUnrevoked(t *testing.T) {
test.AssertByteEquals(t, rows[0].DER, []byte{1, 2, 3})
bkr.maxRevocations = 0
_, err = bkr.findUnrevoked(uncheckedBlockedKey{KeyHash: hashA})
_, err = bkr.findUnrevoked(ctx, uncheckedBlockedKey{KeyHash: hashA})
test.AssertError(t, err, "findUnrevoked didn't fail with 0 maxRevocations")
test.AssertEquals(t, err.Error(), fmt.Sprintf("too many certificates to revoke associated with %x: got 1, max 0", hashA))
}
@ -246,7 +259,7 @@ func TestResolveContacts(t *testing.T) {
regIDC := insertRegistration(t, dbMap, fc, "example.com")
regIDD := insertRegistration(t, dbMap, fc, "example-2.com")
idToEmail, err := bkr.resolveContacts([]int64{regIDA, regIDB, regIDC, regIDD})
idToEmail, err := bkr.resolveContacts(context.Background(), []int64{regIDA, regIDB, regIDC, regIDD})
test.AssertNotError(t, err, "resolveContacts failed")
test.AssertDeepEquals(t, idToEmail, map[int64][]string{
regIDA: {""},
@ -308,6 +321,8 @@ func TestRevokeCerts(t *testing.T) {
}
func TestCertificateAbsent(t *testing.T) {
ctx := context.Background()
dbMap, err := sa.DBMapForTest(vars.DBConnSAFullPerms)
test.AssertNotError(t, err, "failed setting up db client")
defer test.ResetBoulderTestDatabase(t)()
@ -321,7 +336,8 @@ func TestCertificateAbsent(t *testing.T) {
// Add an entry to keyHashToSerial but not to certificateStatus or certificate
// status, and expect an error.
_, err = dbMap.Exec(
_, err = dbMap.ExecContext(
ctx,
"INSERT INTO keyHashToSerial (keyHash, certNotAfter, certSerial) VALUES (?, ?, ?)",
hashA,
fc.Now().Add(90*24*time.Hour-1*time.Second), // 90 days exclusive
@ -340,11 +356,13 @@ func TestCertificateAbsent(t *testing.T) {
logger: blog.NewMock(),
clk: fc,
}
_, err = bkr.invoke()
_, err = bkr.invoke(ctx)
test.AssertError(t, err, "expected error when row in keyHashToSerial didn't have a matching cert")
}
func TestInvoke(t *testing.T) {
ctx := context.Background()
dbMap, err := sa.DBMapForTest(vars.DBConnSAFullPerms)
test.AssertNotError(t, err, "failed setting up db client")
defer test.ResetBoulderTestDatabase(t)()
@ -377,7 +395,7 @@ func TestInvoke(t *testing.T) {
insertGoodCert(t, dbMap, fc, hashA, "dd", regIDC)
insertGoodCert(t, dbMap, fc, hashA, "cc", regIDD)
noWork, err := bkr.invoke()
noWork, err := bkr.invoke(ctx)
test.AssertNotError(t, err, "invoke failed")
test.AssertEquals(t, noWork, false)
test.AssertEquals(t, mr.revoked, 4)
@ -388,7 +406,7 @@ func TestInvoke(t *testing.T) {
var checked struct {
ExtantCertificatesChecked bool
}
err = dbMap.SelectOne(&checked, "SELECT extantCertificatesChecked FROM blockedKeys WHERE keyHash = ?", hashA)
err = dbMap.SelectOne(ctx, &checked, "SELECT extantCertificatesChecked FROM blockedKeys WHERE keyHash = ?", hashA)
test.AssertNotError(t, err, "failed to select row from blockedKeys")
test.AssertEquals(t, checked.ExtantCertificatesChecked, true)
@ -397,16 +415,16 @@ func TestInvoke(t *testing.T) {
insertBlockedRow(t, dbMap, fc, hashB, regIDC, false)
insertCert(t, dbMap, fc, hashB, "bb", regIDA, Expired, Revoked)
noWork, err = bkr.invoke()
noWork, err = bkr.invoke(ctx)
test.AssertNotError(t, err, "invoke failed")
test.AssertEquals(t, noWork, false)
checked.ExtantCertificatesChecked = false
err = dbMap.SelectOne(&checked, "SELECT extantCertificatesChecked FROM blockedKeys WHERE keyHash = ?", hashB)
err = dbMap.SelectOne(ctx, &checked, "SELECT extantCertificatesChecked FROM blockedKeys WHERE keyHash = ?", hashB)
test.AssertNotError(t, err, "failed to select row from blockedKeys")
test.AssertEquals(t, checked.ExtantCertificatesChecked, true)
noWork, err = bkr.invoke()
noWork, err = bkr.invoke(ctx)
test.AssertNotError(t, err, "invoke failed")
test.AssertEquals(t, noWork, true)
}
@ -450,7 +468,7 @@ func TestInvokeRevokerHasNoExtantCerts(t *testing.T) {
insertGoodCert(t, dbMap, fc, hashA, "cc", regIDC)
insertGoodCert(t, dbMap, fc, hashA, "bb", regIDC)
noWork, err := bkr.invoke()
noWork, err := bkr.invoke(context.Background())
test.AssertNotError(t, err, "invoke failed")
test.AssertEquals(t, noWork, false)
test.AssertEquals(t, mr.revoked, 4)

View File

@ -82,12 +82,12 @@ type reportEntry struct {
Problems []string `json:"problems,omitempty"`
}
// certDB is an interface collecting the gorp.saDbMap functions that the various
// certDB is an interface collecting the borp.DbMap functions that the various
// parts of cert-checker rely on. Using this adapter shim allows tests to swap
// out the saDbMap implementation.
type certDB interface {
Select(i interface{}, query string, args ...interface{}) ([]interface{}, error)
SelectNullInt(query string, args ...interface{}) (sql.NullInt64, error)
Select(ctx context.Context, i interface{}, query string, args ...interface{}) ([]interface{}, error)
SelectNullInt(ctx context.Context, query string, args ...interface{}) (sql.NullInt64, error)
}
type certChecker struct {
@ -125,7 +125,7 @@ func newChecker(saDbMap certDB,
}
}
func (c *certChecker) getCerts(unexpiredOnly bool) error {
func (c *certChecker) getCerts(ctx context.Context, unexpiredOnly bool) error {
c.issuedReport.end = c.clock.Now()
c.issuedReport.begin = c.issuedReport.end.Add(-c.checkPeriod)
@ -139,6 +139,7 @@ func (c *certChecker) getCerts(unexpiredOnly bool) error {
var retries int
for {
sni, err = c.dbMap.SelectNullInt(
ctx,
"SELECT MIN(id) FROM certificates WHERE issued >= :issued AND expires >= :now",
args,
)
@ -170,6 +171,7 @@ func (c *certChecker) getCerts(unexpiredOnly bool) error {
for {
certs, err := sa.SelectCertificates(
ctx,
c.dbMap,
"WHERE id > :id AND issued >= :issued AND expires >= :now ORDER BY id LIMIT :limit",
args,
@ -199,9 +201,9 @@ func (c *certChecker) getCerts(unexpiredOnly bool) error {
return nil
}
func (c *certChecker) processCerts(wg *sync.WaitGroup, badResultsOnly bool, ignoredLints map[string]bool) {
func (c *certChecker) processCerts(ctx context.Context, wg *sync.WaitGroup, badResultsOnly bool, ignoredLints map[string]bool) {
for cert := range c.certs {
dnsNames, problems := c.checkCert(cert, ignoredLints)
dnsNames, problems := c.checkCert(ctx, cert, ignoredLints)
valid := len(problems) == 0
c.rMu.Lock()
if !badResultsOnly || (badResultsOnly && !valid) {
@ -245,8 +247,8 @@ var expectedExtensionContent = map[string][]byte{
// likely valid at the time the certificate was issued. Authorizations with
// status = "deactivated" are counted for this, so long as their validatedAt
// is before the issuance and expiration is after.
func (c *certChecker) checkValidations(cert core.Certificate, dnsNames []string) error {
authzs, err := sa.SelectAuthzsMatchingIssuance(c.dbMap, cert.RegistrationID, cert.Issued, dnsNames)
func (c *certChecker) checkValidations(ctx context.Context, cert core.Certificate, dnsNames []string) error {
authzs, err := sa.SelectAuthzsMatchingIssuance(ctx, c.dbMap, cert.RegistrationID, cert.Issued, dnsNames)
if err != nil {
return fmt.Errorf("error checking authzs for certificate %s: %w", cert.Serial, err)
}
@ -277,7 +279,7 @@ func (c *certChecker) checkValidations(cert core.Certificate, dnsNames []string)
}
// checkCert returns a list of DNS names in the certificate and a list of problems with the certificate.
func (c *certChecker) checkCert(cert core.Certificate, ignoredLints map[string]bool) ([]string, []string) {
func (c *certChecker) checkCert(ctx context.Context, cert core.Certificate, ignoredLints map[string]bool) ([]string, []string) {
var dnsNames []string
var problems []string
@ -386,13 +388,13 @@ func (c *certChecker) checkCert(cert core.Certificate, ignoredLints map[string]b
if err != nil {
problems = append(problems, fmt.Sprintf("Couldn't parse stored certificate: %s", err))
}
err = c.kp.GoodKey(context.Background(), p.PublicKey)
err = c.kp.GoodKey(ctx, p.PublicKey)
if err != nil {
problems = append(problems, fmt.Sprintf("Key Policy isn't willing to issue for public key: %s", err))
}
if features.Enabled(features.CertCheckerChecksValidations) {
err = c.checkValidations(cert, parsedCert.DNSNames)
err = c.checkValidations(ctx, cert, parsedCert.DNSNames)
if err != nil {
if features.Enabled(features.CertCheckerRequiresValidations) {
problems = append(problems, err.Error())
@ -531,7 +533,7 @@ func main() {
// is finished it will close the certificate channel which allows the range
// loops in checker.processCerts to break
go func() {
err := checker.getCerts(config.CertChecker.UnexpiredOnly)
err := checker.getCerts(context.TODO(), config.CertChecker.UnexpiredOnly)
cmd.FailOnError(err, "Batch retrieval of certificates failed")
}()
@ -541,7 +543,7 @@ func main() {
wg.Add(1)
go func() {
s := checker.clock.Now()
checker.processCerts(wg, config.CertChecker.BadResultsOnly, ignoredLintsMap)
checker.processCerts(context.TODO(), wg, config.CertChecker.BadResultsOnly, ignoredLintsMap)
checkerLatency.Observe(checker.clock.Since(s).Seconds())
}()
}

View File

@ -86,7 +86,7 @@ func BenchmarkCheckCert(b *testing.B) {
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
checker.checkCert(cert, nil)
checker.checkCert(context.Background(), cert, nil)
}
}
@ -130,7 +130,7 @@ func TestCheckWildcardCert(t *testing.T) {
Issued: parsed.NotBefore,
DER: wildcardCertDer,
}
_, problems := checker.checkCert(cert, nil)
_, problems := checker.checkCert(context.Background(), cert, nil)
for _, p := range problems {
t.Errorf(p)
}
@ -163,7 +163,7 @@ func TestCheckCertReturnsDNSNames(t *testing.T) {
DER: block.Bytes,
}
names, problems := checker.checkCert(cert, nil)
names, problems := checker.checkCert(context.Background(), cert, nil)
if !reflect.DeepEqual(names, []string{"quite_invalid.com", "al--so--wr--ong.com"}) {
t.Errorf("didn't get expected DNS names. other problems: %s", strings.Join(problems, "\n"))
}
@ -268,7 +268,7 @@ func TestCheckCert(t *testing.T) {
Expires: goodExpiry.AddDate(0, 0, 2), // Expiration doesn't match
}
_, problems := checker.checkCert(cert, nil)
_, problems := checker.checkCert(context.Background(), cert, nil)
problemsMap := map[string]int{
"Stored digest doesn't match certificate digest": 1,
@ -294,7 +294,7 @@ func TestCheckCert(t *testing.T) {
// Same settings as above, but the stored serial number in the DB is invalid.
cert.Serial = "not valid"
_, problems = checker.checkCert(cert, nil)
_, problems = checker.checkCert(context.Background(), cert, nil)
foundInvalidSerialProblem := false
for _, p := range problems {
if p == "Stored serial is invalid" {
@ -319,7 +319,7 @@ func TestCheckCert(t *testing.T) {
cert.DER = goodCertDer
cert.Expires = parsed.NotAfter
cert.Issued = parsed.NotBefore
_, problems = checker.checkCert(cert, nil)
_, problems = checker.checkCert(context.Background(), cert, nil)
test.AssertEquals(t, len(problems), 0)
})
}
@ -365,12 +365,12 @@ func TestGetAndProcessCerts(t *testing.T) {
}
batchSize = 2
err = checker.getCerts(false)
err = checker.getCerts(context.Background(), false)
test.AssertNotError(t, err, "Failed to retrieve certificates")
test.AssertEquals(t, len(checker.certs), 5)
wg := new(sync.WaitGroup)
wg.Add(1)
checker.processCerts(wg, false, nil)
checker.processCerts(context.Background(), wg, false, nil)
test.AssertEquals(t, checker.issuedReport.BadCerts, int64(5))
test.AssertEquals(t, len(checker.issuedReport.Entries), 5)
}
@ -383,7 +383,7 @@ type mismatchedCountDB struct{}
// `getCerts` calls `SelectInt` first to determine how many rows there are
// matching the `getCertsCountQuery` criteria. For this mock we return
// a non-zero number
func (db mismatchedCountDB) SelectNullInt(_ string, _ ...interface{}) (sql.NullInt64, error) {
func (db mismatchedCountDB) SelectNullInt(_ context.Context, _ string, _ ...interface{}) (sql.NullInt64, error) {
return sql.NullInt64{
Int64: 99999,
Valid: true,
@ -393,7 +393,7 @@ func (db mismatchedCountDB) SelectNullInt(_ string, _ ...interface{}) (sql.NullI
// `getCerts` then calls `Select` to retrieve the Certificate rows. We pull
// a dastardly switch-a-roo here and return an empty set
func (db mismatchedCountDB) Select(output interface{}, _ string, _ ...interface{}) ([]interface{}, error) {
func (db mismatchedCountDB) Select(_ context.Context, output interface{}, _ string, _ ...interface{}) ([]interface{}, error) {
// But actually return nothing
outputPtr, _ := output.(*[]sa.CertWithID)
*outputPtr = []sa.CertWithID{}
@ -425,7 +425,7 @@ func TestGetCertsEmptyResults(t *testing.T) {
checker.dbMap = mismatchedCountDB{}
batchSize = 3
err = checker.getCerts(false)
err = checker.getCerts(context.Background(), false)
test.AssertNotError(t, err, "Failed to retrieve certificates")
}
@ -437,7 +437,7 @@ type emptyDB struct {
// SelectNullInt is a method that returns a false sql.NullInt64 struct to
// mock a null DB response
func (db emptyDB) SelectNullInt(_ string, _ ...interface{}) (sql.NullInt64, error) {
func (db emptyDB) SelectNullInt(_ context.Context, _ string, _ ...interface{}) (sql.NullInt64, error) {
return sql.NullInt64{Valid: false},
nil
}
@ -452,7 +452,7 @@ func TestGetCertsNullResults(t *testing.T) {
checker := newChecker(saDbMap, clock.NewFake(), pa, kp, time.Hour, testValidityDurations, blog.NewMock())
checker.dbMap = emptyDB{}
err = checker.getCerts(false)
err = checker.getCerts(context.Background(), false)
test.AssertError(t, err, "Should have gotten error from empty DB")
}
@ -592,13 +592,13 @@ func TestIgnoredLint(t *testing.T) {
// Check the certificate with a nil ignore map. This should return the
// expected zlint problems.
_, problems := checker.checkCert(cert, nil)
_, problems := checker.checkCert(context.Background(), cert, nil)
sort.Strings(problems)
test.AssertDeepEquals(t, problems, expectedProblems)
// Check the certificate again with an ignore map that excludes the affected
// lints. This should return no problems.
_, problems = checker.checkCert(cert, map[string]bool{
_, problems = checker.checkCert(context.Background(), cert, map[string]bool{
"e_sub_cert_aia_does_not_contain_ocsp_url": true,
"n_subject_common_name_included": true,
"w_ct_sct_policy_count_unsatisfied": true,

View File

@ -1,6 +1,7 @@
package notmain
import (
"context"
"database/sql"
"encoding/json"
"errors"
@ -68,8 +69,8 @@ func validateContacts(id int64, createdAt string, contacts []string) error {
// beginAuditQuery executes the audit query and returns a cursor used to
// stream the results.
func (c contactAuditor) beginAuditQuery() (*sql.Rows, error) {
rows, err := c.db.Query(`
func (c contactAuditor) beginAuditQuery(ctx context.Context) (*sql.Rows, error) {
rows, err := c.db.QueryContext(ctx, `
SELECT DISTINCT id, contact, createdAt
FROM registrations
WHERE contact NOT IN ('[]', 'null');`)
@ -98,9 +99,9 @@ func (c contactAuditor) writeResults(result string) {
// run retrieves a cursor from `beginAuditQuery` and then audits the
// `contact` column of all returned rows for abnormalities or policy
// violations.
func (c contactAuditor) run(resChan chan *result) error {
func (c contactAuditor) run(ctx context.Context, resChan chan *result) error {
c.logger.Infof("Beginning database query")
rows, err := c.beginAuditQuery()
rows, err := c.beginAuditQuery(ctx)
if err != nil {
return err
}
@ -193,7 +194,7 @@ func main() {
logger.Info("Running contact-auditor")
err = auditor.run(nil)
err = auditor.run(context.TODO(), nil)
cmd.FailOnError(err, "Audit was interrupted, results may be incomplete")
logger.Info("Audit finished successfully")

View File

@ -41,7 +41,7 @@ func TestContactAuditor(t *testing.T) {
testCtx.addRegistrations(t)
resChan := make(chan *result, 10)
err := testCtx.c.run(resChan)
err := testCtx.c.run(context.Background(), resChan)
test.AssertNotError(t, err, "received error")
// We should get back A, B, C, and D

View File

@ -296,7 +296,7 @@ func (m *mailer) updateLastNagTimestampsChunk(ctx context.Context, certs []*x509
)
params[0] = m.clk.Now()
_, err := m.dbMap.WithContext(ctx).Exec(query, params...)
_, err := m.dbMap.ExecContext(ctx, query, params...)
if err != nil {
m.log.AuditErrf("Error updating certificate status for %d certs: %s", len(certs), err)
m.stats.errorCount.With(prometheus.Labels{"type": "UpdateCertificateStatus"}).Inc()
@ -307,7 +307,8 @@ func (m *mailer) certIsRenewed(ctx context.Context, names []string, issued time.
namehash := sa.HashNames(names)
var present bool
err := m.dbMap.WithContext(ctx).SelectOne(
err := m.dbMap.SelectOne(
ctx,
&present,
`SELECT EXISTS (SELECT id FROM fqdnSets WHERE setHash = ? AND issued > ? LIMIT 1)`,
namehash,
@ -530,7 +531,8 @@ func (m *mailer) getCertsWithJoin(ctx context.Context, left, right time.Time, ex
// sequentially fetch the certificate details. This avoids an expensive
// JOIN.
var certs []certDERWithRegID
_, err := m.dbMap.WithContext(ctx).Select(
_, err := m.dbMap.Select(
ctx,
&certs,
`SELECT
cert.der as der, cert.registrationID as regID
@ -564,7 +566,8 @@ func (m *mailer) getCerts(ctx context.Context, left, right time.Time, expiresIn
// sequentially fetch the certificate details. This avoids an expensive
// JOIN.
var serials []string
_, err := m.dbMap.WithContext(ctx).Select(
_, err := m.dbMap.Select(
ctx,
&serials,
`SELECT
cs.serial
@ -596,7 +599,7 @@ func (m *mailer) getCerts(ctx context.Context, left, right time.Time, expiresIn
return nil, ctx.Err()
}
var cert core.Certificate
cert, err := sa.SelectCertificate(m.dbMap.WithContext(ctx), serial)
cert, err := sa.SelectCertificate(ctx, m.dbMap, serial)
if err != nil {
// We can get a NoRowsErr when processing a serial number corresponding
// to a precertificate with no final certificate. Since this certificate

View File

@ -329,6 +329,8 @@ func TestNoContactCertIsNotRenewed(t *testing.T) {
// An account with no contact info has a certificate that is expiring but has been renewed.
// We should only examine that certificate once.
func TestNoContactCertIsRenewed(t *testing.T) {
ctx := context.Background()
testCtx := setup(t, []time.Duration{time.Hour * 24 * 7})
reg, err := makeRegistration(testCtx.ssa, 1, jsonKeyA, []string{})
@ -350,7 +352,7 @@ func TestNoContactCertIsRenewed(t *testing.T) {
setupDBMap, err := sa.DBMapForTest(vars.DBConnSAFullPerms)
test.AssertNotError(t, err, "setting up DB")
err = setupDBMap.Insert(&core.FQDNSet{
err = setupDBMap.Insert(ctx, &core.FQDNSet{
SetHash: sa.HashNames(names),
Serial: core.SerialToString(serial2),
Issued: testCtx.fc.Now().Add(time.Hour),
@ -358,7 +360,7 @@ func TestNoContactCertIsRenewed(t *testing.T) {
})
test.AssertNotError(t, err, "inserting FQDNSet for renewal")
err = testCtx.m.findExpiringCertificates(context.Background())
err = testCtx.m.findExpiringCertificates(ctx)
test.AssertNotError(t, err, "finding expired certificates")
// We should have examined exactly one certificate
@ -370,7 +372,7 @@ func TestNoContactCertIsRenewed(t *testing.T) {
// Run findExpiringCertificates again. The count of examined certificates
// should not increase again.
err = testCtx.m.findExpiringCertificates(context.Background())
err = testCtx.m.findExpiringCertificates(ctx)
test.AssertNotError(t, err, "finding expired certificates")
test.AssertMetricWithLabelsEquals(t, certsExamined, prometheus.Labels{}, 1.0)
test.AssertMetricWithLabelsEquals(t, certsAlreadyRenewed, prometheus.Labels{}, 1.0)
@ -498,6 +500,8 @@ func makeCertificate(regID int64, serial *big.Int, dnsNames []string, expires ti
}
func insertCertificate(cert certDERWithRegID, lastNagSent time.Time) error {
ctx := context.Background()
parsedCert, err := x509.ParseCertificate(cert.DER)
if err != nil {
return err
@ -507,7 +511,7 @@ func insertCertificate(cert certDERWithRegID, lastNagSent time.Time) error {
if err != nil {
return err
}
err = setupDBMap.Insert(&core.Certificate{
err = setupDBMap.Insert(ctx, &core.Certificate{
RegistrationID: cert.RegID,
Serial: core.SerialToString(parsedCert.SerialNumber),
Issued: parsedCert.NotBefore,
@ -518,7 +522,7 @@ func insertCertificate(cert certDERWithRegID, lastNagSent time.Time) error {
return fmt.Errorf("inserting certificate: %w", err)
}
return setupDBMap.Insert(&core.CertificateStatus{
return setupDBMap.Insert(ctx, &core.CertificateStatus{
Serial: core.SerialToString(parsedCert.SerialNumber),
LastExpirationNagSent: lastNagSent,
Status: core.OCSPStatusGood,
@ -599,9 +603,9 @@ func addExpiringCerts(t *testing.T, ctx *testCtx) []certDERWithRegID {
setupDBMap, err := sa.DBMapForTest(vars.DBConnSAFullPerms)
test.AssertNotError(t, err, "setting up DB")
err = setupDBMap.Insert(fqdnStatusD)
err = setupDBMap.Insert(context.Background(), fqdnStatusD)
test.AssertNotError(t, err, "Couldn't add fqdnStatusD")
err = setupDBMap.Insert(fqdnStatusDRenewed)
err = setupDBMap.Insert(context.Background(), fqdnStatusDRenewed)
test.AssertNotError(t, err, "Couldn't add fqdnStatusDRenewed")
return []certDERWithRegID{certA, certB, certC, certD}
}
@ -752,7 +756,7 @@ func TestCertIsRenewed(t *testing.T) {
err = insertCertificate(certDERWithRegID{DER: certDer, RegID: reg.Id}, time.Time{})
test.AssertNotError(t, err, fmt.Sprintf("Couldn't add cert %s", testData.stringSerial))
err = setupDBMap.Insert(fqdnStatus)
err = setupDBMap.Insert(context.Background(), fqdnStatus)
test.AssertNotError(t, err, fmt.Sprintf("Couldn't add fqdnStatus %s", testData.stringSerial))
}
@ -858,13 +862,15 @@ func TestDontFindRevokedCert(t *testing.T) {
err = insertCertificate(certA, time.Time{})
test.AssertNotError(t, err, "inserting certificate")
ctx := context.Background()
setupDBMap, err := sa.DBMapForTest(vars.DBConnSAFullPerms)
test.AssertNotError(t, err, "sa.NewDbMap failed")
_, err = setupDBMap.Exec("UPDATE certificateStatus SET status = ? WHERE serial = ?",
_, err = setupDBMap.ExecContext(ctx, "UPDATE certificateStatus SET status = ? WHERE serial = ?",
string(core.OCSPStatusRevoked), core.SerialToString(serial1))
test.AssertNotError(t, err, "revoking certificate")
err = testCtx.m.findExpiringCertificates(context.Background())
err = testCtx.m.findExpiringCertificates(ctx)
test.AssertNotError(t, err, "err from findExpiringCertificates")
if len(testCtx.mc.Messages) != 0 {

View File

@ -2,6 +2,7 @@ package notmain
import (
"bufio"
"context"
"encoding/json"
"errors"
"flag"
@ -68,9 +69,10 @@ func (i *idExporterResults) writeToFile(outfile string) error {
}
// findIDs gathers all registration IDs with unexpired certificates.
func (c idExporter) findIDs() (idExporterResults, error) {
func (c idExporter) findIDs(ctx context.Context) (idExporterResults, error) {
var holder idExporterResults
_, err := c.dbMap.Select(
ctx,
&holder,
`SELECT DISTINCT r.id
FROM registrations AS r
@ -89,9 +91,10 @@ func (c idExporter) findIDs() (idExporterResults, error) {
// findIDsWithExampleHostnames gathers all registration IDs with
// unexpired certificates and a corresponding example hostname.
func (c idExporter) findIDsWithExampleHostnames() (idExporterResults, error) {
func (c idExporter) findIDsWithExampleHostnames(ctx context.Context) (idExporterResults, error) {
var holder idExporterResults
_, err := c.dbMap.Select(
ctx,
&holder,
`SELECT SQL_BIG_RESULT
cert.registrationID AS id,
@ -116,13 +119,14 @@ func (c idExporter) findIDsWithExampleHostnames() (idExporterResults, error) {
// findIDsForHostnames gathers all registration IDs with unexpired
// certificates for each `hostnames` entry.
func (c idExporter) findIDsForHostnames(hostnames []string) (idExporterResults, error) {
func (c idExporter) findIDsForHostnames(ctx context.Context, hostnames []string) (idExporterResults, error) {
var holder idExporterResults
for _, hostname := range hostnames {
// Pass the same list in each time, gorp will happily just append to the slice
// Pass the same list in each time, borp will happily just append to the slice
// instead of overwriting it each time
// https://github.com/go-gorp/gorp/blob/2ae7d174a4cf270240c4561092402affba25da5e/select.go#L348-L355
// https://github.com/letsencrypt/borp/blob/c87bd6443d59746a33aca77db34a60cfc344adb2/select.go#L349-L353
_, err := c.dbMap.Select(
ctx,
&holder,
`SELECT DISTINCT c.registrationID AS id
FROM certificates AS c
@ -279,15 +283,15 @@ func main() {
hostnames, err := unmarshalHostnames(*hostnamesFile)
cmd.FailOnError(err, "Problem unmarshalling hostnames")
results, err = exporter.findIDsForHostnames(hostnames)
results, err = exporter.findIDsForHostnames(context.TODO(), hostnames)
cmd.FailOnError(err, "Could not find IDs for hostnames")
} else if *withExampleHostnames {
results, err = exporter.findIDsWithExampleHostnames()
results, err = exporter.findIDsWithExampleHostnames(context.TODO())
cmd.FailOnError(err, "Could not find IDs with hostnames")
} else {
results, err = exporter.findIDs()
results, err = exporter.findIDs(context.TODO())
cmd.FailOnError(err, "Could not find IDs")
}

View File

@ -41,6 +41,8 @@ const (
)
func TestFindIDs(t *testing.T) {
ctx := context.Background()
testCtx := setup(t)
defer testCtx.cleanUp()
@ -49,7 +51,7 @@ func TestFindIDs(t *testing.T) {
// Run findIDs - since no certificates have been added corresponding to
// the above registrations, no IDs should be found.
results, err := testCtx.c.findIDs()
results, err := testCtx.c.findIDs(ctx)
test.AssertNotError(t, err, "findIDs() produced error")
test.AssertEquals(t, len(results), 0)
@ -61,7 +63,7 @@ func TestFindIDs(t *testing.T) {
// *not* be present since their certificate has already expired. Unlike
// previous versions of this test RegD is not filtered out for having a `tel:`
// contact field anymore - this is the duty of the notify-mailer.
results, err = testCtx.c.findIDs()
results, err = testCtx.c.findIDs(ctx)
test.AssertNotError(t, err, "findIDs() produced error")
test.AssertEquals(t, len(results), 3)
for _, entry := range results {
@ -76,7 +78,7 @@ func TestFindIDs(t *testing.T) {
// Allow a 1 year grace period
testCtx.c.grace = 360 * 24 * time.Hour
results, err = testCtx.c.findIDs()
results, err = testCtx.c.findIDs(ctx)
test.AssertNotError(t, err, "findIDs() produced error")
// Now all four registration should be returned, including RegB since its
// certificate expired within the grace period
@ -93,6 +95,7 @@ func TestFindIDs(t *testing.T) {
}
func TestFindIDsWithExampleHostnames(t *testing.T) {
ctx := context.Background()
testCtx := setup(t)
defer testCtx.cleanUp()
@ -102,7 +105,7 @@ func TestFindIDsWithExampleHostnames(t *testing.T) {
// Run findIDsWithExampleHostnames - since no certificates have been
// added corresponding to the above registrations, no IDs should be
// found.
results, err := testCtx.c.findIDsWithExampleHostnames()
results, err := testCtx.c.findIDsWithExampleHostnames(ctx)
test.AssertNotError(t, err, "findIDs() produced error")
test.AssertEquals(t, len(results), 0)
@ -113,7 +116,7 @@ func TestFindIDsWithExampleHostnames(t *testing.T) {
// registrations with unexpired certs we should get exactly three
// IDs back: RegA, RegC and RegD. RegB should *not* be present since
// their certificate has already expired.
results, err = testCtx.c.findIDsWithExampleHostnames()
results, err = testCtx.c.findIDsWithExampleHostnames(ctx)
test.AssertNotError(t, err, "findIDs() produced error")
test.AssertEquals(t, len(results), 3)
for _, entry := range results {
@ -131,7 +134,7 @@ func TestFindIDsWithExampleHostnames(t *testing.T) {
// Allow a 1 year grace period
testCtx.c.grace = 360 * 24 * time.Hour
results, err = testCtx.c.findIDsWithExampleHostnames()
results, err = testCtx.c.findIDsWithExampleHostnames(ctx)
test.AssertNotError(t, err, "findIDs() produced error")
// Now all four registrations should be returned, including RegB
@ -154,6 +157,8 @@ func TestFindIDsWithExampleHostnames(t *testing.T) {
}
func TestFindIDsForHostnames(t *testing.T) {
ctx := context.Background()
testCtx := setup(t)
defer testCtx.cleanUp()
@ -162,14 +167,14 @@ func TestFindIDsForHostnames(t *testing.T) {
// Run findIDsForHostnames - since no certificates have been added corresponding to
// the above registrations, no IDs should be found.
results, err := testCtx.c.findIDsForHostnames([]string{"example-a.com", "example-b.com", "example-c.com", "example-d.com"})
results, err := testCtx.c.findIDsForHostnames(ctx, []string{"example-a.com", "example-b.com", "example-c.com", "example-d.com"})
test.AssertNotError(t, err, "findIDs() produced error")
test.AssertEquals(t, len(results), 0)
// Now add some certificates
testCtx.addCertificates(t)
results, err = testCtx.c.findIDsForHostnames([]string{"example-a.com", "example-b.com", "example-c.com", "example-d.com"})
results, err = testCtx.c.findIDsForHostnames(ctx, []string{"example-a.com", "example-b.com", "example-c.com", "example-d.com"})
test.AssertNotError(t, err, "findIDsForHostnames() failed")
test.AssertEquals(t, len(results), 3)
for _, entry := range results {
@ -314,6 +319,7 @@ func (tc testCtx) addRegistrations(t *testing.T) {
}
func (tc testCtx) addCertificates(t *testing.T) {
ctx := context.Background()
serial1 := big.NewInt(1336)
serial1String := core.SerialToString(serial1)
serial2 := big.NewInt(1337)
@ -352,9 +358,10 @@ func (tc testCtx) addCertificates(t *testing.T) {
Expires: rawCertA.NotAfter,
DER: certDerA,
}
err := tc.c.dbMap.Insert(certA)
err := tc.c.dbMap.Insert(ctx, certA)
test.AssertNotError(t, err, "Couldn't add certA")
_, err = tc.c.dbMap.Exec(
_, err = tc.c.dbMap.ExecContext(
ctx,
"INSERT INTO issuedNames (reversedName, serial, notBefore) VALUES (?,?,0)",
"com.example-a",
serial1String,
@ -377,9 +384,10 @@ func (tc testCtx) addCertificates(t *testing.T) {
Expires: rawCertB.NotAfter,
DER: certDerB,
}
err = tc.c.dbMap.Insert(certB)
err = tc.c.dbMap.Insert(ctx, certB)
test.AssertNotError(t, err, "Couldn't add certB")
_, err = tc.c.dbMap.Exec(
_, err = tc.c.dbMap.ExecContext(
ctx,
"INSERT INTO issuedNames (reversedName, serial, notBefore) VALUES (?,?,0)",
"com.example-b",
serial2String,
@ -402,9 +410,10 @@ func (tc testCtx) addCertificates(t *testing.T) {
Expires: rawCertC.NotAfter,
DER: certDerC,
}
err = tc.c.dbMap.Insert(certC)
err = tc.c.dbMap.Insert(ctx, certC)
test.AssertNotError(t, err, "Couldn't add certC")
_, err = tc.c.dbMap.Exec(
_, err = tc.c.dbMap.ExecContext(
ctx,
"INSERT INTO issuedNames (reversedName, serial, notBefore) VALUES (?,?,0)",
"com.example-c",
serial3String,
@ -427,9 +436,10 @@ func (tc testCtx) addCertificates(t *testing.T) {
Expires: rawCertD.NotAfter,
DER: certDerD,
}
err = tc.c.dbMap.Insert(certD)
err = tc.c.dbMap.Insert(ctx, certD)
test.AssertNotError(t, err, "Couldn't add certD")
_, err = tc.c.dbMap.Exec(
_, err = tc.c.dbMap.ExecContext(
ctx,
"INSERT INTO issuedNames (reversedName, serial, notBefore) VALUES (?,?,0)",
"com.example-d",
serial4String,

View File

@ -1,6 +1,7 @@
package notmain
import (
"context"
"encoding/csv"
"encoding/json"
"errors"
@ -119,7 +120,7 @@ func (m *mailer) makeMessageBody(recipients []recipient) (string, error) {
return messageBody.String(), nil
}
func (m *mailer) run() error {
func (m *mailer) run(ctx context.Context) error {
err := m.ok()
if err != nil {
return err
@ -128,7 +129,7 @@ func (m *mailer) run() error {
totalRecipients := len(m.recipients)
m.log.Infof("Resolving addresses for (%d) recipients", totalRecipients)
addressToRecipient, err := m.resolveAddresses()
addressToRecipient, err := m.resolveAddresses(ctx)
if err != nil {
return err
}
@ -238,10 +239,10 @@ func (m *mailer) run() error {
// resolveAddresses creates a mapping of email addresses to (a list of)
// `recipient`s that resolve to that email address.
func (m *mailer) resolveAddresses() (addressToRecipientMap, error) {
func (m *mailer) resolveAddresses(ctx context.Context) (addressToRecipientMap, error) {
result := make(addressToRecipientMap, len(m.recipients))
for _, recipient := range m.recipients {
addresses, err := getAddressForID(recipient.id, m.dbMap)
addresses, err := getAddressForID(ctx, recipient.id, m.dbMap)
if err != nil {
return nil, err
}
@ -258,17 +259,17 @@ func (m *mailer) resolveAddresses() (addressToRecipientMap, error) {
return result, nil
}
// dbSelector abstracts over a subset of methods from `gorp.DbMap` objects to
// dbSelector abstracts over a subset of methods from `borp.DbMap` objects to
// facilitate mocking in unit tests.
type dbSelector interface {
SelectOne(holder interface{}, query string, args ...interface{}) error
SelectOne(ctx context.Context, holder interface{}, query string, args ...interface{}) error
}
// getAddressForID queries the database for the email address associated with
// the provided registration ID.
func getAddressForID(id int64, dbMap dbSelector) ([]string, error) {
func getAddressForID(ctx context.Context, id int64, dbMap dbSelector) ([]string, error) {
var result contactQueryResult
err := dbMap.SelectOne(&result,
err := dbMap.SelectOne(ctx, &result,
`SELECT id,
contact
FROM registrations
@ -606,7 +607,7 @@ func main() {
parallelSends: *parallelSends,
}
err = m.run()
err = m.run(context.TODO())
cmd.FailOnError(err, "Couldn't complete")
log.Info("Completed successfully")

View File

@ -1,6 +1,7 @@
package notmain
import (
"context"
"database/sql"
"errors"
"fmt"
@ -294,7 +295,7 @@ func TestSleepInterval(t *testing.T) {
// Call run() - this should sleep `sleepLen` per destination address
// After it returns, we expect (sleepLen * number of destinations) seconds has
// elapsed
err := m.run()
err := m.run(context.Background())
test.AssertNotError(t, err, "error calling mailer run()")
expectedEnd := clock.NewFake()
expectedEnd.Add(time.Second * time.Duration(sleepLen*len(recipients)))
@ -314,7 +315,7 @@ func TestSleepInterval(t *testing.T) {
// Call run() - this should blast through all destinations without sleep
// After it returns, we expect no clock time to have elapsed on the fake clock
err = m.run()
err = m.run(context.Background())
test.AssertNotError(t, err, "error calling mailer run()")
expectedEnd = clock.NewFake()
test.AssertEquals(t, m.clk.Now(), expectedEnd.Now())
@ -345,7 +346,7 @@ func TestMailIntervals(t *testing.T) {
// Run the mailer. It should produce an error about the interval start
mc.Clear()
err := m.run()
err := m.run(context.Background())
test.AssertError(t, err, "expected error")
test.AssertEquals(t, len(mc.Messages), 0)
@ -364,7 +365,7 @@ func TestMailIntervals(t *testing.T) {
// Run the mailer. It should produce an error about the sleep interval
mc.Clear()
err = m.run()
err = m.run(context.Background())
test.AssertEquals(t, len(mc.Messages), 0)
test.AssertEquals(t, err.Error(), "sleep interval (-10) is < 0")
@ -386,7 +387,7 @@ func TestMailIntervals(t *testing.T) {
// test-example-updated@letsencrypt.org (beginning of the range),
// and one to test-test-test@letsencrypt.org.
mc.Clear()
err = m.run()
err = m.run(context.Background())
test.AssertNotError(t, err, "run() produced an error")
test.AssertEquals(t, len(mc.Messages), 2)
test.AssertEquals(t, mocks.MailerMessage{
@ -417,7 +418,7 @@ func TestMailIntervals(t *testing.T) {
// Run the mailer. Two messages should have been produced, one to
// example@letsencrypt.org (ID 1), one to example-example-example@example.com (ID 2)
mc.Clear()
err = m.run()
err = m.run(context.Background())
test.AssertNotError(t, err, "run() produced an error")
test.AssertEquals(t, len(mc.Messages), 2)
test.AssertEquals(t, mocks.MailerMessage{
@ -456,7 +457,7 @@ func TestParallelism(t *testing.T) {
}
mc.Clear()
err := m.run()
err := m.run(context.Background())
test.AssertNotError(t, err, "run() produced an error")
// The fake clock should have advanced 9 seconds, one for each parallel
@ -499,7 +500,7 @@ func TestMessageContentStatic(t *testing.T) {
// Run the mailer, one message should have been created with the content
// expected
err := m.run()
err := m.run(context.Background())
test.AssertNotError(t, err, "error calling mailer run()")
test.AssertEquals(t, len(mc.Messages), 1)
test.AssertEquals(t, mocks.MailerMessage{
@ -536,7 +537,7 @@ func TestMessageContentInterpolated(t *testing.T) {
// Run the mailer, one message should have been created with the content
// expected
err := m.run()
err := m.run(context.Background())
test.AssertNotError(t, err, "error calling mailer run()")
test.AssertEquals(t, len(mc.Messages), 1)
test.AssertEquals(t, mocks.MailerMessage{
@ -594,7 +595,7 @@ func TestMessageContentInterpolatedMultiple(t *testing.T) {
// Run the mailer, one message should have been created with the content
// expected
err := m.run()
err := m.run(context.Background())
test.AssertNotError(t, err, "error calling mailer run()")
test.AssertEquals(t, len(mc.Messages), 1)
test.AssertEquals(t, mocks.MailerMessage{
@ -616,7 +617,7 @@ type mockEmailResolver struct{}
// the `mockEmailResolver` select method treats the requested reg ID as an index
// into a list of anonymous structs
func (bs mockEmailResolver) SelectOne(output interface{}, _ string, args ...interface{}) error {
func (bs mockEmailResolver) SelectOne(ctx context.Context, output interface{}, _ string, args ...interface{}) error {
// The "dbList" is just a list of contact records in memory
dbList := []contactQueryResult{
{
@ -762,7 +763,7 @@ func TestResolveEmails(t *testing.T) {
clk: clock.NewFake(),
}
addressesToRecipients, err := m.resolveAddresses()
addressesToRecipients, err := m.resolveAddresses(context.Background())
test.AssertNotError(t, err, "failed to resolveEmailAddresses")
expected := []string{

View File

@ -44,7 +44,8 @@ func getStartingID(ctx context.Context, clk clock.Clock, db *db.WrappedMap) (int
// certificates.
startTime := clk.Now().Add(-24 * time.Hour)
var minID *int64
err := db.WithContext(ctx).QueryRow(
err := db.QueryRowContext(
ctx,
"SELECT MIN(id) FROM certificateStatus WHERE notAfter >= ?",
startTime,
).Scan(&minID)
@ -70,7 +71,8 @@ func (cl *client) loadFromDB(ctx context.Context, speed ProcessingSpeed, startFr
// Find the current maximum id in certificateStatus. We do this because the table is always
// growing. If we scanned until we saw a batch with no rows, we would scan forever.
var maxID *int64
err = cl.db.WithContext(ctx).QueryRow(
err = cl.db.QueryRowContext(
ctx,
"SELECT MAX(id) FROM certificateStatus",
).Scan(&maxID)
if err != nil {
@ -162,7 +164,7 @@ func (cl *client) scanFromDBOneBatch(ctx context.Context, prevID int64, frequenc
return -1, fmt.Errorf("initializing db map: %w", err)
}
rows, err := selector.Query(ctx, clauses, params...)
rows, err := selector.QueryContext(ctx, clauses, params...)
if err != nil {
return -1, fmt.Errorf("scanning certificateStatus: %w", err)
}

View File

@ -50,6 +50,8 @@ func makeClient() (*rocsp.RWClient, clock.Clock) {
}
func TestGetStartingID(t *testing.T) {
ctx := context.Background()
clk := clock.NewFake()
dbMap, err := sa.DBMapForTest(vars.DBConnSAFullPerms)
test.AssertNotError(t, err, "failed setting up db client")
@ -60,7 +62,7 @@ func TestGetStartingID(t *testing.T) {
Serial: "1337",
NotAfter: clk.Now().Add(12 * time.Hour),
}
err = dbMap.Insert(&cs)
err = dbMap.Insert(ctx, &cs)
test.AssertNotError(t, err, "inserting certificate status")
firstID := cs.ID
@ -68,7 +70,7 @@ func TestGetStartingID(t *testing.T) {
Serial: "1338",
NotAfter: clk.Now().Add(36 * time.Hour),
}
err = dbMap.Insert(&cs)
err = dbMap.Insert(ctx, &cs)
test.AssertNotError(t, err, "inserting certificate status")
secondID := cs.ID
t.Logf("first ID %d, second ID %d", firstID, secondID)
@ -129,7 +131,7 @@ func TestLoadFromDB(t *testing.T) {
defer test.ResetBoulderTestDatabase(t)
for i := 0; i < 100; i++ {
err = dbMap.Insert(&core.CertificateStatus{
err = dbMap.Insert(context.Background(), &core.CertificateStatus{
Serial: fmt.Sprintf("%036x", i),
NotAfter: clk.Now().Add(200 * time.Hour),
OCSPLastUpdated: clk.Now(),

View File

@ -51,7 +51,7 @@ func NewMappedSelector[T any](executor MappedExecutor) (MappedSelector[T], error
// - Note that the reverse is not true: it's perfectly okay for there to be
// database columns which do not correspond to fields in the struct; those
// columns will be ignored.
// TODO: In the future, when we replace gorp's TableMap with our own, this
// TODO: In the future, when we replace borp's TableMap with our own, this
// check should be performed at the time the mapping is declared.
columns := make([]string, 0)
seen := make(map[string]struct{})
@ -80,21 +80,21 @@ type mappedSelector[T any] struct {
columns []string
}
// Query performs a SELECT on the appropriate table for T. It combines the best
// features of gorp, the go stdlib, and generics, using the type parameter of
// QueryContext performs a SELECT on the appropriate table for T. It combines the best
// features of borp, the go stdlib, and generics, using the type parameter of
// the typeSelector object to automatically look up the proper table name and
// columns to select. It returns an iterable which yields fully-populated
// objects of the parameterized type directly. The given clauses MUST be only
// the bits of a sql query from "WHERE ..." onwards; if they contain any of the
// "SELECT ... FROM ..." portion of the query it will result in an error. The
// args take the same kinds of values as gorp's SELECT: either one argument per
// args take the same kinds of values as borp's SELECT: either one argument per
// positional placeholder, or a map of placeholder names to their arguments
// (see https://pkg.go.dev/gopkg.in/gorp.v2#readme-ad-hoc-sql).
// (see https://pkg.go.dev/github.com/letsencrypt/borp#readme-ad-hoc-sql).
//
// The caller is responsible for calling `Rows.Close()` when they are done with
// the query. The caller is also responsible for ensuring that the clauses
// argument does not contain any user-influenced input.
func (ts mappedSelector[T]) Query(ctx context.Context, clauses string, args ...interface{}) (Rows[T], error) {
func (ts mappedSelector[T]) QueryContext(ctx context.Context, clauses string, args ...interface{}) (Rows[T], error) {
// Look up the table to use based on the type of this TypeSelector.
var throwaway T
tableMap, err := ts.wrapped.TableFor(reflect.TypeOf(throwaway), false)
@ -106,7 +106,7 @@ func (ts mappedSelector[T]) Query(ctx context.Context, clauses string, args ...i
}
// QueryFrom is the same as Query, but it additionally takes a table name to
// select from, rather than automatically computing the table name from gorp's
// select from, rather than automatically computing the table name from borp's
// DbMap.
//
// The caller is responsible for calling `Rows.Close()` when they are done with
@ -127,7 +127,7 @@ func (ts mappedSelector[T]) QueryFrom(ctx context.Context, tablename string, cla
clauses,
)
r, err := ts.wrapped.WithContext(ctx).Query(query, args...)
r, err := ts.wrapped.QueryContext(ctx, query, args...)
if err != nil {
return nil, fmt.Errorf("reading db: %w", err)
}

View File

@ -6,7 +6,7 @@ import (
"errors"
"reflect"
"github.com/go-gorp/gorp/v3"
"github.com/letsencrypt/borp"
)
// These interfaces exist to aid in mocking database operations for unit tests.
@ -18,26 +18,26 @@ import (
// A OneSelector is anything that provides a `SelectOne` function.
type OneSelector interface {
SelectOne(interface{}, string, ...interface{}) error
SelectOne(context.Context, interface{}, string, ...interface{}) error
}
// A Selector is anything that provides a `Select` function.
type Selector interface {
Select(interface{}, string, ...interface{}) ([]interface{}, error)
Select(context.Context, interface{}, string, ...interface{}) ([]interface{}, error)
}
// A Inserter is anything that provides an `Insert` function
type Inserter interface {
Insert(list ...interface{}) error
Insert(context.Context, ...interface{}) error
}
// A Execer is anything that provides an `Exec` function
// A Execer is anything that provides an `ExecContext` function
type Execer interface {
Exec(string, ...interface{}) (sql.Result, error)
ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
}
// SelectExecer offers a subset of gorp.SqlExecutor's methods: Select and
// Exec.
// SelectExecer offers a subset of borp.SqlExecutor's methods: Select and
// ExecContext.
type SelectExecer interface {
Selector
Execer
@ -49,48 +49,46 @@ type DatabaseMap interface {
OneSelector
Inserter
SelectExecer
Begin() (Transaction, error)
BeginTx(context.Context) (Transaction, error)
}
// Executor offers the full combination of OneSelector, Inserter, SelectExecer
// and adds a handful of other high level Gorp methods we use in Boulder.
// and adds a handful of other high level borp methods we use in Boulder.
type Executor interface {
OneSelector
Inserter
SelectExecer
Delete(...interface{}) (int64, error)
Get(interface{}, ...interface{}) (interface{}, error)
Update(...interface{}) (int64, error)
Query(string, ...interface{}) (*sql.Rows, error)
Queryer
Delete(context.Context, ...interface{}) (int64, error)
Get(context.Context, interface{}, ...interface{}) (interface{}, error)
Update(context.Context, ...interface{}) (int64, error)
}
// Queryer offers the Query method. Note that this is not read-only (i.e. not
// Selector), since a Query can be `INSERT`, `UPDATE`, etc. The difference
// between Query and Exec is that Query can return rows. So for instance it is
// Queryer offers the QueryContext method. Note that this is not read-only (i.e. not
// Selector), since a QueryContext can be `INSERT`, `UPDATE`, etc. The difference
// between QueryContext and ExecContext is that QueryContext can return rows. So for instance it is
// suitable for inserting rows and getting back ids.
type Queryer interface {
Query(string, ...interface{}) (*sql.Rows, error)
QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
}
// Transaction extends an Executor and adds Rollback, Commit, and WithContext.
// Transaction extends an Executor and adds Rollback and Commit
type Transaction interface {
Executor
Rollback() error
Commit() error
WithContext(ctx context.Context) gorp.SqlExecutor
}
// MappedExecutor is anything that can map types to tables, and which can
// produce a SqlExecutor bound to a context.
// MappedExecutor is anything that can map types to tables
type MappedExecutor interface {
TableFor(reflect.Type, bool) (*gorp.TableMap, error)
WithContext(ctx context.Context) gorp.SqlExecutor
TableFor(reflect.Type, bool) (*borp.TableMap, error)
QueryContext(ctx context.Context, clauses string, args ...interface{}) (*sql.Rows, error)
}
// MappedSelector is anything that can execute various kinds of SQL statements
// against a table automatically determined from the parameterized type.
type MappedSelector[T any] interface {
Query(ctx context.Context, clauses string, args ...interface{}) (Rows[T], error)
QueryContext(ctx context.Context, clauses string, args ...interface{}) (Rows[T], error)
QueryFrom(ctx context.Context, tablename string, clauses string, args ...interface{}) (Rows[T], error)
}
@ -105,8 +103,8 @@ type Rows[T any] interface {
// MockSqlExecuter implement SqlExecutor by returning errors from every call.
//
// To mock out WithContext, we need to be able to return objects that satisfy
// gorp.SqlExecutor. That's a pretty big interface, so we specify one no-op mock
// TODO: To mock out WithContext, we needed to be able to return objects that satisfy
// borp.SqlExecutor. That's a pretty big interface, so we specify one no-op mock
// that we can embed everywhere we need to satisfy it.
// Note: MockSqlExecutor does *not* implement WithContext. The expectation is
// that structs that embed MockSqlExecutor will define their own WithContext
@ -114,48 +112,48 @@ type Rows[T any] interface {
// to override the specific methods they need to implement (e.g. SelectOne).
type MockSqlExecutor struct{}
func (mse MockSqlExecutor) Get(i interface{}, keys ...interface{}) (interface{}, error) {
func (mse MockSqlExecutor) Get(ctx context.Context, i interface{}, keys ...interface{}) (interface{}, error) {
return nil, errors.New("unimplemented")
}
func (mse MockSqlExecutor) Insert(list ...interface{}) error {
func (mse MockSqlExecutor) Insert(ctx context.Context, list ...interface{}) error {
return errors.New("unimplemented")
}
func (mse MockSqlExecutor) Update(list ...interface{}) (int64, error) {
func (mse MockSqlExecutor) Update(ctx context.Context, list ...interface{}) (int64, error) {
return 0, errors.New("unimplemented")
}
func (mse MockSqlExecutor) Delete(list ...interface{}) (int64, error) {
func (mse MockSqlExecutor) Delete(ctx context.Context, list ...interface{}) (int64, error) {
return 0, errors.New("unimplemented")
}
func (mse MockSqlExecutor) Exec(query string, args ...interface{}) (sql.Result, error) {
func (mse MockSqlExecutor) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
return nil, errors.New("unimplemented")
}
func (mse MockSqlExecutor) Select(i interface{}, query string, args ...interface{}) ([]interface{}, error) {
func (mse MockSqlExecutor) Select(ctx context.Context, i interface{}, query string, args ...interface{}) ([]interface{}, error) {
return nil, errors.New("unimplemented")
}
func (mse MockSqlExecutor) SelectInt(query string, args ...interface{}) (int64, error) {
func (mse MockSqlExecutor) SelectInt(ctx context.Context, query string, args ...interface{}) (int64, error) {
return 0, errors.New("unimplemented")
}
func (mse MockSqlExecutor) SelectNullInt(query string, args ...interface{}) (sql.NullInt64, error) {
func (mse MockSqlExecutor) SelectNullInt(ctx context.Context, query string, args ...interface{}) (sql.NullInt64, error) {
return sql.NullInt64{}, errors.New("unimplemented")
}
func (mse MockSqlExecutor) SelectFloat(query string, args ...interface{}) (float64, error) {
func (mse MockSqlExecutor) SelectFloat(ctx context.Context, query string, args ...interface{}) (float64, error) {
return 0, errors.New("unimplemented")
}
func (mse MockSqlExecutor) SelectNullFloat(query string, args ...interface{}) (sql.NullFloat64, error) {
func (mse MockSqlExecutor) SelectNullFloat(ctx context.Context, query string, args ...interface{}) (sql.NullFloat64, error) {
return sql.NullFloat64{}, errors.New("unimplemented")
}
func (mse MockSqlExecutor) SelectStr(query string, args ...interface{}) (string, error) {
func (mse MockSqlExecutor) SelectStr(ctx context.Context, query string, args ...interface{}) (string, error) {
return "", errors.New("unimplemented")
}
func (mse MockSqlExecutor) SelectNullStr(query string, args ...interface{}) (sql.NullString, error) {
func (mse MockSqlExecutor) SelectNullStr(ctx context.Context, query string, args ...interface{}) (sql.NullString, error) {
return sql.NullString{}, errors.New("unimplemented")
}
func (mse MockSqlExecutor) SelectOne(holder interface{}, query string, args ...interface{}) error {
func (mse MockSqlExecutor) SelectOne(ctx context.Context, holder interface{}, query string, args ...interface{}) error {
return errors.New("unimplemented")
}
func (mse MockSqlExecutor) Query(query string, args ...interface{}) (*sql.Rows, error) {
func (mse MockSqlExecutor) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
return nil, errors.New("unimplemented")
}
func (mse MockSqlExecutor) QueryRow(query string, args ...interface{}) *sql.Row {
func (mse MockSqlExecutor) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row {
return nil
}

181
db/map.go
View File

@ -5,10 +5,11 @@ import (
"database/sql"
"errors"
"fmt"
"reflect"
"regexp"
gorp "github.com/go-gorp/gorp/v3"
"github.com/go-sql-driver/mysql"
"github.com/letsencrypt/borp"
)
// ErrDatabaseOp wraps an underlying err with a description of the operation
@ -44,7 +45,7 @@ func (e ErrDatabaseOp) Unwrap() error {
// IsNoRows is a utility function for determining if an error wraps the go sql
// package's ErrNoRows, which is returned when a Scan operation has no more
// results to return, and as such is returned by many gorp methods.
// results to return, and as such is returned by many borp methods.
func IsNoRows(err error) bool {
return errors.Is(err, sql.ErrNoRows)
}
@ -57,50 +58,74 @@ func IsDuplicate(err error) bool {
return errors.As(err, &dbErr) && dbErr.Number == 1062
}
// WrappedMap wraps a *gorp.DbMap such that its major functions wrap error
// WrappedMap wraps a *borp.DbMap such that its major functions wrap error
// results in ErrDatabaseOp instances before returning them to the caller.
type WrappedMap struct {
*gorp.DbMap
dbMap *borp.DbMap
}
func (m *WrappedMap) Get(holder interface{}, keys ...interface{}) (interface{}, error) {
return WrappedExecutor{SqlExecutor: m.DbMap}.Get(holder, keys...)
func NewWrappedMap(dbMap *borp.DbMap) *WrappedMap {
return &WrappedMap{dbMap: dbMap}
}
func (m *WrappedMap) Insert(list ...interface{}) error {
return WrappedExecutor{SqlExecutor: m.DbMap}.Insert(list...)
func (m *WrappedMap) SQLDb() *sql.DB {
return m.dbMap.Db
}
func (m *WrappedMap) Update(list ...interface{}) (int64, error) {
return WrappedExecutor{SqlExecutor: m.DbMap}.Update(list...)
func (m *WrappedMap) BorpDB() *borp.DbMap {
return m.dbMap
}
func (m *WrappedMap) Delete(list ...interface{}) (int64, error) {
return WrappedExecutor{SqlExecutor: m.DbMap}.Delete(list...)
func (m *WrappedMap) TableFor(t reflect.Type, checkPK bool) (*borp.TableMap, error) {
return m.dbMap.TableFor(t, checkPK)
}
func (m *WrappedMap) Select(holder interface{}, query string, args ...interface{}) ([]interface{}, error) {
return WrappedExecutor{SqlExecutor: m.DbMap}.Select(holder, query, args...)
func (m *WrappedMap) Get(ctx context.Context, holder interface{}, keys ...interface{}) (interface{}, error) {
return WrappedExecutor{sqlExecutor: m.dbMap}.Get(ctx, holder, keys...)
}
func (m *WrappedMap) SelectOne(holder interface{}, query string, args ...interface{}) error {
return WrappedExecutor{SqlExecutor: m.DbMap}.SelectOne(holder, query, args...)
func (m *WrappedMap) Insert(ctx context.Context, list ...interface{}) error {
return WrappedExecutor{sqlExecutor: m.dbMap}.Insert(ctx, list...)
}
func (m *WrappedMap) Query(query string, args ...interface{}) (*sql.Rows, error) {
return WrappedExecutor{SqlExecutor: m.DbMap}.Query(query, args...)
func (m *WrappedMap) Update(ctx context.Context, list ...interface{}) (int64, error) {
return WrappedExecutor{sqlExecutor: m.dbMap}.Update(ctx, list...)
}
func (m *WrappedMap) Exec(query string, args ...interface{}) (sql.Result, error) {
return WrappedExecutor{SqlExecutor: m.DbMap}.Exec(query, args...)
func (m *WrappedMap) Delete(ctx context.Context, list ...interface{}) (int64, error) {
return WrappedExecutor{sqlExecutor: m.dbMap}.Delete(ctx, list...)
}
func (m *WrappedMap) WithContext(ctx context.Context) gorp.SqlExecutor {
return WrappedExecutor{SqlExecutor: m.DbMap.WithContext(ctx)}
func (m *WrappedMap) Select(ctx context.Context, holder interface{}, query string, args ...interface{}) ([]interface{}, error) {
return WrappedExecutor{sqlExecutor: m.dbMap}.Select(ctx, holder, query, args...)
}
func (m *WrappedMap) Begin() (Transaction, error) {
tx, err := m.DbMap.Begin()
func (m *WrappedMap) SelectOne(ctx context.Context, holder interface{}, query string, args ...interface{}) error {
return WrappedExecutor{sqlExecutor: m.dbMap}.SelectOne(ctx, holder, query, args...)
}
func (m *WrappedMap) SelectNullInt(ctx context.Context, query string, args ...interface{}) (sql.NullInt64, error) {
return WrappedExecutor{sqlExecutor: m.dbMap}.SelectNullInt(ctx, query, args...)
}
func (m *WrappedMap) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
return WrappedExecutor{sqlExecutor: m.dbMap}.QueryContext(ctx, query, args...)
}
func (m *WrappedMap) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row {
return WrappedExecutor{sqlExecutor: m.dbMap}.QueryRowContext(ctx, query, args...)
}
func (m *WrappedMap) SelectStr(ctx context.Context, query string, args ...interface{}) (string, error) {
return WrappedExecutor{sqlExecutor: m.dbMap}.SelectStr(ctx, query, args...)
}
func (m *WrappedMap) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
return WrappedExecutor{sqlExecutor: m.dbMap}.ExecContext(ctx, query, args...)
}
func (m *WrappedMap) BeginTx(ctx context.Context) (Transaction, error) {
tx, err := m.dbMap.BeginTx(ctx)
if err != nil {
return tx, ErrDatabaseOp{
Op: "begin transaction",
@ -108,66 +133,62 @@ func (m *WrappedMap) Begin() (Transaction, error) {
}
}
return WrappedTransaction{
Transaction: tx,
transaction: tx,
}, err
}
// WrappedTransaction wraps a *gorp.Transaction such that its major functions
// WrappedTransaction wraps a *borp.Transaction such that its major functions
// wrap error results in ErrDatabaseOp instances before returning them to the
// caller.
type WrappedTransaction struct {
*gorp.Transaction
}
func (tx WrappedTransaction) WithContext(ctx context.Context) gorp.SqlExecutor {
return WrappedExecutor{SqlExecutor: tx.Transaction.WithContext(ctx)}
transaction *borp.Transaction
}
func (tx WrappedTransaction) Commit() error {
return tx.Transaction.Commit()
return tx.transaction.Commit()
}
func (tx WrappedTransaction) Rollback() error {
return tx.Transaction.Rollback()
return tx.transaction.Rollback()
}
func (tx WrappedTransaction) Get(holder interface{}, keys ...interface{}) (interface{}, error) {
return (WrappedExecutor{SqlExecutor: tx.Transaction}).Get(holder, keys...)
func (tx WrappedTransaction) Get(ctx context.Context, holder interface{}, keys ...interface{}) (interface{}, error) {
return (WrappedExecutor{sqlExecutor: tx.transaction}).Get(ctx, holder, keys...)
}
func (tx WrappedTransaction) Insert(list ...interface{}) error {
return (WrappedExecutor{SqlExecutor: tx.Transaction}).Insert(list...)
func (tx WrappedTransaction) Insert(ctx context.Context, list ...interface{}) error {
return (WrappedExecutor{sqlExecutor: tx.transaction}).Insert(ctx, list...)
}
func (tx WrappedTransaction) Update(list ...interface{}) (int64, error) {
return (WrappedExecutor{SqlExecutor: tx.Transaction}).Update(list...)
func (tx WrappedTransaction) Update(ctx context.Context, list ...interface{}) (int64, error) {
return (WrappedExecutor{sqlExecutor: tx.transaction}).Update(ctx, list...)
}
func (tx WrappedTransaction) Delete(list ...interface{}) (int64, error) {
return (WrappedExecutor{SqlExecutor: tx.Transaction}).Delete(list...)
func (tx WrappedTransaction) Delete(ctx context.Context, list ...interface{}) (int64, error) {
return (WrappedExecutor{sqlExecutor: tx.transaction}).Delete(ctx, list...)
}
func (tx WrappedTransaction) Select(holder interface{}, query string, args ...interface{}) ([]interface{}, error) {
return (WrappedExecutor{SqlExecutor: tx.Transaction}).Select(holder, query, args...)
func (tx WrappedTransaction) Select(ctx context.Context, holder interface{}, query string, args ...interface{}) ([]interface{}, error) {
return (WrappedExecutor{sqlExecutor: tx.transaction}).Select(ctx, holder, query, args...)
}
func (tx WrappedTransaction) SelectOne(holder interface{}, query string, args ...interface{}) error {
return (WrappedExecutor{SqlExecutor: tx.Transaction}).SelectOne(holder, query, args...)
func (tx WrappedTransaction) SelectOne(ctx context.Context, holder interface{}, query string, args ...interface{}) error {
return (WrappedExecutor{sqlExecutor: tx.transaction}).SelectOne(ctx, holder, query, args...)
}
func (tx WrappedTransaction) Query(query string, args ...interface{}) (*sql.Rows, error) {
return (WrappedExecutor{SqlExecutor: tx.Transaction}).Query(query, args...)
func (tx WrappedTransaction) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
return (WrappedExecutor{sqlExecutor: tx.transaction}).QueryContext(ctx, query, args...)
}
func (tx WrappedTransaction) Exec(query string, args ...interface{}) (sql.Result, error) {
return (WrappedExecutor{SqlExecutor: tx.Transaction}).Exec(query, args...)
func (tx WrappedTransaction) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
return (WrappedExecutor{sqlExecutor: tx.transaction}).ExecContext(ctx, query, args...)
}
// WrappedExecutor wraps a gorp.SqlExecutor such that its major functions
// WrappedExecutor wraps a borp.SqlExecutor such that its major functions
// wrap error results in ErrDatabaseOp instances before returning them to the
// caller.
type WrappedExecutor struct {
gorp.SqlExecutor
sqlExecutor borp.SqlExecutor
}
func errForOp(operation string, err error, list []interface{}) ErrDatabaseOp {
@ -203,56 +224,78 @@ func errForQuery(query, operation string, err error, list []interface{}) ErrData
}
}
func (we WrappedExecutor) Get(holder interface{}, keys ...interface{}) (interface{}, error) {
res, err := we.SqlExecutor.Get(holder, keys...)
func (we WrappedExecutor) Get(ctx context.Context, holder interface{}, keys ...interface{}) (interface{}, error) {
res, err := we.sqlExecutor.Get(ctx, holder, keys...)
if err != nil {
return res, errForOp("get", err, []interface{}{holder})
}
return res, err
}
func (we WrappedExecutor) Insert(list ...interface{}) error {
err := we.SqlExecutor.Insert(list...)
func (we WrappedExecutor) Insert(ctx context.Context, list ...interface{}) error {
err := we.sqlExecutor.Insert(ctx, list...)
if err != nil {
return errForOp("insert", err, list)
}
return nil
}
func (we WrappedExecutor) Update(list ...interface{}) (int64, error) {
updatedRows, err := we.SqlExecutor.Update(list...)
func (we WrappedExecutor) Update(ctx context.Context, list ...interface{}) (int64, error) {
updatedRows, err := we.sqlExecutor.Update(ctx, list...)
if err != nil {
return updatedRows, errForOp("update", err, list)
}
return updatedRows, err
}
func (we WrappedExecutor) Delete(list ...interface{}) (int64, error) {
deletedRows, err := we.SqlExecutor.Delete(list...)
func (we WrappedExecutor) Delete(ctx context.Context, list ...interface{}) (int64, error) {
deletedRows, err := we.sqlExecutor.Delete(ctx, list...)
if err != nil {
return deletedRows, errForOp("delete", err, list)
}
return deletedRows, err
}
func (we WrappedExecutor) Select(holder interface{}, query string, args ...interface{}) ([]interface{}, error) {
result, err := we.SqlExecutor.Select(holder, query, args...)
func (we WrappedExecutor) Select(ctx context.Context, holder interface{}, query string, args ...interface{}) ([]interface{}, error) {
result, err := we.sqlExecutor.Select(ctx, holder, query, args...)
if err != nil {
return result, errForQuery(query, "select", err, []interface{}{holder})
}
return result, err
}
func (we WrappedExecutor) SelectOne(holder interface{}, query string, args ...interface{}) error {
err := we.SqlExecutor.SelectOne(holder, query, args...)
func (we WrappedExecutor) SelectOne(ctx context.Context, holder interface{}, query string, args ...interface{}) error {
err := we.sqlExecutor.SelectOne(ctx, holder, query, args...)
if err != nil {
return errForQuery(query, "select one", err, []interface{}{holder})
}
return nil
}
func (we WrappedExecutor) Query(query string, args ...interface{}) (*sql.Rows, error) {
rows, err := we.SqlExecutor.Query(query, args...)
func (we WrappedExecutor) SelectNullInt(ctx context.Context, query string, args ...interface{}) (sql.NullInt64, error) {
rows, err := we.sqlExecutor.SelectNullInt(ctx, query, args...)
if err != nil {
return sql.NullInt64{}, errForQuery(query, "select", err, nil)
}
return rows, nil
}
func (we WrappedExecutor) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row {
// Note: we can't do error wrapping here because the error is passed via the `*sql.Row`
// object, and we can't produce a `*sql.Row` object with a custom error because it is unexported.
return we.sqlExecutor.QueryRowContext(ctx, query, args...)
}
func (we WrappedExecutor) SelectStr(ctx context.Context, query string, args ...interface{}) (string, error) {
str, err := we.sqlExecutor.SelectStr(ctx, query, args...)
if err != nil {
return "", errForQuery(query, "select", err, nil)
}
return str, nil
}
func (we WrappedExecutor) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
rows, err := we.sqlExecutor.QueryContext(ctx, query, args...)
if err != nil {
return nil, errForQuery(query, "select", err, nil)
}
@ -271,10 +314,10 @@ var (
// tableRegexps is a list of regexps that tableFromQuery will try to use in
// succession to find the table name for an SQL query. While tableFromQuery
// isn't used by the higher level gorp Insert/Update/Select/etc functions we
// isn't used by the higher level borp Insert/Update/Select/etc functions we
// include regexps for matching inserts, updates, selects, etc because we want
// to match the correct table when these types of queries are run through
// Exec().
// ExecContext().
tableRegexps = []*regexp.Regexp{
selectTableRegexp,
insertTableRegexp,
@ -295,8 +338,8 @@ func tableFromQuery(query string) string {
return ""
}
func (we WrappedExecutor) Exec(query string, args ...interface{}) (sql.Result, error) {
res, err := we.SqlExecutor.Exec(query, args...)
func (we WrappedExecutor) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
res, err := we.sqlExecutor.ExecContext(ctx, query, args...)
if err != nil {
return res, errForQuery(query, "exec", err, args)
}

View File

@ -7,7 +7,7 @@ import (
"fmt"
"testing"
gorp "github.com/go-gorp/gorp/v3"
"github.com/letsencrypt/borp"
"github.com/go-sql-driver/mysql"
"github.com/letsencrypt/boulder/core"
@ -236,25 +236,28 @@ func testDbMap(t *testing.T) *WrappedMap {
dbConn, err := sql.Open("mysql", config.FormatDSN())
test.AssertNotError(t, err, "opening DB connection")
dialect := gorp.MySQLDialect{Engine: "InnoDB", Encoding: "UTF8"}
dialect := borp.MySQLDialect{Engine: "InnoDB", Encoding: "UTF8"}
// NOTE(@cpu): We avoid giving a sa.BoulderTypeConverter to the DbMap field to
// avoid the cyclic dep. We don't need to convert any types in the db tests.
dbMap := &gorp.DbMap{Db: dbConn, Dialect: dialect, TypeConverter: nil}
return &WrappedMap{DbMap: dbMap}
dbMap := &borp.DbMap{Db: dbConn, Dialect: dialect, TypeConverter: nil}
return &WrappedMap{dbMap: dbMap}
}
func TestWrappedMap(t *testing.T) {
mustDbErr := func(err error) ErrDatabaseOp {
t.Helper()
var dbOpErr ErrDatabaseOp
test.AssertErrorWraps(t, err, &dbOpErr)
return dbOpErr
}
ctx := context.Background()
testWrapper := func(dbMap Executor) {
reg := &core.Registration{}
// Test wrapped Get
_, err := dbMap.Get(reg)
_, err := dbMap.Get(ctx, reg)
test.AssertError(t, err, "expected err Getting Registration w/o type converter")
dbOpErr := mustDbErr(err)
test.AssertEquals(t, dbOpErr.Op, "get")
@ -262,7 +265,7 @@ func TestWrappedMap(t *testing.T) {
test.AssertError(t, dbOpErr.Err, "expected non-nil underlying err")
// Test wrapped Insert
err = dbMap.Insert(reg)
err = dbMap.Insert(ctx, reg)
test.AssertError(t, err, "expected err Inserting Registration w/o type converter")
dbOpErr = mustDbErr(err)
test.AssertEquals(t, dbOpErr.Op, "insert")
@ -270,7 +273,7 @@ func TestWrappedMap(t *testing.T) {
test.AssertError(t, dbOpErr.Err, "expected non-nil underlying err")
// Test wrapped Update
_, err = dbMap.Update(reg)
_, err = dbMap.Update(ctx, reg)
test.AssertError(t, err, "expected err Updating Registration w/o type converter")
dbOpErr = mustDbErr(err)
test.AssertEquals(t, dbOpErr.Op, "update")
@ -278,7 +281,7 @@ func TestWrappedMap(t *testing.T) {
test.AssertError(t, dbOpErr.Err, "expected non-nil underlying err")
// Test wrapped Delete
_, err = dbMap.Delete(reg)
_, err = dbMap.Delete(ctx, reg)
test.AssertError(t, err, "expected err Deleting Registration w/o type converter")
dbOpErr = mustDbErr(err)
test.AssertEquals(t, dbOpErr.Op, "delete")
@ -286,7 +289,7 @@ func TestWrappedMap(t *testing.T) {
test.AssertError(t, dbOpErr.Err, "expected non-nil underlying err")
// Test wrapped Select with a bogus query
_, err = dbMap.Select(reg, "blah")
_, err = dbMap.Select(ctx, reg, "blah")
test.AssertError(t, err, "expected err Selecting Registration w/o type converter")
dbOpErr = mustDbErr(err)
test.AssertEquals(t, dbOpErr.Op, "select")
@ -294,7 +297,7 @@ func TestWrappedMap(t *testing.T) {
test.AssertError(t, dbOpErr.Err, "expected non-nil underlying err")
// Test wrapped Select with a valid query
_, err = dbMap.Select(reg, "SELECT id, contact FROM registrationzzz WHERE id > 1;")
_, err = dbMap.Select(ctx, reg, "SELECT id, contact FROM registrationzzz WHERE id > 1;")
test.AssertError(t, err, "expected err Selecting Registration w/o type converter")
dbOpErr = mustDbErr(err)
test.AssertEquals(t, dbOpErr.Op, "select")
@ -302,7 +305,7 @@ func TestWrappedMap(t *testing.T) {
test.AssertError(t, dbOpErr.Err, "expected non-nil underlying err")
// Test wrapped SelectOne with a bogus query
err = dbMap.SelectOne(reg, "blah")
err = dbMap.SelectOne(ctx, reg, "blah")
test.AssertError(t, err, "expected err SelectOne-ing Registration w/o type converter")
dbOpErr = mustDbErr(err)
test.AssertEquals(t, dbOpErr.Op, "select one")
@ -310,7 +313,7 @@ func TestWrappedMap(t *testing.T) {
test.AssertError(t, dbOpErr.Err, "expected non-nil underlying err")
// Test wrapped SelectOne with a valid query
err = dbMap.SelectOne(reg, "SELECT contact FROM doesNotExist WHERE id=1;")
err = dbMap.SelectOne(ctx, reg, "SELECT contact FROM doesNotExist WHERE id=1;")
test.AssertError(t, err, "expected err SelectOne-ing Registration w/o type converter")
dbOpErr = mustDbErr(err)
test.AssertEquals(t, dbOpErr.Op, "select one")
@ -318,7 +321,7 @@ func TestWrappedMap(t *testing.T) {
test.AssertError(t, dbOpErr.Err, "expected non-nil underlying err")
// Test wrapped Exec
_, err = dbMap.Exec("INSERT INTO whatever (id) VALUES (?) WHERE id = ?", 10)
_, err = dbMap.ExecContext(ctx, "INSERT INTO whatever (id) VALUES (?) WHERE id = ?", 10)
test.AssertError(t, err, "expected err Exec-ing bad query")
dbOpErr = mustDbErr(err)
test.AssertEquals(t, dbOpErr.Op, "exec")
@ -333,24 +336,10 @@ func TestWrappedMap(t *testing.T) {
// database errors.
testWrapper(dbMap)
// Using WithContext on the WrappedMap should return a map that continues to
// operate in the expected fashion.
dbMapWithCtx := dbMap.WithContext(context.Background())
testWrapper(dbMapWithCtx)
// Using Begin to start a transaction with the dbMap should return a
// transaction that continues to operate in the expected fashion.
tx, err := dbMap.Begin()
tx, err := dbMap.BeginTx(ctx)
defer func() { _ = tx.Rollback() }()
test.AssertNotError(t, err, "unexpected error beginning transaction")
testWrapper(tx)
// Using Begin to start a transaction with the dbMap and then using
// WithContext should return a transaction that continues to operate in the
// expected fashion.
tx, err = dbMap.Begin()
defer func() { _ = tx.Rollback() }()
test.AssertNotError(t, err, "unexpected error beginning transaction")
txWithContext := tx.WithContext(context.Background())
testWrapper(txWithContext)
}

View File

@ -1,6 +1,7 @@
package db
import (
"context"
"fmt"
"strings"
)
@ -68,7 +69,7 @@ func (mi *MultiInserter) Add(row []interface{}) error {
}
// query returns the formatted query string, and the slice of arguments for
// for gorp to use in place of the query's question marks. Currently only
// for borp to use in place of the query's question marks. Currently only
// used by .Insert(), below.
func (mi *MultiInserter) query() (string, []interface{}) {
var questionsBuf strings.Builder
@ -101,12 +102,11 @@ func (mi *MultiInserter) query() (string, []interface{}) {
}
// Insert inserts all the collected rows into the database represented by
// `queryer`. `queryer` is assumed to already have a context attached. If a
// non-empty returningColumn was provided, then it returns the list of values
// from that column returned by the query.
func (mi *MultiInserter) Insert(queryer Queryer) ([]int64, error) {
// `queryer`. If a non-empty returningColumn was provided, then it returns
// the list of values from that column returned by the query.
func (mi *MultiInserter) Insert(ctx context.Context, queryer Queryer) ([]int64, error) {
query, queryArgs := mi.query()
rows, err := queryer.Query(query, queryArgs...)
rows, err := queryer.QueryContext(ctx, query, queryArgs...)
if err != nil {
return nil, err
}

View File

@ -1,6 +1,7 @@
package db
import (
"context"
"testing"
berrors "github.com/letsencrypt/boulder/errors"
@ -8,9 +9,10 @@ import (
)
func TestRollback(t *testing.T) {
ctx := context.Background()
dbMap := testDbMap(t)
tx, _ := dbMap.Begin()
tx, _ := dbMap.BeginTx(ctx)
// Commit the transaction so that a subsequent rollback will always fail.
_ = tx.Commit()
@ -28,7 +30,7 @@ func TestRollback(t *testing.T) {
// Create a new transaction and don't commit it this time. The rollback should
// succeed.
tx, _ = dbMap.Begin()
tx, _ = dbMap.BeginTx(ctx)
result = rollback(tx, innerErr)
// We expect that the err is returned unwrapped.

View File

@ -3,19 +3,18 @@ package db
import "context"
// txFunc represents a function that does work in the context of a transaction.
type txFunc func(txWithCtx Executor) (interface{}, error)
type txFunc func(tx Executor) (interface{}, error)
// WithTransaction runs the given function in a transaction, rolling back if it
// returns an error and committing if not. The provided context is also attached
// to the transaction. WithTransaction also passes through a value returned by
// `f`, if there is no error.
func WithTransaction(ctx context.Context, dbMap DatabaseMap, f txFunc) (interface{}, error) {
tx, err := dbMap.Begin()
tx, err := dbMap.BeginTx(ctx)
if err != nil {
return nil, err
}
txWithCtx := tx.WithContext(ctx)
result, err := f(txWithCtx)
result, err := f(tx)
if err != nil {
return nil, rollback(tx, err)
}

View File

@ -191,10 +191,10 @@ Specifically, that means that all of our `SELECT` statements should enumerate
columns to select, and not use `*`. Also, generally speaking, we will need a
separate model `struct` for serializing and deserializing data before and
after the migration. This is because the ORM package we use,
[`gorp`](https://github.com/go-gorp/gorp), expects every field in a struct to
[`borp`](https://github.com/letsencrypt/borp), expects every field in a struct to
map to a column in the table. If we add a new field to a model struct and
Boulder attempts to write that struct to a table that doesn't yet have the
corresponding column (case 1), gorp will fail with `Insert failed table posts
corresponding column (case 1), borp will fail with `Insert failed table posts
has no column named Foo`. There are examples of such models in sa/model.go,
along with code to turn a model into a `struct` used internally.
@ -254,14 +254,14 @@ func (ssa *SQLStorageAuthority) GetPerson() (Person, error) {
func (ssa *SQLStorageAuthority) AddPerson(p Person) (error) {
if features.Enabled(features.AllowWizards) { // Added!
return ssa.dbMap.Insert(personModelv2{
return ssa.dbMap.Insert(context.Background(), personModelv2{
personModelv1: {
HatSize: p.HatSize,
},
IsWizard: p.IsWizard,
})
} else {
return ssa.dbMap.Insert(personModelv1{
return ssa.dbMap.Insert(context.Background(), personModelv1{
HatSize: p.HatSize,
// p.IsWizard ignored
})
@ -270,14 +270,14 @@ func (ssa *SQLStorageAuthority) AddPerson(p Person) (error) {
```
You will also need to update the `initTables` function from `sa/database.go` to
tell Gorp which table to use for your versioned model structs. Make sure to
tell borp which table to use for your versioned model structs. Make sure to
consult the flag you defined so that only **one** of the table maps is added at
any given time, otherwise Gorp will error. Depending on your table you may also
any given time, otherwise borp will error. Depending on your table you may also
need to add `SetKeys` and `SetVersionCol` entries for your versioned models.
Example:
```go
func initTables(dbMap *gorp.DbMap) {
func initTables(dbMap *borp.DbMap) {
// < unrelated lines snipped for brevity >
if features.Enabled(features.AllowWizards) {

6
go.mod
View File

@ -9,7 +9,6 @@ require (
github.com/aws/smithy-go v1.13.5
github.com/beeker1121/goque v1.0.3-0.20191103205551-d618510128af
github.com/eggsampler/acme/v3 v3.4.0
github.com/go-gorp/gorp/v3 v3.1.0
github.com/go-logr/stdr v1.2.2
github.com/go-redis/redis/v8 v8.11.5
github.com/go-sql-driver/mysql v1.7.1
@ -18,6 +17,7 @@ require (
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
github.com/hpcloud/tail v1.0.0
github.com/jmhodges/clock v1.2.0
github.com/letsencrypt/borp v0.0.0-20230707160741-6cc6ce580243
github.com/letsencrypt/challtestsrv v1.2.1
github.com/letsencrypt/pkcs11key/v4 v4.0.0
github.com/letsencrypt/validator/v10 v10.0.0-20230215210743-a0c7dfc17158
@ -73,11 +73,12 @@ require (
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/pelletier/go-toml v1.9.3 // indirect
github.com/poy/onpar v1.1.2 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/syndtr/goleveldb v1.0.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.15.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.15.0 // indirect
@ -87,6 +88,7 @@ require (
golang.org/x/sys v0.9.0 // indirect
golang.org/x/tools v0.8.0 // indirect
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/fsnotify.v1 v1.4.7 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
k8s.io/klog/v2 v2.80.1 // indirect

78
go.sum
View File

@ -39,12 +39,14 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g=
github.com/a8m/expect v1.0.0/go.mod h1:4IwSCMumY49ScypDnjNbYEjgVeqy1/U2cEs3Lat96eA=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/aws/aws-sdk-go-v2 v1.17.7/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
github.com/aws/aws-sdk-go-v2 v1.18.0/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
github.com/aws/aws-sdk-go-v2 v1.18.1 h1:+tefE750oAb7ZQGzla6bLkOwfcQCEtC5y2RqoqCeqKo=
@ -113,11 +115,20 @@ github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWH
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/eggsampler/acme/v3 v3.4.0 h1:LHWnB3wShVshK1+umL6ObCjnc0MM+D7TE8JINjk8zGY=
github.com/eggsampler/acme/v3 v3.4.0/go.mod h1:/qh0rKC/Dh7Jj+p4So7DbWmFNzC4dpcpK53r226Fhuo=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@ -127,6 +138,7 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@ -135,8 +147,6 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs=
github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
@ -162,9 +172,11 @@ github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrt
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -233,21 +245,27 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2 h1:gDLXvp5S9izjldquuoAhDzccbskOL6tDC5jMSyx3zxE=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2/go.mod h1:7pdNwVWBBHGiCxa9lAszqCJMbfTISJ7oMftp8+UGV08=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/jmhodges/clock v1.2.0 h1:eq4kys+NI0PLngzaHEe7AmPT90XMGIEySD1JfV1PDIs=
github.com/jmhodges/clock v1.2.0/go.mod h1:qKjhA7x7u/lQpPB1XAqX1b1lCI/w3/fNuYpI/ZjLynI=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@ -257,23 +275,32 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/letsencrypt/borp v0.0.0-20230707160741-6cc6ce580243 h1:xS2U6PQYRURk61YN4Y5xvyLbQVyAP/8fpE6hJZdwEWs=
github.com/letsencrypt/borp v0.0.0-20230707160741-6cc6ce580243/go.mod h1:podMDq5wDu2ZO6JMKYQcjD3QdqOfNLWtP2RDSy8CHUU=
github.com/letsencrypt/challtestsrv v1.2.1 h1:Lzv4jM+wSgVMCeO5a/F/IzSanhClstFMnX6SfrAJXjI=
github.com/letsencrypt/challtestsrv v1.2.1/go.mod h1:Ur4e4FvELUXLGhkMztHOsPIsvGxD/kzSJninOrkM+zc=
github.com/letsencrypt/pkcs11key/v4 v4.0.0 h1:qLc/OznH7xMr5ARJgkZCCWk+EomQkiNTOoOF5LAgagc=
github.com/letsencrypt/pkcs11key/v4 v4.0.0/go.mod h1:EFUvBDay26dErnNb70Nd0/VW3tJiIbETBPTl9ATXQag=
github.com/letsencrypt/validator/v10 v10.0.0-20230215210743-a0c7dfc17158 h1:HGFsIltYMUiB5eoFSowFzSoXkocM2k9ctmJ57QMGjys=
github.com/letsencrypt/validator/v10 v10.0.0-20230215210743-a0c7dfc17158/go.mod h1:ZFNBS3H6OEsprCRjscty6GCBe5ZiX44x6qY4s7+bDX0=
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
@ -282,6 +309,8 @@ github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7Xn
github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=
github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
@ -291,22 +320,29 @@ github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1
github.com/mreiferson/go-httpclient v0.0.0-20201222173833-5e475fde3a4d/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nelsam/hel/v2 v2.3.2/go.mod h1:1ZTGfU2PFTOd5mx22i5O0Lc2GY933lQ2wb/ggy+rL3w=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ=
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/poy/onpar v0.0.0-20200406201722-06f95a1c68e8/go.mod h1:nSbFQvMj97ZyhFRSJYtut+msi4sOY6zJDGCdSc+/rZU=
github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY=
github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
@ -319,6 +355,8 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
@ -326,23 +364,35 @@ github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+
github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
@ -350,17 +400,21 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0=
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/weppos/publicsuffix-go v0.12.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
github.com/weppos/publicsuffix-go v0.13.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
github.com/weppos/publicsuffix-go v0.30.0/go.mod h1:kBi8zwYnR0zrbm8RcuN1o9Fzgpnnn+btVN8uWPMyXAY=
github.com/weppos/publicsuffix-go v0.30.1-0.20230620154423-38c92ad2d5c6 h1:kNn7cjQYeNjKUflvFFCxFeyS7ENcDdfPmkhFpgd0G/A=
github.com/weppos/publicsuffix-go v0.30.1-0.20230620154423-38c92ad2d5c6/go.mod h1:wdMq89hDN07Zqr0yqYAXIBTJXl4MEELx+HYHOZdf5gM=
github.com/weppos/publicsuffix-go/publicsuffix/generator v0.0.0-20220927085643-dc0d00c92642/go.mod h1:GHfoeIdZLdZmLjMlzBftbTDntahTttUMWjxZwQJhULE=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@ -377,6 +431,7 @@ github.com/zmap/zcrypto v0.0.0-20230310154051-c8b263fd8300/go.mod h1:mOd4yUMgn2f
github.com/zmap/zlint/v3 v3.0.0/go.mod h1:paGwFySdHIBEMJ61YjoqT4h7Ge+fdYG4sUQhnTb1lJ8=
github.com/zmap/zlint/v3 v3.5.0 h1:Eh2B5t6VKgVH0DFmTwOqE50POvyDhUaU9T2mJOe1vfQ=
github.com/zmap/zlint/v3 v3.5.0/go.mod h1:JkNSrsDJ8F4VRtBZcYUQSvnWFL7utcjDIn+FE64mlBI=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@ -403,7 +458,10 @@ go.opentelemetry.io/otel/trace v1.15.0/go.mod h1:CUsmE2Ht1CRkvE8OsMESvraoZrrcgD1
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw=
go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@ -457,12 +515,14 @@ golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@ -523,8 +583,10 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -600,6 +662,7 @@ golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@ -632,6 +695,7 @@ golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200313205530-4303120df7d8/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
@ -708,6 +772,7 @@ google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
@ -746,13 +811,16 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/go-jose/go-jose.v2 v2.6.1 h1:qEzJlIDmG9q5VO0M/o8tGS65QMHMS1w01TQJB1VPJ4U=
gopkg.in/go-jose/go-jose.v2 v2.6.1/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -6,7 +6,6 @@ import (
"reflect"
"sync"
"github.com/go-gorp/gorp/v3"
"github.com/prometheus/client_golang/prometheus"
"golang.org/x/crypto/ocsp"
@ -22,8 +21,7 @@ import (
// dbSelector is a limited subset of the db.WrappedMap interface to allow for
// easier mocking of mysql operations in tests.
type dbSelector interface {
SelectOne(holder interface{}, query string, args ...interface{}) error
WithContext(ctx context.Context) gorp.SqlExecutor
SelectOne(ctx context.Context, holder interface{}, query string, args ...interface{}) error
}
// rocspSourceInterface expands on responder.Source by adding a private signAndSave method.
@ -102,7 +100,7 @@ func (src *checkedRedisSource) Response(ctx context.Context, req *ocsp.Request)
if src.sac != nil {
dbStatus, dbErr = src.sac.GetRevocationStatus(ctx, &sapb.Serial{Serial: serialString})
} else {
dbStatus, dbErr = sa.SelectRevocationStatus(src.dbMap.WithContext(ctx), serialString)
dbStatus, dbErr = sa.SelectRevocationStatus(ctx, src.dbMap, serialString)
}
}()
go func() {

View File

@ -9,7 +9,6 @@ import (
"testing"
"time"
"github.com/go-gorp/gorp/v3"
"golang.org/x/crypto/ocsp"
"google.golang.org/grpc"
"google.golang.org/protobuf/types/known/timestamppb"
@ -71,11 +70,7 @@ type echoSelector struct {
status sa.RevocationStatusModel
}
func (s echoSelector) WithContext(context.Context) gorp.SqlExecutor {
return s
}
func (s echoSelector) SelectOne(output interface{}, _ string, _ ...interface{}) error {
func (s echoSelector) SelectOne(_ context.Context, output interface{}, _ string, _ ...interface{}) error {
outputPtr, ok := output.(*sa.RevocationStatusModel)
if !ok {
return fmt.Errorf("incorrect output type %T", output)
@ -89,27 +84,19 @@ type errorSelector struct {
db.MockSqlExecutor
}
func (s errorSelector) SelectOne(_ interface{}, _ string, _ ...interface{}) error {
func (s errorSelector) SelectOne(_ context.Context, _ interface{}, _ string, _ ...interface{}) error {
return errors.New("oops")
}
func (s errorSelector) WithContext(context.Context) gorp.SqlExecutor {
return s
}
// notFoundSelector always returns an NoRows error.
type notFoundSelector struct {
db.MockSqlExecutor
}
func (s notFoundSelector) SelectOne(_ interface{}, _ string, _ ...interface{}) error {
func (s notFoundSelector) SelectOne(_ context.Context, _ interface{}, _ string, _ ...interface{}) error {
return db.ErrDatabaseOp{Err: sql.ErrNoRows}
}
func (s notFoundSelector) WithContext(context.Context) gorp.SqlExecutor {
return s
}
// echoSA always returns the given revocation status.
type echoSA struct {
mocks.StorageAuthorityReadOnly

View File

@ -5,8 +5,8 @@ import (
"fmt"
"time"
"github.com/go-gorp/gorp/v3"
"github.com/go-sql-driver/mysql"
"github.com/letsencrypt/borp"
"github.com/prometheus/client_golang/prometheus"
"github.com/letsencrypt/boulder/cmd"
@ -45,7 +45,7 @@ type DbSettings struct {
ConnMaxIdleTime time.Duration
}
// InitWrappedDb constructs a wrapped gorp mapping object with the provided
// InitWrappedDb constructs a wrapped borp mapping object with the provided
// settings. If scope is non-nil, Prometheus metrics will be exported. If logger
// is non-nil, SQL debug-level logging will be enabled. The only required parameter
// is config.
@ -82,7 +82,7 @@ func InitWrappedDb(config cmd.DBConfig, scope prometheus.Registerer, logger blog
}
if scope != nil {
err = initDBMetrics(dbMap.Db, scope, settings, addr, user)
err = initDBMetrics(dbMap.SQLDb(), scope, settings, addr, user)
if err != nil {
return nil, fmt.Errorf("while initializing metrics: %w", err)
}
@ -90,7 +90,7 @@ func InitWrappedDb(config cmd.DBConfig, scope prometheus.Registerer, logger blog
return dbMap, nil
}
// DBMapForTest creates a wrapped root gorp mapping object. Create one of these for
// DBMapForTest creates a wrapped root borp mapping object. Create one of these for
// each database schema you wish to map. Each DbMap contains a list of mapped
// tables. It automatically maps the tables for the primary parts of Boulder
// around the Storage Authority.
@ -146,7 +146,7 @@ var setConnMaxIdleTime = func(db *sql.DB, connMaxIdleTime time.Duration) {
//
// This function also:
// - pings the database (and errors if it's unreachable)
// - wraps the connection in a gorp.DbMap so we can use the handy Get/Insert methods gorp provides
// - wraps the connection in a borp.DbMap so we can use the handy Get/Insert methods borp provides
// - wraps that in a db.WrappedMap to get more useful error messages
func newDbMapFromMySQLConfig(config *mysql.Config, settings DbSettings) (*boulderDB.WrappedMap, error) {
err := adjustMySQLConfig(config)
@ -166,11 +166,11 @@ func newDbMapFromMySQLConfig(config *mysql.Config, settings DbSettings) (*boulde
setConnMaxLifetime(db, settings.ConnMaxLifetime)
setConnMaxIdleTime(db, settings.ConnMaxIdleTime)
dialect := gorp.MySQLDialect{Engine: "InnoDB", Encoding: "UTF8"}
dbmap := &gorp.DbMap{Db: db, Dialect: dialect, TypeConverter: BoulderTypeConverter{}}
dialect := borp.MySQLDialect{Engine: "InnoDB", Encoding: "UTF8"}
dbmap := &borp.DbMap{Db: db, Dialect: dialect, TypeConverter: BoulderTypeConverter{}}
initTables(dbmap)
return &boulderDB.WrappedMap{DbMap: dbmap}, nil
return boulderDB.NewWrappedMap(dbmap), nil
}
// adjustMySQLConfig sets certain flags that we want on every connection.
@ -239,17 +239,17 @@ func adjustMySQLConfig(conf *mysql.Config) error {
return nil
}
// SetSQLDebug enables GORP SQL-level Debugging
// SetSQLDebug enables borp SQL-level Debugging
func SetSQLDebug(dbMap *boulderDB.WrappedMap, log blog.Logger) {
dbMap.TraceOn("SQL: ", &SQLLogger{log})
dbMap.BorpDB().TraceOn("SQL: ", &SQLLogger{log})
}
// SQLLogger adapts the Boulder Logger to a format GORP can use.
// SQLLogger adapts the Boulder Logger to a format borp can use.
type SQLLogger struct {
blog.Logger
}
// Printf adapts the AuditLogger to GORP's interface
// Printf adapts the AuditLogger to borp's interface
func (log *SQLLogger) Printf(format string, v ...interface{}) {
log.Debugf(format, v...)
}
@ -259,8 +259,8 @@ func (log *SQLLogger) Printf(format string, v ...interface{}) {
// it is very important to declare them as a such here. It produces a side
// effect in Insert() where the inserted object has its id field set to the
// autoincremented value that resulted from the insert. See
// https://godoc.org/github.com/coopernurse/gorp#DbMap.Insert
func initTables(dbMap *gorp.DbMap) {
// https://godoc.org/github.com/coopernurse/borp#DbMap.Insert
func initTables(dbMap *borp.DbMap) {
regTable := dbMap.AddTableWithName(regModel{}, "registrations").SetKeys(true, "ID")
regTable.SetVersionCol("LockCol")

View File

@ -1,6 +1,7 @@
package sa
import (
"context"
"database/sql"
"errors"
"os"
@ -134,7 +135,7 @@ func TestStrictness(t *testing.T) {
if err != nil {
t.Fatal(err)
}
_, err = dbMap.Exec(`insert into orderToAuthz2 set
_, err = dbMap.ExecContext(ctx, `insert into orderToAuthz2 set
orderID=999999999999999999999999999,
authzID=999999999999999999999999999;`)
if err == nil {
@ -153,7 +154,7 @@ func TestTimeouts(t *testing.T) {
// SLEEP is defined to return 1 if it was interrupted, but we want to actually
// get an error to simulate what would happen with a slow query. So we wrap
// the SLEEP in a subselect.
_, err = dbMap.Exec(`SELECT 1 FROM (SELECT SLEEP(5)) as subselect;`)
_, err = dbMap.ExecContext(ctx, `SELECT 1 FROM (SELECT SLEEP(5)) as subselect;`)
if err == nil {
t.Fatal("Expected error when running slow query, got none.")
}
@ -175,6 +176,7 @@ func TestAutoIncrementSchema(t *testing.T) {
var count int64
err = dbMap.SelectOne(
context.Background(),
&count,
`SELECT COUNT(*) FROM columns WHERE
table_schema LIKE 'boulder%' AND

View File

@ -64,9 +64,9 @@ const regFields = "id, jwk, jwk_sha256, contact, agreement, initialIP, createdAt
// ClearEmail removes the provided email address from one specified registration. If
// there are multiple email addresses present, it does not modify other ones. If the email
// address is not present, it does not modify the registration and will return a nil error.
func ClearEmail(dbMap db.DatabaseMap, ctx context.Context, regID int64, email string) error {
_, overallError := db.WithTransaction(ctx, dbMap, func(txWithCtx db.Executor) (interface{}, error) {
curr, err := selectRegistration(txWithCtx, "id", regID)
func ClearEmail(ctx context.Context, dbMap db.DatabaseMap, regID int64, email string) error {
_, overallError := db.WithTransaction(ctx, dbMap, func(tx db.Executor) (interface{}, error) {
curr, err := selectRegistration(ctx, tx, "id", regID)
if err != nil {
return nil, err
}
@ -94,7 +94,7 @@ func ClearEmail(dbMap db.DatabaseMap, ctx context.Context, regID int64, email st
return nil, err
}
return txWithCtx.Update(newModel)
return tx.Update(ctx, newModel)
})
if overallError != nil {
return overallError
@ -104,13 +104,14 @@ func ClearEmail(dbMap db.DatabaseMap, ctx context.Context, regID int64, email st
}
// selectRegistration selects all fields of one registration model
func selectRegistration(s db.OneSelector, whereCol string, args ...interface{}) (*regModel, error) {
func selectRegistration(ctx context.Context, s db.OneSelector, whereCol string, args ...interface{}) (*regModel, error) {
if whereCol != "id" && whereCol != "jwk_sha256" {
return nil, fmt.Errorf("column name %q invalid for registrations table WHERE clause", whereCol)
}
var model regModel
err := s.SelectOne(
ctx,
&model,
"SELECT "+regFields+" FROM registrations WHERE "+whereCol+" = ? LIMIT 1",
args...,
@ -123,9 +124,10 @@ const certFields = "registrationID, serial, digest, der, issued, expires"
// SelectCertificate selects all fields of one certificate object identified by
// a serial. If more than one row contains the same serial only the first is
// returned.
func SelectCertificate(s db.OneSelector, serial string) (core.Certificate, error) {
func SelectCertificate(ctx context.Context, s db.OneSelector, serial string) (core.Certificate, error) {
var model core.Certificate
err := s.SelectOne(
ctx,
&model,
"SELECT "+certFields+" FROM certificates WHERE serial = ? LIMIT 1",
serial,
@ -137,9 +139,10 @@ const precertFields = "registrationID, serial, der, issued, expires"
// SelectPrecertificate selects all fields of one precertificate object
// identified by serial.
func SelectPrecertificate(s db.OneSelector, serial string) (core.Certificate, error) {
func SelectPrecertificate(ctx context.Context, s db.OneSelector, serial string) (core.Certificate, error) {
var model precertificateModel
err := s.SelectOne(
ctx,
&model,
"SELECT "+precertFields+" FROM precertificates WHERE serial = ? LIMIT 1",
serial)
@ -158,18 +161,20 @@ type CertWithID struct {
}
// SelectCertificates selects all fields of multiple certificate objects
func SelectCertificates(s db.Selector, q string, args map[string]interface{}) ([]CertWithID, error) {
func SelectCertificates(ctx context.Context, s db.Selector, q string, args map[string]interface{}) ([]CertWithID, error) {
var models []CertWithID
_, err := s.Select(
ctx,
&models,
"SELECT id, "+certFields+" FROM certificates "+q, args)
return models, err
}
// SelectPrecertificates selects all fields of multiple precertificate objects.
func SelectPrecertificates(s db.Selector, q string, args map[string]interface{}) ([]CertWithID, error) {
func SelectPrecertificates(ctx context.Context, s db.Selector, q string, args map[string]interface{}) ([]CertWithID, error) {
var models []CertWithID
_, err := s.Select(
ctx,
&models,
"SELECT id, "+precertFields+" FROM precertificates "+q, args)
return models, err
@ -192,9 +197,10 @@ const certStatusFields = "id, serial, status, ocspLastUpdated, revokedDate, revo
// SelectCertificateStatus selects all fields of one certificate status model
// identified by serial
func SelectCertificateStatus(s db.OneSelector, serial string) (core.CertificateStatus, error) {
func SelectCertificateStatus(ctx context.Context, s db.OneSelector, serial string) (core.CertificateStatus, error) {
var model core.CertificateStatus
err := s.SelectOne(
ctx,
&model,
"SELECT "+certStatusFields+" FROM certificateStatus WHERE serial = ? LIMIT 1",
serial,
@ -213,9 +219,10 @@ type RevocationStatusModel struct {
// SelectRevocationStatus returns the authoritative revocation information for
// the certificate with the given serial.
func SelectRevocationStatus(s db.OneSelector, serial string) (*sapb.RevocationStatus, error) {
func SelectRevocationStatus(ctx context.Context, s db.OneSelector, serial string) (*sapb.RevocationStatus, error) {
var model RevocationStatusModel
err := s.SelectOne(
ctx,
&model,
"SELECT status, revokedDate, revokedReason FROM certificateStatus WHERE serial = ? LIMIT 1",
serial,
@ -560,6 +567,7 @@ func rehydrateHostPort(vr *core.ValidationRecord) error {
// the past. If the stored authz has a valid status, it is returned with a
// valid status regardless of whether it is also expired.
func SelectAuthzsMatchingIssuance(
ctx context.Context,
s db.Selector,
regID int64,
issued time.Time,
@ -588,7 +596,7 @@ func SelectAuthzsMatchingIssuance(
}
var authzModels []authzModel
_, err := s.Select(&authzModels, query, args...)
_, err := s.Select(ctx, &authzModels, query, args...)
if err != nil {
return nil, err
}
@ -885,8 +893,8 @@ type orderFQDNSet struct {
Expires time.Time
}
func addFQDNSet(db db.Inserter, names []string, serial string, issued time.Time, expires time.Time) error {
return db.Insert(&core.FQDNSet{
func addFQDNSet(ctx context.Context, db db.Inserter, names []string, serial string, issued time.Time, expires time.Time) error {
return db.Insert(ctx, &core.FQDNSet{
SetHash: HashNames(names),
Serial: serial,
Issued: issued,
@ -899,12 +907,13 @@ func addFQDNSet(db db.Inserter, names []string, serial string, issued time.Time,
// addition can take place within the order addition transaction. The caller is
// required to rollback the transaction if an error is returned.
func addOrderFQDNSet(
ctx context.Context,
db db.Inserter,
names []string,
orderID int64,
regID int64,
expires time.Time) error {
return db.Insert(&orderFQDNSet{
return db.Insert(ctx, &orderFQDNSet{
SetHash: HashNames(names),
OrderID: orderID,
RegistrationID: regID,
@ -917,10 +926,11 @@ func addOrderFQDNSet(
// take place within the finalization transaction. The caller is required to
// rollback the transaction if an error is returned.
func deleteOrderFQDNSet(
ctx context.Context,
db db.Execer,
orderID int64) error {
result, err := db.Exec(`
result, err := db.ExecContext(ctx, `
DELETE FROM orderFqdnSets
WHERE orderID = ?`,
orderID)
@ -940,7 +950,7 @@ func deleteOrderFQDNSet(
return nil
}
func addIssuedNames(queryer db.Queryer, cert *x509.Certificate, isRenewal bool) error {
func addIssuedNames(ctx context.Context, queryer db.Queryer, cert *x509.Certificate, isRenewal bool) error {
if len(cert.DNSNames) == 0 {
return berrors.InternalServerError("certificate has no DNSNames")
}
@ -960,11 +970,11 @@ func addIssuedNames(queryer db.Queryer, cert *x509.Certificate, isRenewal bool)
return err
}
}
_, err = multiInserter.Insert(queryer)
_, err = multiInserter.Insert(ctx, queryer)
return err
}
func addKeyHash(db db.Inserter, cert *x509.Certificate) error {
func addKeyHash(ctx context.Context, db db.Inserter, cert *x509.Certificate) error {
if cert.RawSubjectPublicKeyInfo == nil {
return errors.New("certificate has a nil RawSubjectPublicKeyInfo")
}
@ -974,7 +984,7 @@ func addKeyHash(db db.Inserter, cert *x509.Certificate) error {
CertNotAfter: cert.NotAfter,
CertSerial: core.SerialToString(cert.SerialNumber),
}
return db.Insert(khm)
return db.Insert(ctx, khm)
}
var blockedKeysColumns = "keyHash, added, source, comment"
@ -992,9 +1002,8 @@ var blockedKeysColumns = "keyHash, added, source, comment"
// - If all of the order's authorizations are valid, and we haven't begun
// processing, then the order is status ready.
//
// An error is returned for any other case. It assumes that the provided
// database selector already has a context associated with it.
func statusForOrder(s db.Selector, order *corepb.Order, now time.Time) (string, error) {
// An error is returned for any other case.
func statusForOrder(ctx context.Context, s db.Selector, order *corepb.Order, now time.Time) (string, error) {
// Without any further work we know an order with an error is invalid
if order.Error != nil {
return string(core.StatusInvalid), nil
@ -1013,7 +1022,7 @@ func statusForOrder(s db.Selector, order *corepb.Order, now time.Time) (string,
}
// Get the full Authorization objects for the order
authzValidityInfo, err := getAuthorizationStatuses(s, order.V2Authorizations)
authzValidityInfo, err := getAuthorizationStatuses(ctx, s, order.V2Authorizations)
// If there was an error getting the authorizations, return it immediately
if err != nil {
return "", err
@ -1109,9 +1118,8 @@ type authzValidity struct {
}
// getAuthorizationStatuses takes a sequence of authz IDs, and returns the
// status and expiration date of each of them. It assumes that the provided
// database selector already has a context associated with it.
func getAuthorizationStatuses(s db.Selector, ids []int64) ([]authzValidity, error) {
// status and expiration date of each of them.
func getAuthorizationStatuses(ctx context.Context, s db.Selector, ids []int64) ([]authzValidity, error) {
var params []interface{}
for _, id := range ids {
params = append(params, id)
@ -1121,6 +1129,7 @@ func getAuthorizationStatuses(s db.Selector, ids []int64) ([]authzValidity, erro
Expires time.Time
}
_, err := s.Select(
ctx,
&validityInfo,
fmt.Sprintf("SELECT status, expires FROM authz2 WHERE id IN (%s)",
db.QuestionMarks(len(ids))),
@ -1140,11 +1149,11 @@ func getAuthorizationStatuses(s db.Selector, ids []int64) ([]authzValidity, erro
return allAuthzValidity, nil
}
// authzForOrder retrieves the authorization IDs for an order. It assumes that
// the provided database selector already has a context associated with it.
func authzForOrder(s db.Selector, orderID int64) ([]int64, error) {
// authzForOrder retrieves the authorization IDs for an order.
func authzForOrder(ctx context.Context, s db.Selector, orderID int64) ([]int64, error) {
var v2IDs []int64
_, err := s.Select(
ctx,
&v2IDs,
"SELECT authzID FROM orderToAuthz2 WHERE orderID = ?",
orderID,
@ -1153,11 +1162,11 @@ func authzForOrder(s db.Selector, orderID int64) ([]int64, error) {
}
// namesForOrder finds all of the requested names associated with an order. The
// names are returned in their reversed form (see `sa.ReverseName`). It assumes
// that the provided database selector already has a context associated with it.
func namesForOrder(s db.Selector, orderID int64) ([]string, error) {
// names are returned in their reversed form (see `sa.ReverseName`).
func namesForOrder(ctx context.Context, s db.Selector, orderID int64) ([]string, error) {
var reversedNames []string
_, err := s.Select(
ctx,
&reversedNames,
`SELECT reversedName
FROM requestedNames

View File

@ -1,6 +1,7 @@
package sa
import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
@ -278,33 +279,35 @@ func TestPopulateAttemptedFieldsBadJSON(t *testing.T) {
}
func TestCertificatesTableContainsDuplicateSerials(t *testing.T) {
ctx := context.Background()
sa, fc, cleanUp := initSA(t)
defer cleanUp()
serialString := core.SerialToString(big.NewInt(1337))
// Insert a certificate with a serial of `1337`.
err := insertCertificate(sa.dbMap, fc, "1337.com", "leet", 1337, 1)
err := insertCertificate(ctx, sa.dbMap, fc, "1337.com", "leet", 1337, 1)
test.AssertNotError(t, err, "couldn't insert valid certificate")
// This should return the certificate that we just inserted.
certA, err := SelectCertificate(sa.dbMap, serialString)
certA, err := SelectCertificate(ctx, sa.dbMap, serialString)
test.AssertNotError(t, err, "received an error for a valid query")
// Insert a certificate with a serial of `1337` but for a different
// hostname.
err = insertCertificate(sa.dbMap, fc, "1337.net", "leet", 1337, 1)
err = insertCertificate(ctx, sa.dbMap, fc, "1337.net", "leet", 1337, 1)
test.AssertNotError(t, err, "couldn't insert valid certificate")
// Despite a duplicate being present, this shouldn't error.
certB, err := SelectCertificate(sa.dbMap, serialString)
certB, err := SelectCertificate(ctx, sa.dbMap, serialString)
test.AssertNotError(t, err, "received an error for a valid query")
// Ensure that `certA` and `certB` are the same.
test.AssertByteEquals(t, certA.DER, certB.DER)
}
func insertCertificate(dbMap *db.WrappedMap, fc clock.FakeClock, hostname, cn string, serial, regID int64) error {
func insertCertificate(ctx context.Context, dbMap *db.WrappedMap, fc clock.FakeClock, hostname, cn string, serial, regID int64) error {
serialBigInt := big.NewInt(serial)
serialString := core.SerialToString(serialBigInt)
@ -325,7 +328,7 @@ func insertCertificate(dbMap *db.WrappedMap, fc clock.FakeClock, hostname, cn st
Expires: template.NotAfter,
DER: certDer,
}
err := dbMap.Insert(cert)
err := dbMap.Insert(ctx, cert)
if err != nil {
return err
}
@ -349,12 +352,14 @@ func makeKey() rsa.PrivateKey {
}
func TestIncidentSerialModel(t *testing.T) {
ctx := context.Background()
testIncidentsDbMap, err := DBMapForTest(vars.DBConnIncidentsFullPerms)
test.AssertNotError(t, err, "Couldn't create test dbMap")
defer test.ResetIncidentsTestDatabase(t)
// Inserting and retrieving a row with only the serial populated should work.
_, err = testIncidentsDbMap.Exec(
_, err = testIncidentsDbMap.ExecContext(ctx,
"INSERT INTO incident_foo (serial) VALUES (?)",
"1337",
)
@ -362,6 +367,7 @@ func TestIncidentSerialModel(t *testing.T) {
var res1 incidentSerialModel
err = testIncidentsDbMap.SelectOne(
ctx,
&res1,
"SELECT * FROM incident_foo WHERE serial = ?",
"1337",
@ -374,7 +380,7 @@ func TestIncidentSerialModel(t *testing.T) {
test.AssertBoxedNil(t, res1.LastNoticeSent, "lastNoticeSent should be NULL")
// Inserting and retrieving a row with all columns populated should work.
_, err = testIncidentsDbMap.Exec(
_, err = testIncidentsDbMap.ExecContext(ctx,
"INSERT INTO incident_foo (serial, registrationID, orderID, lastNoticeSent) VALUES (?, ?, ?, ?)",
"1338",
1,
@ -385,6 +391,7 @@ func TestIncidentSerialModel(t *testing.T) {
var res2 incidentSerialModel
err = testIncidentsDbMap.SelectOne(
ctx,
&res2,
"SELECT * FROM incident_foo WHERE serial = ?",
"1338",

View File

@ -1,6 +1,7 @@
package sa
import (
"context"
"strings"
"time"
@ -29,9 +30,8 @@ func baseDomain(name string) string {
// addCertificatesPerName adds 1 to the rate limit count for the provided
// domains, in a specific time bucket. It must be executed in a transaction, and
// the input timeToTheHour must be a time rounded to an hour. It assumes that
// the given db already has a context associated with it.
func (ssa *SQLStorageAuthority) addCertificatesPerName(db db.SelectExecer, names []string, timeToTheHour time.Time) error {
// the input timeToTheHour must be a time rounded to an hour.
func (ssa *SQLStorageAuthority) addCertificatesPerName(ctx context.Context, db db.SelectExecer, names []string, timeToTheHour time.Time) error {
// De-duplicate the base domains.
baseDomainsMap := make(map[string]bool)
var qmarks []string
@ -45,7 +45,7 @@ func (ssa *SQLStorageAuthority) addCertificatesPerName(db db.SelectExecer, names
}
}
_, err := db.Exec(`INSERT INTO certificatesPerName (eTLDPlusOne, time, count) VALUES `+
_, err := db.ExecContext(ctx, `INSERT INTO certificatesPerName (eTLDPlusOne, time, count) VALUES `+
strings.Join(qmarks, ", ")+` ON DUPLICATE KEY UPDATE count=count+1;`,
values...)
if err != nil {
@ -56,15 +56,15 @@ func (ssa *SQLStorageAuthority) addCertificatesPerName(db db.SelectExecer, names
}
// countCertificates returns the count of certificates issued for a domain's
// eTLD+1 (aka base domain), during a given time range. It assumes that the
// given db already has a context associated with it.
func (ssa *SQLStorageAuthorityRO) countCertificates(dbMap db.Selector, domain string, timeRange *sapb.Range) (int64, time.Time, error) {
// eTLD+1 (aka base domain), during a given time range.
func (ssa *SQLStorageAuthorityRO) countCertificates(ctx context.Context, dbMap db.Selector, domain string, timeRange *sapb.Range) (int64, time.Time, error) {
latest := time.Unix(0, timeRange.Latest)
var results []struct {
Count int64
Time time.Time
}
_, err := dbMap.Select(
ctx,
&results,
`SELECT count, time FROM certificatesPerName
WHERE eTLDPlusOne = :baseDomain AND
@ -100,10 +100,9 @@ func (ssa *SQLStorageAuthorityRO) countCertificates(dbMap db.Selector, domain st
// addNewOrdersRateLimit adds 1 to the rate limit count for the provided ID, in
// a specific time bucket. It must be executed in a transaction, and the input
// timeToTheMinute must be a time rounded to a minute. It assumes that the given
// db already has a context associated with it.
func addNewOrdersRateLimit(dbMap db.SelectExecer, regID int64, timeToTheMinute time.Time) error {
_, err := dbMap.Exec(`INSERT INTO newOrdersRL
// timeToTheMinute must be a time rounded to a minute.
func addNewOrdersRateLimit(ctx context.Context, dbMap db.SelectExecer, regID int64, timeToTheMinute time.Time) error {
_, err := dbMap.ExecContext(ctx, `INSERT INTO newOrdersRL
(regID, time, count)
VALUES (?, ?, 1)
ON DUPLICATE KEY UPDATE count=count+1;`,
@ -117,11 +116,11 @@ func addNewOrdersRateLimit(dbMap db.SelectExecer, regID int64, timeToTheMinute t
}
// countNewOrders returns the count of orders created in the given time range
// for the given registration ID. It assumes that the given db already has a
// context associated with it.
func countNewOrders(dbMap db.Selector, req *sapb.CountOrdersRequest) (*sapb.Count, error) {
// for the given registration ID.
func countNewOrders(ctx context.Context, dbMap db.Selector, req *sapb.CountOrdersRequest) (*sapb.Count, error) {
var counts []int64
_, err := dbMap.Select(
ctx,
&counts,
`SELECT count FROM newOrdersRL
WHERE regID = :regID AND

View File

@ -1,6 +1,7 @@
package sa
import (
"context"
"fmt"
"testing"
"time"
@ -10,6 +11,8 @@ import (
)
func TestCertsPerNameRateLimitTable(t *testing.T) {
ctx := context.Background()
sa, _, cleanUp := initSA(t)
defer cleanUp()
@ -41,11 +44,11 @@ func TestCertsPerNameRateLimitTable(t *testing.T) {
}
for _, input := range inputs {
tx, err := sa.dbMap.Begin()
tx, err := sa.dbMap.BeginTx(ctx)
if err != nil {
t.Fatal(err)
}
err = sa.addCertificatesPerName(tx, input.names, input.time)
err = sa.addCertificatesPerName(ctx, tx, input.names, input.time)
if err != nil {
t.Fatal(err)
}
@ -78,7 +81,7 @@ func TestCertsPerNameRateLimitTable(t *testing.T) {
Earliest: aprilFirst.Add(-1 * time.Second).UnixNano(),
Latest: aprilFirst.Add(aWeek).UnixNano(),
}
count, earliest, err := sa.countCertificatesByName(sa.dbMap, tc.domainName, timeRange)
count, earliest, err := sa.countCertificatesByName(ctx, sa.dbMap, tc.domainName, timeRange)
if err != nil {
t.Fatal(err)
}
@ -110,27 +113,27 @@ func TestNewOrdersRateLimitTable(t *testing.T) {
}
for i := 0; i <= 10; i++ {
tx, err := sa.dbMap.Begin()
tx, err := sa.dbMap.BeginTx(ctx)
test.AssertNotError(t, err, "failed to open tx")
for j := 0; j < i+1; j++ {
err = addNewOrdersRateLimit(tx, manyCountRegID, start.Add(time.Minute*time.Duration(i)))
err = addNewOrdersRateLimit(ctx, tx, manyCountRegID, start.Add(time.Minute*time.Duration(i)))
}
test.AssertNotError(t, err, "addNewOrdersRateLimit failed")
test.AssertNotError(t, tx.Commit(), "failed to commit tx")
}
count, err := countNewOrders(sa.dbMap, req)
count, err := countNewOrders(ctx, sa.dbMap, req)
test.AssertNotError(t, err, "countNewOrders failed")
test.AssertEquals(t, count.Count, int64(0))
req.AccountID = manyCountRegID
count, err = countNewOrders(sa.dbMap, req)
count, err = countNewOrders(ctx, sa.dbMap, req)
test.AssertNotError(t, err, "countNewOrders failed")
test.AssertEquals(t, count.Count, int64(65))
req.Range.Earliest = start.Add(time.Minute * 5).UnixNano()
req.Range.Latest = start.Add(time.Minute * 10).UnixNano()
count, err = countNewOrders(sa.dbMap, req)
count, err = countNewOrders(ctx, sa.dbMap, req)
test.AssertNotError(t, err, "countNewOrders failed")
test.AssertEquals(t, count.Count, int64(45))
}

102
sa/sa.go
View File

@ -107,7 +107,7 @@ func (ssa *SQLStorageAuthority) NewRegistration(ctx context.Context, req *corepb
reg.CreatedAt = ssa.clk.Now()
err = ssa.dbMap.WithContext(ctx).Insert(reg)
err = ssa.dbMap.Insert(ctx, reg)
if err != nil {
if db.IsDuplicate(err) {
// duplicate entry error can only happen when jwk_sha256 collides, indicate
@ -125,7 +125,7 @@ func (ssa *SQLStorageAuthority) UpdateRegistration(ctx context.Context, req *cor
return nil, errIncompleteRequest
}
curr, err := selectRegistration(ssa.dbMap.WithContext(ctx), "id", req.Id)
curr, err := selectRegistration(ctx, ssa.dbMap, "id", req.Id)
if err != nil {
if db.IsNoRows(err) {
return nil, berrors.NotFoundError("registration with ID '%d' not found", req.Id)
@ -141,7 +141,7 @@ func (ssa *SQLStorageAuthority) UpdateRegistration(ctx context.Context, req *cor
// Copy the existing registration model's LockCol to the new updated
// registration model's LockCol
update.LockCol = curr.LockCol
n, err := ssa.dbMap.WithContext(ctx).Update(update)
n, err := ssa.dbMap.Update(ctx, update)
if err != nil {
if db.IsDuplicate(err) {
// duplicate entry error can only happen when jwk_sha256 collides, indicate
@ -162,7 +162,7 @@ func (ssa *SQLStorageAuthority) AddSerial(ctx context.Context, req *sapb.AddSeri
if req.Serial == "" || req.RegID == 0 || req.Created == 0 || req.Expires == 0 {
return nil, errIncompleteRequest
}
err := ssa.dbMap.WithContext(ctx).Insert(&recordedSerialModel{
err := ssa.dbMap.Insert(ctx, &recordedSerialModel{
Serial: req.Serial,
RegistrationID: req.RegID,
Created: time.Unix(0, req.Created),
@ -177,7 +177,7 @@ func (ssa *SQLStorageAuthority) AddSerial(ctx context.Context, req *sapb.AddSeri
// SetCertificateStatusReady changes a serial's OCSP status from core.OCSPStatusNotReady to core.OCSPStatusGood.
// Called when precertificate issuance succeeds. returns an error if the serial doesn't have status core.OCSPStatusNotReady.
func (ssa *SQLStorageAuthority) SetCertificateStatusReady(ctx context.Context, req *sapb.Serial) (*emptypb.Empty, error) {
res, err := ssa.dbMap.WithContext(ctx).Exec(
res, err := ssa.dbMap.ExecContext(ctx,
`UPDATE certificateStatus
SET status = ?
WHERE status = ? AND
@ -222,12 +222,12 @@ func (ssa *SQLStorageAuthority) AddPrecertificate(ctx context.Context, req *sapb
Expires: parsed.NotAfter,
}
_, overallError := db.WithTransaction(ctx, ssa.dbMap, func(txWithCtx db.Executor) (interface{}, error) {
_, overallError := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (interface{}, error) {
// Select to see if precert exists
var row struct {
Count int64
}
err := txWithCtx.SelectOne(&row, "SELECT COUNT(*) as count FROM precertificates WHERE serial=?", serialHex)
err := tx.SelectOne(ctx, &row, "SELECT COUNT(*) as count FROM precertificates WHERE serial=?", serialHex)
if err != nil {
return nil, err
}
@ -235,7 +235,7 @@ func (ssa *SQLStorageAuthority) AddPrecertificate(ctx context.Context, req *sapb
return nil, berrors.DuplicateError("cannot add a duplicate cert")
}
err = txWithCtx.Insert(preCertModel)
err = tx.Insert(ctx, preCertModel)
if err != nil {
return nil, err
}
@ -255,7 +255,7 @@ func (ssa *SQLStorageAuthority) AddPrecertificate(ctx context.Context, req *sapb
IsExpired: false,
IssuerNameID: req.IssuerNameID,
}
err = ssa.dbMap.WithContext(ctx).Insert(cs)
err = ssa.dbMap.Insert(ctx, cs)
if err != nil {
return nil, err
}
@ -267,18 +267,19 @@ func (ssa *SQLStorageAuthority) AddPrecertificate(ctx context.Context, req *sapb
// would be a misissuance and miscalculating whether the cert is a renewal or
// not for the purpose of rate limiting is the least of our troubles.
isRenewal, err := ssa.checkFQDNSetExists(
txWithCtx.SelectOne,
ctx,
tx.SelectOne,
parsed.DNSNames)
if err != nil {
return nil, err
}
err = addIssuedNames(txWithCtx, parsed, isRenewal)
err = addIssuedNames(ctx, tx, parsed, isRenewal)
if err != nil {
return nil, err
}
err = addKeyHash(txWithCtx, parsed)
err = addKeyHash(ctx, tx, parsed)
if err != nil {
return nil, err
}
@ -314,12 +315,12 @@ func (ssa *SQLStorageAuthority) AddCertificate(ctx context.Context, req *sapb.Ad
Expires: parsedCertificate.NotAfter,
}
isRenewalRaw, overallError := db.WithTransaction(ctx, ssa.dbMap, func(txWithCtx db.Executor) (interface{}, error) {
isRenewalRaw, overallError := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (interface{}, error) {
// Select to see if cert exists
var row struct {
Count int64
}
err := txWithCtx.SelectOne(&row, "SELECT COUNT(*) as count FROM certificates WHERE serial=?", serial)
err := tx.SelectOne(ctx, &row, "SELECT COUNT(*) as count FROM certificates WHERE serial=?", serial)
if err != nil {
return nil, err
}
@ -328,7 +329,7 @@ func (ssa *SQLStorageAuthority) AddCertificate(ctx context.Context, req *sapb.Ad
}
// Save the final certificate
err = txWithCtx.Insert(cert)
err = tx.Insert(ctx, cert)
if err != nil {
return nil, err
}
@ -340,7 +341,8 @@ func (ssa *SQLStorageAuthority) AddCertificate(ctx context.Context, req *sapb.Ad
// would be a misissuance and miscalculating whether the cert is a renewal or
// not for the purpose of rate limiting is the least of our troubles.
isRenewal, err := ssa.checkFQDNSetExists(
txWithCtx.SelectOne,
ctx,
tx.SelectOne,
parsedCertificate.DNSNames)
if err != nil {
return nil, err
@ -367,12 +369,12 @@ func (ssa *SQLStorageAuthority) AddCertificate(ctx context.Context, req *sapb.Ad
// for rate limits. Since the effects of failing these writes is slight
// miscalculation of rate limits we choose to not fail the AddCertificate
// operation if the rate limit update transaction fails.
_, rlTransactionErr := db.WithTransaction(ctx, ssa.dbMap, func(txWithCtx db.Executor) (interface{}, error) {
_, rlTransactionErr := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (interface{}, error) {
// Add to the rate limit table, but only for new certificates. Renewals
// don't count against the certificatesPerName limit.
if !isRenewal {
timeToTheHour := parsedCertificate.NotBefore.Round(time.Hour)
err := ssa.addCertificatesPerName(txWithCtx, parsedCertificate.DNSNames, timeToTheHour)
err := ssa.addCertificatesPerName(ctx, tx, parsedCertificate.DNSNames, timeToTheHour)
if err != nil {
return nil, err
}
@ -381,7 +383,8 @@ func (ssa *SQLStorageAuthority) AddCertificate(ctx context.Context, req *sapb.Ad
// Update the FQDN sets now that there is a final certificate to ensure rate
// limits are calculated correctly.
err = addFQDNSet(
txWithCtx,
ctx,
tx,
parsedCertificate.DNSNames,
core.SerialToString(parsedCertificate.SerialNumber),
parsedCertificate.NotBefore,
@ -408,7 +411,7 @@ func (ssa *SQLStorageAuthority) DeactivateRegistration(ctx context.Context, req
if req == nil || req.Id == 0 {
return nil, errIncompleteRequest
}
_, err := ssa.dbMap.WithContext(ctx).Exec(
_, err := ssa.dbMap.ExecContext(ctx,
"UPDATE registrations SET status = ? WHERE status = ? AND id = ?",
string(core.StatusDeactivated),
string(core.StatusValid),
@ -426,7 +429,7 @@ func (ssa *SQLStorageAuthority) DeactivateAuthorization2(ctx context.Context, re
return nil, errIncompleteRequest
}
_, err := ssa.dbMap.Exec(
_, err := ssa.dbMap.ExecContext(ctx,
`UPDATE authz2 SET status = :deactivated WHERE id = :id and status IN (:valid,:pending)`,
map[string]interface{}{
"deactivated": statusUint(core.StatusDeactivated),
@ -451,7 +454,7 @@ func (ssa *SQLStorageAuthority) NewOrderAndAuthzs(ctx context.Context, req *sapb
return nil, errIncompleteRequest
}
output, err := db.WithTransaction(ctx, ssa.dbMap, func(txWithCtx db.Executor) (interface{}, error) {
output, err := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (interface{}, error) {
// First, insert all of the new authorizations and record their IDs.
newAuthzIDs := make([]int64, 0)
if len(req.NewAuthzs) != 0 {
@ -485,7 +488,7 @@ func (ssa *SQLStorageAuthority) NewOrderAndAuthzs(ctx context.Context, req *sapb
return nil, err
}
}
newAuthzIDs, err = inserter.Insert(txWithCtx)
newAuthzIDs, err = inserter.Insert(ctx, tx)
if err != nil {
return nil, err
}
@ -497,7 +500,7 @@ func (ssa *SQLStorageAuthority) NewOrderAndAuthzs(ctx context.Context, req *sapb
Expires: time.Unix(0, req.NewOrder.Expires),
Created: ssa.clk.Now(),
}
err := txWithCtx.Insert(order)
err := tx.Insert(ctx, order)
if err != nil {
return nil, err
}
@ -519,7 +522,7 @@ func (ssa *SQLStorageAuthority) NewOrderAndAuthzs(ctx context.Context, req *sapb
return nil, err
}
}
_, err = inserter.Insert(txWithCtx)
_, err = inserter.Insert(ctx, tx)
if err != nil {
return nil, err
}
@ -535,13 +538,13 @@ func (ssa *SQLStorageAuthority) NewOrderAndAuthzs(ctx context.Context, req *sapb
return nil, err
}
}
_, err = inserter.Insert(txWithCtx)
_, err = inserter.Insert(ctx, tx)
if err != nil {
return nil, err
}
// Fifth, insert the FQDNSet entry for the order.
err = addOrderFQDNSet(txWithCtx, req.NewOrder.Names, order.ID, order.RegistrationID, order.Expires)
err = addOrderFQDNSet(ctx, tx, req.NewOrder.Names, order.ID, order.RegistrationID, order.Expires)
if err != nil {
return nil, err
}
@ -563,7 +566,7 @@ func (ssa *SQLStorageAuthority) NewOrderAndAuthzs(ctx context.Context, req *sapb
// Calculate the order status before returning it. Since it may have reused
// all valid authorizations the order may be "born" in a ready status.
status, err := statusForOrder(txWithCtx, res, ssa.clk.Now())
status, err := statusForOrder(ctx, tx, res, ssa.clk.Now())
if err != nil {
return nil, err
}
@ -581,7 +584,7 @@ func (ssa *SQLStorageAuthority) NewOrderAndAuthzs(ctx context.Context, req *sapb
}
// Increment the order creation count
err = addNewOrdersRateLimit(ssa.dbMap.WithContext(ctx), req.NewOrder.RegistrationID, ssa.clk.Now().Truncate(time.Minute))
err = addNewOrdersRateLimit(ctx, ssa.dbMap, req.NewOrder.RegistrationID, ssa.clk.Now().Truncate(time.Minute))
if err != nil {
return nil, err
}
@ -596,8 +599,8 @@ func (ssa *SQLStorageAuthority) SetOrderProcessing(ctx context.Context, req *sap
if req.Id == 0 {
return nil, errIncompleteRequest
}
_, overallError := db.WithTransaction(ctx, ssa.dbMap, func(txWithCtx db.Executor) (interface{}, error) {
result, err := txWithCtx.Exec(`
_, overallError := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (interface{}, error) {
result, err := tx.ExecContext(ctx, `
UPDATE orders
SET beganProcessing = ?
WHERE id = ?
@ -627,7 +630,7 @@ func (ssa *SQLStorageAuthority) SetOrderError(ctx context.Context, req *sapb.Set
if req.Id == 0 || req.Error == nil {
return nil, errIncompleteRequest
}
_, overallError := db.WithTransaction(ctx, ssa.dbMap, func(txWithCtx db.Executor) (interface{}, error) {
_, overallError := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (interface{}, error) {
om, err := orderToModel(&corepb.Order{
Id: req.Id,
Error: req.Error,
@ -636,7 +639,7 @@ func (ssa *SQLStorageAuthority) SetOrderError(ctx context.Context, req *sapb.Set
return nil, err
}
result, err := txWithCtx.Exec(`
result, err := tx.ExecContext(ctx, `
UPDATE orders
SET error = ?
WHERE id = ?`,
@ -667,8 +670,8 @@ func (ssa *SQLStorageAuthority) FinalizeOrder(ctx context.Context, req *sapb.Fin
if req.Id == 0 || req.CertificateSerial == "" {
return nil, errIncompleteRequest
}
_, overallError := db.WithTransaction(ctx, ssa.dbMap, func(txWithCtx db.Executor) (interface{}, error) {
result, err := txWithCtx.Exec(`
_, overallError := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (interface{}, error) {
result, err := tx.ExecContext(ctx, `
UPDATE orders
SET certificateSerial = ?
WHERE id = ? AND
@ -686,7 +689,7 @@ func (ssa *SQLStorageAuthority) FinalizeOrder(ctx context.Context, req *sapb.Fin
// Delete the orderFQDNSet row for the order now that it has been finalized.
// We use this table for order reuse and should not reuse a finalized order.
err = deleteOrderFQDNSet(txWithCtx, req.Id)
err = deleteOrderFQDNSet(ctx, tx, req.Id)
if err != nil {
return nil, err
}
@ -770,7 +773,7 @@ func (ssa *SQLStorageAuthority) FinalizeAuthorization2(ctx context.Context, req
"validationError": veJSON,
}
res, err := ssa.dbMap.Exec(query, params)
res, err := ssa.dbMap.ExecContext(ctx, query, params)
if err != nil {
return nil, err
}
@ -795,7 +798,7 @@ func (ssa *SQLStorageAuthority) RevokeCertificate(ctx context.Context, req *sapb
revokedDate := time.Unix(0, req.Date)
res, err := ssa.dbMap.Exec(
res, err := ssa.dbMap.ExecContext(ctx,
`UPDATE certificateStatus SET
status = ?,
revokedReason = ?,
@ -838,7 +841,7 @@ func (ssa *SQLStorageAuthority) UpdateRevokedCertificate(ctx context.Context, re
thisUpdate := time.Unix(0, req.Date)
revokedDate := time.Unix(0, req.Backdate)
res, err := ssa.dbMap.Exec(
res, err := ssa.dbMap.ExecContext(ctx,
`UPDATE certificateStatus SET
revokedReason = ?,
ocspLastUpdated = ?
@ -887,7 +890,7 @@ func (ssa *SQLStorageAuthority) AddBlockedKey(ctx context.Context, req *sapb.Add
qs += ", ?"
vals = append(vals, req.RevokedBy)
}
_, err := ssa.dbMap.Exec(
_, err := ssa.dbMap.ExecContext(ctx,
fmt.Sprintf("INSERT INTO blockedKeys (%s) VALUES (%s)", cols, qs),
vals...,
)
@ -904,7 +907,7 @@ func (ssa *SQLStorageAuthority) AddBlockedKey(ctx context.Context, req *sapb.Add
// Health implements the grpc.checker interface.
func (ssa *SQLStorageAuthority) Health(ctx context.Context) error {
err := ssa.dbMap.WithContext(ctx).SelectOne(new(int), "SELECT 1")
err := ssa.dbMap.SelectOne(ctx, new(int), "SELECT 1")
if err != nil {
return err
}
@ -939,9 +942,10 @@ func (ssa *SQLStorageAuthority) LeaseCRLShard(ctx context.Context, req *sapb.Lea
// leased or are previously-unknown indices are considered older than any other
// shard. It returns an error if all shards for the issuer are already leased.
func (ssa *SQLStorageAuthority) leaseOldestCRLShard(ctx context.Context, req *sapb.LeaseCRLShardRequest) (*sapb.LeaseCRLShardResponse, error) {
shardIdx, err := db.WithTransaction(ctx, ssa.dbMap, func(txWithCtx db.Executor) (interface{}, error) {
shardIdx, err := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (interface{}, error) {
var shards []*crlShardModel
_, err := txWithCtx.Select(
_, err := tx.Select(
ctx,
&shards,
`SELECT id, issuerID, idx, thisUpdate, nextUpdate, leasedUntil
FROM crlShards
@ -989,7 +993,7 @@ func (ssa *SQLStorageAuthority) leaseOldestCRLShard(ctx context.Context, req *sa
}
if needToInsert {
_, err = txWithCtx.Exec(
_, err = tx.ExecContext(ctx,
`INSERT INTO crlShards (issuerID, idx, leasedUntil)
VALUES (?, ?, ?)`,
req.IssuerNameID,
@ -997,7 +1001,7 @@ func (ssa *SQLStorageAuthority) leaseOldestCRLShard(ctx context.Context, req *sa
req.Until.AsTime(),
)
} else {
_, err = txWithCtx.Exec(
_, err = tx.ExecContext(ctx,
`UPDATE crlShards
SET leasedUntil = ?
WHERE issuerID = ?
@ -1032,8 +1036,8 @@ func (ssa *SQLStorageAuthority) leaseSpecificCRLShard(ctx context.Context, req *
return nil, fmt.Errorf("request must identify a single shard index: %d != %d", req.MinShardIdx, req.MaxShardIdx)
}
_, err := db.WithTransaction(ctx, ssa.dbMap, func(txWithCtx db.Executor) (interface{}, error) {
res, err := txWithCtx.Exec(
_, err := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (interface{}, error) {
res, err := tx.ExecContext(ctx,
`UPDATE crlShards
SET leasedUntil = ?
WHERE issuerID = ?
@ -1084,8 +1088,8 @@ func (ssa *SQLStorageAuthority) UpdateCRLShard(ctx context.Context, req *sapb.Up
return nil, errIncompleteRequest
}
_, err := db.WithTransaction(ctx, ssa.dbMap, func(txWithCtx db.Executor) (interface{}, error) {
res, err := txWithCtx.Exec(
_, err := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (interface{}, error) {
res, err := tx.ExecContext(ctx,
`UPDATE crlShards
SET thisUpdate = ?, nextUpdate = ?
WHERE issuerID = ?

View File

@ -127,7 +127,7 @@ func createPendingAuthorization(t *testing.T, sa *SQLStorageAuthority, domain st
Token: token,
}
err = sa.dbMap.Insert(&am)
err = sa.dbMap.Insert(context.Background(), &am)
test.AssertNotError(t, err, "creating test authorization")
return am.ID
@ -238,7 +238,6 @@ func TestSelectRegistration(t *testing.T) {
sa, _, cleanUp := initSA(t)
defer cleanUp()
var ctx = context.Background()
var ssaCtx = sa.dbMap.WithContext(ctx)
jwk := goodTestJWK()
jwkJSON, _ := jwk.MarshalJSON()
sha, err := core.KeyDigestB64(jwk.Key)
@ -253,11 +252,11 @@ func TestSelectRegistration(t *testing.T) {
test.AssertNotError(t, err, fmt.Sprintf("couldn't create new registration: %s", err))
test.Assert(t, reg.Id != 0, "ID shouldn't be 0")
_, err = selectRegistration(ssaCtx, "id", reg.Id)
_, err = selectRegistration(ctx, sa.dbMap, "id", reg.Id)
test.AssertNotError(t, err, "selecting by id should work")
_, err = selectRegistration(ssaCtx, "jwk_sha256", sha)
_, err = selectRegistration(ctx, sa.dbMap, "jwk_sha256", sha)
test.AssertNotError(t, err, "selecting by jwk_sha256 should work")
_, err = selectRegistration(ssaCtx, "initialIP", reg.Id)
_, err = selectRegistration(ctx, sa.dbMap, "initialIP", reg.Id)
test.AssertError(t, err, "selecting by any other column should not work")
}
@ -305,9 +304,10 @@ func TestReplicationLagRetries(t *testing.T) {
// findIssuedName is a small helper test function to directly query the
// issuedNames table for a given name to find a serial (or return an err).
func findIssuedName(dbMap db.OneSelector, name string) (string, error) {
func findIssuedName(ctx context.Context, dbMap db.OneSelector, name string) (string, error) {
var issuedNamesSerial string
err := dbMap.SelectOne(
ctx,
&issuedNamesSerial,
`SELECT serial FROM issuedNames
WHERE reversedName = ?
@ -389,6 +389,7 @@ func TestGetSerialMetadata(t *testing.T) {
}
func TestAddPrecertificate(t *testing.T) {
ctx := context.Background()
sa, clk, cleanUp := initSA(t)
defer cleanUp()
@ -416,7 +417,7 @@ func TestAddPrecertificate(t *testing.T) {
test.AssertEquals(t, clk.Now().UnixNano(), certStatus.OcspLastUpdated)
// It should show up in the issued names table
issuedNamesSerial, err := findIssuedName(sa.dbMap, testCert.DNSNames[0])
issuedNamesSerial, err := findIssuedName(ctx, sa.dbMap, testCert.DNSNames[0])
test.AssertNotError(t, err, "expected no err querying issuedNames for precert")
test.AssertEquals(t, issuedNamesSerial, serial)
@ -511,7 +512,7 @@ func TestAddPrecertificateKeyHash(t *testing.T) {
test.AssertNotError(t, err, "failed to add precert")
var keyHashes []keyHashModel
_, err = sa.dbMap.Select(&keyHashes, "SELECT * FROM keyHashToSerial")
_, err = sa.dbMap.Select(context.Background(), &keyHashes, "SELECT * FROM keyHashToSerial")
test.AssertNotError(t, err, "failed to retrieve rows from keyHashToSerial")
test.AssertEquals(t, len(keyHashes), 1)
test.AssertEquals(t, keyHashes[0].CertSerial, serial)
@ -669,10 +670,10 @@ func TestCountCertificatesByNames(t *testing.T) {
interlocker.Add(len(names))
sa.parallelismPerRPC = len(names)
oldCertCountFunc := sa.countCertificatesByName
sa.countCertificatesByName = func(sel db.Selector, domain string, timeRange *sapb.Range) (int64, time.Time, error) {
sa.countCertificatesByName = func(ctx context.Context, sel db.Selector, domain string, timeRange *sapb.Range) (int64, time.Time, error) {
interlocker.Done()
interlocker.Wait()
return oldCertCountFunc(sel, domain, timeRange)
return oldCertCountFunc(ctx, sel, domain, timeRange)
}
certDER2, err := os.ReadFile("test-cert2.der")
@ -849,15 +850,16 @@ func TestCountRegistrationsByIPRange(t *testing.T) {
}
func TestFQDNSets(t *testing.T) {
ctx := context.Background()
sa, fc, cleanUp := initSA(t)
defer cleanUp()
tx, err := sa.dbMap.Begin()
tx, err := sa.dbMap.BeginTx(ctx)
test.AssertNotError(t, err, "Failed to open transaction")
names := []string{"a.example.com", "B.example.com"}
expires := fc.Now().Add(time.Hour * 2).UTC()
issued := fc.Now()
err = addFQDNSet(tx, names, "serial", issued, expires)
err = addFQDNSet(ctx, tx, names, "serial", issued, expires)
test.AssertNotError(t, err, "Failed to add name set")
test.AssertNotError(t, tx.Commit(), "Failed to commit transaction")
@ -878,9 +880,9 @@ func TestFQDNSets(t *testing.T) {
test.AssertEquals(t, count.Count, int64(1))
// add another valid set
tx, err = sa.dbMap.Begin()
tx, err = sa.dbMap.BeginTx(ctx)
test.AssertNotError(t, err, "Failed to open transaction")
err = addFQDNSet(tx, names, "anotherSerial", issued, expires)
err = addFQDNSet(ctx, tx, names, "anotherSerial", issued, expires)
test.AssertNotError(t, err, "Failed to add name set")
test.AssertNotError(t, tx.Commit(), "Failed to commit transaction")
@ -891,9 +893,10 @@ func TestFQDNSets(t *testing.T) {
test.AssertEquals(t, count.Count, int64(2))
// add an expired set
tx, err = sa.dbMap.Begin()
tx, err = sa.dbMap.BeginTx(ctx)
test.AssertNotError(t, err, "Failed to open transaction")
err = addFQDNSet(
ctx,
tx,
names,
"yetAnotherSerial",
@ -913,7 +916,7 @@ func TestFQDNSetTimestampsForWindow(t *testing.T) {
sa, fc, cleanUp := initSA(t)
defer cleanUp()
tx, err := sa.dbMap.Begin()
tx, err := sa.dbMap.BeginTx(ctx)
test.AssertNotError(t, err, "Failed to open transaction")
names := []string{"a.example.com", "B.example.com"}
@ -931,7 +934,7 @@ func TestFQDNSetTimestampsForWindow(t *testing.T) {
// Add an issuance for names inside the window.
expires := fc.Now().Add(time.Hour * 2).UTC()
firstIssued := fc.Now()
err = addFQDNSet(tx, names, "serial", firstIssued, expires)
err = addFQDNSet(ctx, tx, names, "serial", firstIssued, expires)
test.AssertNotError(t, err, "Failed to add name set")
test.AssertNotError(t, tx.Commit(), "Failed to commit transaction")
@ -949,9 +952,9 @@ func TestFQDNSetTimestampsForWindow(t *testing.T) {
test.AssertEquals(t, firstIssued, time.Unix(0, resp.Timestamps[len(resp.Timestamps)-1]).UTC())
// Add another issuance for names inside the window.
tx, err = sa.dbMap.Begin()
tx, err = sa.dbMap.BeginTx(ctx)
test.AssertNotError(t, err, "Failed to open transaction")
err = addFQDNSet(tx, names, "anotherSerial", firstIssued, expires)
err = addFQDNSet(ctx, tx, names, "anotherSerial", firstIssued, expires)
test.AssertNotError(t, err, "Failed to add name set")
test.AssertNotError(t, tx.Commit(), "Failed to commit transaction")
@ -963,9 +966,9 @@ func TestFQDNSetTimestampsForWindow(t *testing.T) {
test.AssertEquals(t, firstIssued, time.Unix(0, resp.Timestamps[len(resp.Timestamps)-1]).UTC())
// Add another issuance for names but just outside the window.
tx, err = sa.dbMap.Begin()
tx, err = sa.dbMap.BeginTx(ctx)
test.AssertNotError(t, err, "Failed to open transaction")
err = addFQDNSet(tx, names, "yetAnotherSerial", firstIssued.Add(-window), expires)
err = addFQDNSet(ctx, tx, names, "yetAnotherSerial", firstIssued.Add(-window), expires)
test.AssertNotError(t, err, "Failed to add name set")
test.AssertNotError(t, tx.Commit(), "Failed to commit transaction")
@ -985,11 +988,11 @@ func TestFQDNSetsExists(t *testing.T) {
test.AssertNotError(t, err, "Failed to check FQDN set existence")
test.Assert(t, !exists.Exists, "FQDN set shouldn't exist")
tx, err := sa.dbMap.Begin()
tx, err := sa.dbMap.BeginTx(ctx)
test.AssertNotError(t, err, "Failed to open transaction")
expires := fc.Now().Add(time.Hour * 2).UTC()
issued := fc.Now()
err = addFQDNSet(tx, names, "serial", issued, expires)
err = addFQDNSet(ctx, tx, names, "serial", issued, expires)
test.AssertNotError(t, err, "Failed to add name set")
test.AssertNotError(t, tx.Commit(), "Failed to commit transaction")
@ -1003,7 +1006,7 @@ type queryRecorder struct {
args []interface{}
}
func (e *queryRecorder) Query(query string, args ...interface{}) (*sql.Rows, error) {
func (e *queryRecorder) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
e.query = query
e.args = args
return nil, nil
@ -1090,6 +1093,7 @@ func TestAddIssuedNames(t *testing.T) {
t.Run(tc.Name, func(t *testing.T) {
var e queryRecorder
err := addIssuedNames(
ctx,
&e,
&x509.Certificate{
DNSNames: tc.IssuedNames,
@ -1262,12 +1266,12 @@ func TestNewOrderAndAuthzs(t *testing.T) {
test.AssertDeepEquals(t, order.V2Authorizations, []int64{1, 2, 3, 4})
var authzIDs []int64
_, err = sa.dbMap.Select(&authzIDs, "SELECT authzID FROM orderToAuthz2 WHERE orderID = ?;", order.Id)
_, err = sa.dbMap.Select(ctx, &authzIDs, "SELECT authzID FROM orderToAuthz2 WHERE orderID = ?;", order.Id)
test.AssertNotError(t, err, "Failed to count orderToAuthz entries")
test.AssertEquals(t, len(authzIDs), 4)
test.AssertDeepEquals(t, authzIDs, []int64{1, 2, 3, 4})
names, err := namesForOrder(sa.dbReadOnlyMap, order.Id)
names, err := namesForOrder(ctx, sa.dbReadOnlyMap, order.Id)
test.AssertNotError(t, err, "namesForOrder errored")
test.AssertEquals(t, len(names), 4)
test.AssertDeepEquals(t, names, []string{"com.a", "com.b", "com.c", "com.d"})
@ -1343,7 +1347,7 @@ func TestNewOrderAndAuthzs_NewAuthzExpectedFields(t *testing.T) {
test.AssertNotError(t, err, "sa.NewOrderAndAuthzs failed")
// Safely get the authz for the order we created above.
obj, err := sa.dbReadOnlyMap.Get(authzModel{}, order.V2Authorizations[0])
obj, err := sa.dbReadOnlyMap.Get(ctx, authzModel{}, order.V2Authorizations[0])
test.AssertNotError(t, err, fmt.Sprintf("authorization %d not found", order.V2Authorizations[0]))
// To access the data stored in obj at compile time, we type assert obj
@ -1559,17 +1563,17 @@ func TestGetAuthorizations2(t *testing.T) {
// Associate authorizations with an order so that GetAuthorizations2 thinks
// they are WFE2 authorizations.
err := sa.dbMap.Insert(&orderToAuthzModel{
err := sa.dbMap.Insert(ctx, &orderToAuthzModel{
OrderID: 1,
AuthzID: authzIDA,
})
test.AssertNotError(t, err, "sa.dbMap.Insert failed")
err = sa.dbMap.Insert(&orderToAuthzModel{
err = sa.dbMap.Insert(ctx, &orderToAuthzModel{
OrderID: 1,
AuthzID: authzIDB,
})
test.AssertNotError(t, err, "sa.dbMap.Insert failed")
err = sa.dbMap.Insert(&orderToAuthzModel{
err = sa.dbMap.Insert(ctx, &orderToAuthzModel{
OrderID: 1,
AuthzID: authzIDC,
})
@ -2176,9 +2180,9 @@ func TestAddCertificateRenewalBit(t *testing.T) {
serial := "thrilla"
// Add a FQDN set for the names so that it will be considered a renewal
tx, err := sa.dbMap.Begin()
tx, err := sa.dbMap.BeginTx(ctx)
test.AssertNotError(t, err, "Failed to open transaction")
err = addFQDNSet(tx, names, serial, issued, expires)
err = addFQDNSet(ctx, tx, names, serial, issued, expires)
test.AssertNotError(t, err, "Failed to add name set")
test.AssertNotError(t, tx.Commit(), "Failed to commit transaction")
@ -2201,6 +2205,7 @@ func TestAddCertificateRenewalBit(t *testing.T) {
t.Helper()
var count int
err := sa.dbMap.SelectOne(
ctx,
&count,
`SELECT COUNT(*) FROM issuedNames
WHERE reversedName = ?
@ -2929,7 +2934,7 @@ func TestIncidentsForSerial(t *testing.T) {
weekAgo := sa.clk.Now().Add(-time.Hour * 24 * 7)
// Add a disabled incident.
err = testSADbMap.Insert(&incidentModel{
err = testSADbMap.Insert(ctx, &incidentModel{
SerialTable: "incident_foo",
URL: "https://example.com/foo-incident",
RenewBy: sa.clk.Now().Add(time.Hour * 24 * 7),
@ -2943,7 +2948,7 @@ func TestIncidentsForSerial(t *testing.T) {
test.AssertEquals(t, len(result.Incidents), 0)
// Add an enabled incident.
err = testSADbMap.Insert(&incidentModel{
err = testSADbMap.Insert(ctx, &incidentModel{
SerialTable: "incident_bar",
URL: "https://example.com/test-incident",
RenewBy: sa.clk.Now().Add(time.Hour * 24 * 7),
@ -2959,7 +2964,7 @@ func TestIncidentsForSerial(t *testing.T) {
OrderID: &one,
LastNoticeSent: &weekAgo,
}
_, err = testIncidentsDbMap.Exec(
_, err = testIncidentsDbMap.ExecContext(ctx,
fmt.Sprintf("INSERT INTO incident_bar (%s) VALUES ('%s', %d, %d, '%s')",
"serial, registrationID, orderID, lastNoticeSent",
affectedCertA.Serial,
@ -2983,7 +2988,7 @@ func TestIncidentsForSerial(t *testing.T) {
OrderID: &two,
LastNoticeSent: &weekAgo,
}
_, err = testIncidentsDbMap.Exec(
_, err = testIncidentsDbMap.ExecContext(ctx,
fmt.Sprintf("INSERT INTO incident_bar (%s) VALUES ('%s', %d, %d, '%s')",
"serial, registrationID, orderID, lastNoticeSent",
affectedCertB.Serial,
@ -3089,7 +3094,7 @@ func TestSerialsForIncident(t *testing.T) {
}
for i := range expectedSerials {
randInt := func() int64 { return mrand.Int63() }
_, err := testIncidentsDbMap.Exec(
_, err := testIncidentsDbMap.ExecContext(ctx,
fmt.Sprintf("INSERT INTO incident_foo (%s) VALUES ('%s', %d, %d, '%s')",
"serial, registrationID, orderID, lastNoticeSent",
i,
@ -3268,7 +3273,7 @@ func TestLeaseOldestCRLShard(t *testing.T) {
// Create 8 shards: 4 for each of 2 issuers. For each issuer, one shard is
// currently leased, three are available, and one of those failed to update.
_, err := sa.dbMap.Exec(
_, err := sa.dbMap.ExecContext(ctx,
`INSERT INTO crlShards (issuerID, idx, thisUpdate, nextUpdate, leasedUntil) VALUES
(1, 0, ?, ?, ?),
(1, 1, ?, ?, ?),
@ -3321,6 +3326,7 @@ func TestLeaseOldestCRLShard(t *testing.T) {
test.AssertEquals(t, res.ShardIdx, int64(3))
err = sa.dbMap.SelectOne(
ctx,
&untilModel,
`SELECT leasedUntil FROM crlShards WHERE issuerID = ? AND idx = ? LIMIT 1`,
res.IssuerNameID,
@ -3344,6 +3350,7 @@ func TestLeaseOldestCRLShard(t *testing.T) {
test.AssertEquals(t, res.ShardIdx, int64(1))
err = sa.dbMap.SelectOne(
ctx,
&untilModel,
`SELECT leasedUntil FROM crlShards WHERE issuerID = ? AND idx = ? LIMIT 1`,
res.IssuerNameID,
@ -3369,6 +3376,7 @@ func TestLeaseOldestCRLShard(t *testing.T) {
test.Assert(t, res.ShardIdx <= 7, "checking leased index")
err = sa.dbMap.SelectOne(
ctx,
&untilModel,
`SELECT leasedUntil FROM crlShards WHERE issuerID = ? AND idx = ? LIMIT 1`,
res.IssuerNameID,
@ -3388,7 +3396,7 @@ func TestLeaseSpecificCRLShard(t *testing.T) {
// Create 8 shards: 4 for each of 2 issuers. For each issuer, one shard is
// currently leased, three are available, and one of those failed to update.
_, err := sa.dbMap.Exec(
_, err := sa.dbMap.ExecContext(ctx,
`INSERT INTO crlShards (issuerID, idx, thisUpdate, nextUpdate, leasedUntil) VALUES
(1, 0, ?, ?, ?),
(1, 1, ?, ?, ?),
@ -3429,6 +3437,7 @@ func TestLeaseSpecificCRLShard(t *testing.T) {
test.AssertEquals(t, res.ShardIdx, int64(1))
err = sa.dbMap.SelectOne(
ctx,
&untilModel,
`SELECT leasedUntil FROM crlShards WHERE issuerID = ? AND idx = ? LIMIT 1`,
res.IssuerNameID,
@ -3452,6 +3461,7 @@ func TestLeaseSpecificCRLShard(t *testing.T) {
test.AssertEquals(t, res.ShardIdx, int64(3))
err = sa.dbMap.SelectOne(
ctx,
&untilModel,
`SELECT leasedUntil FROM crlShards WHERE issuerID = ? AND idx = ? LIMIT 1`,
res.IssuerNameID,
@ -3496,7 +3506,7 @@ func TestUpdateCRLShard(t *testing.T) {
// Create 8 shards: 4 for each of 2 issuers. For each issuer, one shard is
// currently leased, three are available, and one of those failed to update.
_, err := sa.dbMap.Exec(
_, err := sa.dbMap.ExecContext(ctx,
`INSERT INTO crlShards (issuerID, idx, thisUpdate, nextUpdate, leasedUntil) VALUES
(1, 0, ?, ?, ?),
(1, 1, ?, ?, ?),
@ -3535,6 +3545,7 @@ func TestUpdateCRLShard(t *testing.T) {
test.AssertNotError(t, err, "updating leased shard")
err = sa.dbMap.SelectOne(
ctx,
&thisUpdateModel,
`SELECT thisUpdate FROM crlShards WHERE issuerID = ? AND idx = ? LIMIT 1`,
1,
@ -3556,6 +3567,7 @@ func TestUpdateCRLShard(t *testing.T) {
test.AssertNotError(t, err, "updating unleased shard")
err = sa.dbMap.SelectOne(
ctx,
&thisUpdateModel,
`SELECT thisUpdate FROM crlShards WHERE issuerID = ? AND idx = ? LIMIT 1`,
1,

View File

@ -31,7 +31,7 @@ var (
validIncidentTableRegexp = regexp.MustCompile(`^incident_[0-9a-zA-Z_]{1,100}$`)
)
type certCountFunc func(db db.Selector, domain string, timeRange *sapb.Range) (int64, time.Time, error)
type certCountFunc func(ctx context.Context, db db.Selector, domain string, timeRange *sapb.Range) (int64, time.Time, error)
// SQLStorageAuthorityRO defines a read-only subset of a Storage Authority
type SQLStorageAuthorityRO struct {
@ -70,7 +70,7 @@ type SQLStorageAuthorityRO struct {
}
// NewSQLStorageAuthorityRO provides persistence using a SQL backend for
// Boulder. It will modify the given gorp.DbMap by adding relevant tables.
// Boulder. It will modify the given borp.DbMap by adding relevant tables.
func NewSQLStorageAuthorityRO(
dbReadOnlyMap *db.WrappedMap,
dbIncidentsMap *db.WrappedMap,
@ -107,13 +107,13 @@ func (ssa *SQLStorageAuthorityRO) GetRegistration(ctx context.Context, req *sapb
return nil, errIncompleteRequest
}
model, err := selectRegistration(ssa.dbReadOnlyMap.WithContext(ctx), "id", req.Id)
model, err := selectRegistration(ctx, ssa.dbReadOnlyMap, "id", req.Id)
if db.IsNoRows(err) && ssa.lagFactor != 0 {
// GetRegistration is often called to validate a JWK belonging to a brand
// new account whose registrations table row hasn't propagated to the read
// replica yet. If we get a NoRows, wait a little bit and retry, once.
ssa.clk.Sleep(ssa.lagFactor)
model, err = selectRegistration(ssa.dbReadOnlyMap.WithContext(ctx), "id", req.Id)
model, err = selectRegistration(ctx, ssa.dbReadOnlyMap, "id", req.Id)
if err != nil {
if db.IsNoRows(err) {
ssa.lagFactorCounter.WithLabelValues("GetRegistration", "notfound").Inc()
@ -154,7 +154,7 @@ func (ssa *SQLStorageAuthorityRO) GetRegistrationByKey(ctx context.Context, req
if err != nil {
return nil, err
}
model, err := selectRegistration(ssa.dbReadOnlyMap.WithContext(ctx), "jwk_sha256", sha)
model, err := selectRegistration(ctx, ssa.dbReadOnlyMap, "jwk_sha256", sha)
if err != nil {
if db.IsNoRows(err) {
return nil, berrors.NotFoundError("no registrations with public key sha256 %q", sha)
@ -223,7 +223,8 @@ func (ssa *SQLStorageAuthorityRO) CountRegistrationsByIP(ctx context.Context, re
}
var count int64
err := ssa.dbReadOnlyMap.WithContext(ctx).SelectOne(
err := ssa.dbReadOnlyMap.SelectOne(
ctx,
&count,
`SELECT COUNT(*) FROM registrations
WHERE
@ -256,7 +257,8 @@ func (ssa *SQLStorageAuthorityRO) CountRegistrationsByIPRange(ctx context.Contex
var count int64
beginIP, endIP := ipRange(req.Ip)
err := ssa.dbReadOnlyMap.WithContext(ctx).SelectOne(
err := ssa.dbReadOnlyMap.SelectOne(
ctx,
&count,
`SELECT COUNT(*) FROM registrations
WHERE
@ -321,7 +323,7 @@ func (ssa *SQLStorageAuthorityRO) CountCertificatesByNames(ctx context.Context,
return
default:
}
count, earliest, err := ssa.countCertificatesByName(ssa.dbReadOnlyMap.WithContext(ctx), domain, req.Range)
count, earliest, err := ssa.countCertificatesByName(ctx, ssa.dbReadOnlyMap, domain, req.Range)
if err != nil {
results <- result{err: err}
// Skip any further work
@ -386,7 +388,8 @@ func (ssa *SQLStorageAuthorityRO) GetSerialMetadata(ctx context.Context, req *sa
}
recordedSerial := recordedSerialModel{}
err := ssa.dbReadOnlyMap.WithContext(ctx).SelectOne(
err := ssa.dbReadOnlyMap.SelectOne(
ctx,
&recordedSerial,
"SELECT * FROM serials WHERE serial = ?",
req.Serial,
@ -420,7 +423,7 @@ func (ssa *SQLStorageAuthorityRO) GetCertificate(ctx context.Context, req *sapb.
return nil, fmt.Errorf("invalid certificate serial %s", req.Serial)
}
cert, err := SelectCertificate(ssa.dbReadOnlyMap.WithContext(ctx), req.Serial)
cert, err := SelectCertificate(ctx, ssa.dbReadOnlyMap, req.Serial)
if db.IsNoRows(err) {
return nil, berrors.NotFoundError("certificate with serial %q not found", req.Serial)
}
@ -446,7 +449,7 @@ func (ssa *SQLStorageAuthorityRO) GetCertificateStatus(ctx context.Context, req
return nil, err
}
certStatus, err := SelectCertificateStatus(ssa.dbReadOnlyMap.WithContext(ctx), req.Serial)
certStatus, err := SelectCertificateStatus(ctx, ssa.dbReadOnlyMap, req.Serial)
if db.IsNoRows(err) {
return nil, berrors.NotFoundError("certificate status with serial %q not found", req.Serial)
}
@ -472,7 +475,7 @@ func (ssa *SQLStorageAuthorityRO) GetRevocationStatus(ctx context.Context, req *
return nil, fmt.Errorf("invalid certificate serial %s", req.Serial)
}
status, err := SelectRevocationStatus(ssa.dbReadOnlyMap.WithContext(ctx), req.Serial)
status, err := SelectRevocationStatus(ctx, ssa.dbReadOnlyMap, req.Serial)
if err != nil {
if db.IsNoRows(err) {
return nil, berrors.NotFoundError("certificate status with serial %q not found", req.Serial)
@ -492,7 +495,7 @@ func (ssa *SQLStorageAuthorityRO) CountOrders(ctx context.Context, req *sapb.Cou
return nil, errIncompleteRequest
}
return countNewOrders(ssa.dbReadOnlyMap.WithContext(ctx), req)
return countNewOrders(ctx, ssa.dbReadOnlyMap, req)
}
func (ssa *SQLStorageAuthority) CountOrders(ctx context.Context, req *sapb.CountOrdersRequest) (*sapb.Count, error) {
@ -507,7 +510,8 @@ func (ssa *SQLStorageAuthorityRO) CountFQDNSets(ctx context.Context, req *sapb.C
}
var count int64
err := ssa.dbReadOnlyMap.WithContext(ctx).SelectOne(
err := ssa.dbReadOnlyMap.SelectOne(
ctx,
&count,
`SELECT COUNT(*) FROM fqdnSets
WHERE setHash = ?
@ -533,7 +537,8 @@ func (ssa *SQLStorageAuthorityRO) FQDNSetTimestampsForWindow(ctx context.Context
Issued time.Time
}
var rows []row
_, err := ssa.dbReadOnlyMap.WithContext(ctx).Select(
_, err := ssa.dbReadOnlyMap.Select(
ctx,
&rows,
`SELECT issued FROM fqdnSets
WHERE setHash = ?
@ -563,7 +568,7 @@ func (ssa *SQLStorageAuthorityRO) FQDNSetExists(ctx context.Context, req *sapb.F
if len(req.Domains) == 0 {
return nil, errIncompleteRequest
}
exists, err := ssa.checkFQDNSetExists(ssa.dbReadOnlyMap.WithContext(ctx).SelectOne, req.Domains)
exists, err := ssa.checkFQDNSetExists(ctx, ssa.dbReadOnlyMap.SelectOne, req.Domains)
if err != nil {
return nil, err
}
@ -574,16 +579,17 @@ func (ssa *SQLStorageAuthority) FQDNSetExists(ctx context.Context, req *sapb.FQD
return ssa.SQLStorageAuthorityRO.FQDNSetExists(ctx, req)
}
// oneSelectorFunc is a func type that matches both gorp.Transaction.SelectOne
// and gorp.DbMap.SelectOne.
type oneSelectorFunc func(holder interface{}, query string, args ...interface{}) error
// oneSelectorFunc is a func type that matches both borp.Transaction.SelectOne
// and borp.DbMap.SelectOne.
type oneSelectorFunc func(ctx context.Context, holder interface{}, query string, args ...interface{}) error
// checkFQDNSetExists uses the given oneSelectorFunc to check whether an fqdnSet
// for the given names exists.
func (ssa *SQLStorageAuthorityRO) checkFQDNSetExists(selector oneSelectorFunc, names []string) (bool, error) {
func (ssa *SQLStorageAuthorityRO) checkFQDNSetExists(ctx context.Context, selector oneSelectorFunc, names []string) (bool, error) {
namehash := HashNames(names)
var exists bool
err := selector(
ctx,
&exists,
`SELECT EXISTS (SELECT id FROM fqdnSets WHERE setHash = ? LIMIT 1)`,
namehash,
@ -608,7 +614,8 @@ func (ssa *SQLStorageAuthorityRO) PreviousCertificateExists(ctx context.Context,
// Find the most recently issued certificate containing this domain name.
var serial string
err := ssa.dbReadOnlyMap.WithContext(ctx).SelectOne(
err := ssa.dbReadOnlyMap.SelectOne(
ctx,
&serial,
`SELECT serial FROM issuedNames
WHERE reversedName = ?
@ -625,7 +632,8 @@ func (ssa *SQLStorageAuthorityRO) PreviousCertificateExists(ctx context.Context,
// Check whether that certificate was issued to the specified account.
var count int
err = ssa.dbReadOnlyMap.WithContext(ctx).SelectOne(
err = ssa.dbReadOnlyMap.SelectOne(
ctx,
&count,
`SELECT COUNT(*) FROM certificates
WHERE serial = ?
@ -658,8 +666,8 @@ func (ssa *SQLStorageAuthorityRO) GetOrder(ctx context.Context, req *sapb.OrderR
return nil, errIncompleteRequest
}
txn := func(txWithCtx db.Executor) (interface{}, error) {
omObj, err := txWithCtx.Get(orderModel{}, req.Id)
txn := func(tx db.Executor) (interface{}, error) {
omObj, err := tx.Get(ctx, orderModel{}, req.Id)
if err != nil {
if db.IsNoRows(err) {
return nil, berrors.NotFoundError("no order found for ID %d", req.Id)
@ -680,13 +688,13 @@ func (ssa *SQLStorageAuthorityRO) GetOrder(ctx context.Context, req *sapb.OrderR
return nil, berrors.NotFoundError("no order found for ID %d", req.Id)
}
v2AuthzIDs, err := authzForOrder(txWithCtx, order.Id)
v2AuthzIDs, err := authzForOrder(ctx, tx, order.Id)
if err != nil {
return nil, err
}
order.V2Authorizations = v2AuthzIDs
names, err := namesForOrder(txWithCtx, order.Id)
names, err := namesForOrder(ctx, tx, order.Id)
if err != nil {
return nil, err
}
@ -700,7 +708,7 @@ func (ssa *SQLStorageAuthorityRO) GetOrder(ctx context.Context, req *sapb.OrderR
order.Names = reversedNames
// Calculate the status for the order
status, err := statusForOrder(txWithCtx, order, ssa.clk.Now())
status, err := statusForOrder(ctx, tx, order, ssa.clk.Now())
if err != nil {
return nil, err
}
@ -772,7 +780,7 @@ func (ssa *SQLStorageAuthorityRO) GetOrderForNames(ctx context.Context, req *sap
RegistrationID int64
}
var err error
err = ssa.dbReadOnlyMap.WithContext(ctx).SelectOne(&result, `
err = ssa.dbReadOnlyMap.SelectOne(ctx, &result, `
SELECT orderID, registrationID
FROM orderFqdnSets
WHERE setHash = ?
@ -814,13 +822,13 @@ func (ssa *SQLStorageAuthorityRO) GetAuthorization2(ctx context.Context, req *sa
if req.Id == 0 {
return nil, errIncompleteRequest
}
obj, err := ssa.dbReadOnlyMap.Get(authzModel{}, req.Id)
obj, err := ssa.dbReadOnlyMap.Get(ctx, authzModel{}, req.Id)
if db.IsNoRows(err) && ssa.lagFactor != 0 {
// GetAuthorization2 is often called shortly after a new order is created,
// sometimes before the order's associated authz rows have propagated to the
// read replica yet. If we get a NoRows, wait a little bit and retry, once.
ssa.clk.Sleep(ssa.lagFactor)
obj, err = ssa.dbReadOnlyMap.Get(authzModel{}, req.Id)
obj, err = ssa.dbReadOnlyMap.Get(ctx, authzModel{}, req.Id)
if err != nil {
if db.IsNoRows(err) {
ssa.lagFactorCounter.WithLabelValues("GetAuthorization2", "notfound").Inc()
@ -890,6 +898,7 @@ func (ssa *SQLStorageAuthorityRO) GetAuthorizations2(ctx context.Context, req *s
)
_, err := ssa.dbReadOnlyMap.Select(
ctx,
&authzModels,
query,
params...,
@ -925,7 +934,8 @@ func (ssa *SQLStorageAuthorityRO) GetPendingAuthorization2(ctx context.Context,
return nil, errIncompleteRequest
}
var am authzModel
err := ssa.dbReadOnlyMap.WithContext(ctx).SelectOne(
err := ssa.dbReadOnlyMap.SelectOne(
ctx,
&am,
fmt.Sprintf(`SELECT %s FROM authz2 WHERE
registrationID = :regID AND
@ -964,7 +974,7 @@ func (ssa *SQLStorageAuthorityRO) CountPendingAuthorizations2(ctx context.Contex
}
var count int64
err := ssa.dbReadOnlyMap.WithContext(ctx).SelectOne(&count,
err := ssa.dbReadOnlyMap.SelectOne(ctx, &count,
`SELECT COUNT(*) FROM authz2 WHERE
registrationID = :regID AND
expires > :expires AND
@ -1003,7 +1013,8 @@ func (ssa *SQLStorageAuthorityRO) GetValidOrderAuthorizations2(ctx context.Conte
}
var ams []authzModel
_, err := ssa.dbReadOnlyMap.WithContext(ctx).Select(
_, err := ssa.dbReadOnlyMap.Select(
ctx,
&ams,
fmt.Sprintf(`SELECT %s FROM authz2
LEFT JOIN orderToAuthz2 ON authz2.ID = orderToAuthz2.authzID
@ -1050,7 +1061,8 @@ func (ssa *SQLStorageAuthorityRO) CountInvalidAuthorizations2(ctx context.Contex
}
var count int64
err := ssa.dbReadOnlyMap.WithContext(ctx).SelectOne(
err := ssa.dbReadOnlyMap.SelectOne(
ctx,
&count,
`SELECT COUNT(*) FROM authz2 WHERE
registrationID = :regID AND
@ -1109,6 +1121,7 @@ func (ssa *SQLStorageAuthorityRO) GetValidAuthorizations2(ctx context.Context, r
var authzModels []authzModel
_, err := ssa.dbReadOnlyMap.Select(
ctx,
&authzModels,
query,
params...,
@ -1144,7 +1157,7 @@ func (ssa *SQLStorageAuthorityRO) KeyBlocked(ctx context.Context, req *sapb.KeyB
}
var id int64
err := ssa.dbReadOnlyMap.SelectOne(&id, `SELECT ID FROM blockedKeys WHERE keyHash = ?`, req.KeyHash)
err := ssa.dbReadOnlyMap.SelectOne(ctx, &id, `SELECT ID FROM blockedKeys WHERE keyHash = ?`, req.KeyHash)
if err != nil {
if db.IsNoRows(err) {
return &sapb.Exists{Exists: false}, nil
@ -1167,7 +1180,7 @@ func (ssa *SQLStorageAuthorityRO) IncidentsForSerial(ctx context.Context, req *s
}
var activeIncidents []incidentModel
_, err := ssa.dbReadOnlyMap.Select(&activeIncidents, `SELECT * FROM incidents WHERE enabled = 1`)
_, err := ssa.dbReadOnlyMap.Select(ctx, &activeIncidents, `SELECT * FROM incidents WHERE enabled = 1`)
if err != nil {
if db.IsNoRows(err) {
return &sapb.Incidents{}, nil
@ -1178,7 +1191,7 @@ func (ssa *SQLStorageAuthorityRO) IncidentsForSerial(ctx context.Context, req *s
var incidentsForSerial []*sapb.Incident
for _, i := range activeIncidents {
var count int
err := ssa.dbIncidentsMap.SelectOne(&count, fmt.Sprintf("SELECT COUNT(*) FROM %s WHERE serial = ?",
err := ssa.dbIncidentsMap.SelectOne(ctx, &count, fmt.Sprintf("SELECT COUNT(*) FROM %s WHERE serial = ?",
i.SerialTable), req.Serial)
if err != nil {
if db.IsNoRows(err) {
@ -1293,7 +1306,7 @@ func (ssa *SQLStorageAuthorityRO) GetRevokedCerts(req *sapb.GetRevokedCertsReque
return fmt.Errorf("initializing db map: %w", err)
}
rows, err := selector.Query(stream.Context(), clauses, params...)
rows, err := selector.QueryContext(stream.Context(), clauses, params...)
if err != nil {
return fmt.Errorf("reading db: %w", err)
}
@ -1349,7 +1362,8 @@ func (ssa *SQLStorageAuthorityRO) GetMaxExpiration(ctx context.Context, req *emp
var model struct {
MaxNotAfter time.Time `db:"maxNotAfter"`
}
err := ssa.dbReadOnlyMap.WithContext(ctx).SelectOne(
err := ssa.dbReadOnlyMap.SelectOne(
ctx,
&model,
"SELECT MAX(notAfter) AS maxNotAfter FROM certificateStatus",
)
@ -1365,7 +1379,7 @@ func (ssa *SQLStorageAuthority) GetMaxExpiration(ctx context.Context, req *empty
// Health implements the grpc.checker interface.
func (ssa *SQLStorageAuthorityRO) Health(ctx context.Context) error {
err := ssa.dbReadOnlyMap.WithContext(ctx).SelectOne(new(int), "SELECT 1")
err := ssa.dbReadOnlyMap.SelectOne(ctx, new(int), "SELECT 1")
if err != nil {
return err
}

View File

@ -5,14 +5,14 @@ import (
"errors"
"fmt"
"github.com/go-gorp/gorp/v3"
"github.com/letsencrypt/borp"
"gopkg.in/go-jose/go-jose.v2"
"github.com/letsencrypt/boulder/core"
"github.com/letsencrypt/boulder/identifier"
)
// BoulderTypeConverter is used by Gorp for storing objects in DB.
// BoulderTypeConverter is used by borp for storing objects in DB.
type BoulderTypeConverter struct{}
// ToDb converts a Boulder object to one suitable for the DB representation.
@ -40,7 +40,7 @@ func (tc BoulderTypeConverter) ToDb(val interface{}) (interface{}, error) {
}
// FromDb converts a DB representation back into a Boulder object.
func (tc BoulderTypeConverter) FromDb(target interface{}) (gorp.CustomScanner, bool) {
func (tc BoulderTypeConverter) FromDb(target interface{}) (borp.CustomScanner, bool) {
switch target.(type) {
case *identifier.ACMEIdentifier, *[]core.Challenge, *[]string, *[][]int:
binder := func(holder, target interface{}) error {
@ -58,7 +58,7 @@ func (tc BoulderTypeConverter) FromDb(target interface{}) (gorp.CustomScanner, b
}
return nil
}
return gorp.CustomScanner{Holder: new(string), Target: target, Binder: binder}, true
return borp.CustomScanner{Holder: new(string), Target: target, Binder: binder}, true
case *jose.JSONWebKey:
binder := func(holder, target interface{}) error {
s, ok := holder.(*string)
@ -82,7 +82,7 @@ func (tc BoulderTypeConverter) FromDb(target interface{}) (gorp.CustomScanner, b
}
return nil
}
return gorp.CustomScanner{Holder: new(string), Target: target, Binder: binder}, true
return borp.CustomScanner{Holder: new(string), Target: target, Binder: binder}, true
case *core.AcmeStatus:
binder := func(holder, target interface{}) error {
s, ok := holder.(*string)
@ -97,7 +97,7 @@ func (tc BoulderTypeConverter) FromDb(target interface{}) (gorp.CustomScanner, b
*st = core.AcmeStatus(*s)
return nil
}
return gorp.CustomScanner{Holder: new(string), Target: target, Binder: binder}, true
return borp.CustomScanner{Holder: new(string), Target: target, Binder: binder}, true
case *core.OCSPStatus:
binder := func(holder, target interface{}) error {
s, ok := holder.(*string)
@ -112,8 +112,8 @@ func (tc BoulderTypeConverter) FromDb(target interface{}) (gorp.CustomScanner, b
*st = core.OCSPStatus(*s)
return nil
}
return gorp.CustomScanner{Holder: new(string), Target: target, Binder: binder}, true
return borp.CustomScanner{Holder: new(string), Target: target, Binder: binder}, true
default:
return gorp.CustomScanner{}, false
return borp.CustomScanner{}, false
}
}

View File

@ -1,6 +1,7 @@
package test
import (
"context"
"database/sql"
"fmt"
"io"
@ -15,9 +16,9 @@ var (
// rows in all tables in a database plus close the database
// connection. It is satisfied by *sql.DB.
type CleanUpDB interface {
Begin() (*sql.Tx, error)
Exec(query string, args ...interface{}) (sql.Result, error)
Query(query string, args ...interface{}) (*sql.Rows, error)
BeginTx(context.Context, *sql.TxOptions) (*sql.Tx, error)
ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
io.Closer
}
@ -27,7 +28,7 @@ type CleanUpDB interface {
// table as this is used by sql-migrate (https://github.com/rubenv/sql-migrate)
// to track migrations. If it encounters an error it fails the tests.
func ResetBoulderTestDatabase(t testing.TB) func() {
return resetTestDatabase(t, "boulder")
return resetTestDatabase(t, context.Background(), "boulder")
}
// ResetIncidentsTestDatabase returns a cleanup function which deletes all rows
@ -36,20 +37,20 @@ func ResetBoulderTestDatabase(t testing.TB) func() {
// (https://github.com/rubenv/sql-migrate) to track migrations. If it encounters
// an error it fails the tests.
func ResetIncidentsTestDatabase(t testing.TB) func() {
return resetTestDatabase(t, "incidents")
return resetTestDatabase(t, context.Background(), "incidents")
}
func resetTestDatabase(t testing.TB, dbPrefix string) func() {
func resetTestDatabase(t testing.TB, ctx context.Context, dbPrefix string) func() {
db, err := sql.Open("mysql", fmt.Sprintf("test_setup@tcp(boulder-proxysql:6033)/%s_sa_test", dbPrefix))
if err != nil {
t.Fatalf("Couldn't create db: %s", err)
}
err = deleteEverythingInAllTables(db)
err = deleteEverythingInAllTables(ctx, db)
if err != nil {
t.Fatalf("Failed to delete everything: %s", err)
}
return func() {
err := deleteEverythingInAllTables(db)
err := deleteEverythingInAllTables(ctx, db)
if err != nil {
t.Fatalf("Failed to truncate tables after the test: %s", err)
}
@ -61,8 +62,8 @@ func resetTestDatabase(t testing.TB, dbPrefix string) func() {
// available to the CleanUpDB passed in and resets the autoincrement
// counters. See allTableNamesInDB for what is meant by "all tables
// available". To be used only in test code.
func deleteEverythingInAllTables(db CleanUpDB) error {
ts, err := allTableNamesInDB(db)
func deleteEverythingInAllTables(ctx context.Context, db CleanUpDB) error {
ts, err := allTableNamesInDB(ctx, db)
if err != nil {
return err
}
@ -72,22 +73,22 @@ func deleteEverythingInAllTables(db CleanUpDB) error {
// another connection to make the deletion on. Note that
// `alter table` statements will silently cause transactions
// to commit, so we do them outside of the transaction.
tx, err := db.Begin()
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return fmt.Errorf("unable to start transaction to delete all rows from table %#v: %s", tn, err)
}
_, err = tx.Exec("set FOREIGN_KEY_CHECKS = 0")
_, err = tx.ExecContext(ctx, "set FOREIGN_KEY_CHECKS = 0")
if err != nil {
return fmt.Errorf("unable to disable FOREIGN_KEY_CHECKS to delete all rows from table %#v: %s", tn, err)
}
// 1 = 1 here prevents the MariaDB i_am_a_dummy setting from
// rejecting the DELETE for not having a WHERE clause.
_, err = tx.Exec("delete from `" + tn + "` where 1 = 1")
_, err = tx.ExecContext(ctx, "delete from `"+tn+"` where 1 = 1")
if err != nil {
return fmt.Errorf("unable to delete all rows from table %#v: %s", tn, err)
}
_, err = tx.Exec("set FOREIGN_KEY_CHECKS = 1")
_, err = tx.ExecContext(ctx, "set FOREIGN_KEY_CHECKS = 1")
if err != nil {
return fmt.Errorf("unable to re-enable FOREIGN_KEY_CHECKS to delete all rows from table %#v: %s", tn, err)
}
@ -96,7 +97,7 @@ func deleteEverythingInAllTables(db CleanUpDB) error {
return fmt.Errorf("unable to commit transaction to delete all rows from table %#v: %s", tn, err)
}
_, err = db.Exec("alter table `" + tn + "` AUTO_INCREMENT = 1")
_, err = db.ExecContext(ctx, "alter table `"+tn+"` AUTO_INCREMENT = 1")
if err != nil {
return fmt.Errorf("unable to reset autoincrement on table %#v: %s", tn, err)
}
@ -107,8 +108,8 @@ func deleteEverythingInAllTables(db CleanUpDB) error {
// allTableNamesInDB returns the names of the tables available to the passed
// CleanUpDB. Omits the 'gorp_migrations' table as this is used by sql-migrate
// (https://github.com/rubenv/sql-migrate) to track migrations.
func allTableNamesInDB(db CleanUpDB) ([]string, error) {
r, err := db.Query("select table_name from information_schema.tables t where t.table_schema = DATABASE() and t.table_name != 'gorp_migrations';")
func allTableNamesInDB(ctx context.Context, db CleanUpDB) ([]string, error) {
r, err := db.QueryContext(ctx, "select table_name from information_schema.tables t where t.table_schema = DATABASE() and t.table_name != 'gorp_migrations';")
if err != nil {
return nil, err
}

View File

@ -3,6 +3,7 @@
package integration
import (
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
@ -92,6 +93,8 @@ func TestIssuanceCertStorageFailed(t *testing.T) {
t.Parallel()
os.Setenv("DIRECTORY", "http://boulder.service.consul:4001/directory")
ctx := context.Background()
// This test is gated on the StoreLintingCertificateInsteadOfPrecertificate
// feature flag.
if os.Getenv("BOULDER_CONFIG_DIR") != "test/config-next" {
@ -101,7 +104,7 @@ func TestIssuanceCertStorageFailed(t *testing.T) {
db, err := sql.Open("mysql", vars.DBConnSAIntegrationFullPerms)
test.AssertNotError(t, err, "failed to open db connection")
_, err = db.Exec(`DROP TRIGGER IF EXISTS fail_ready`)
_, err = db.ExecContext(ctx, `DROP TRIGGER IF EXISTS fail_ready`)
test.AssertNotError(t, err, "failed to drop trigger")
// Make a specific update to certificateStatus fail, for this test but not others.
@ -114,7 +117,7 @@ func TestIssuanceCertStorageFailed(t *testing.T) {
// NOTE: CREATE and DROP TRIGGER do not work in prepared statements. Go's
// database/sql will automatically try to use a prepared statement if you pass
// any arguments to Exec besides the query itself, so don't do that.
_, err = db.Exec(`
_, err = db.ExecContext(ctx, `
CREATE TRIGGER fail_ready
BEFORE UPDATE ON certificateStatus
FOR EACH ROW BEGIN
@ -131,7 +134,7 @@ func TestIssuanceCertStorageFailed(t *testing.T) {
`)
test.AssertNotError(t, err, "failed to create trigger")
defer db.Exec(`DROP TRIGGER IF EXISTS fail_ready`)
defer db.ExecContext(ctx, `DROP TRIGGER IF EXISTS fail_ready`)
certKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
test.AssertNotError(t, err, "creating random cert key")

View File

@ -1,36 +0,0 @@
language: go
go:
- "1.15.x"
- "1.16.x"
- tip
matrix:
allow_failures:
- go: tip
services:
- mysql
- postgresql
- sqlite3
env:
global:
- secure: RriLxF6+2yMl67hdVv8ImXlu0h62mhcpqjaOgYNU+IEbUQ7hx96CKY6gkpYubW3BgApvF5RH6j3+HKvh2kGp0XhDOYOQCODfBSaSipZ5Aa5RKjsEYLtuVIobvJ80awR9hUeql69+WXs0/s72WThG0qTbOUY4pqHWfteeY235hWM=
install:
- go get -t -d
- go get -t -d -tags integration
before_script:
- mysql -e "CREATE DATABASE gorptest;"
- mysql -u root -e "GRANT ALL ON gorptest.* TO gorptest@localhost IDENTIFIED BY 'gorptest'"
- psql -c "CREATE DATABASE gorptest;" -U postgres
- psql -c "CREATE USER "gorptest" WITH SUPERUSER PASSWORD 'gorptest';" -U postgres
- go get github.com/lib/pq
- go get github.com/mattn/go-sqlite3
- go get github.com/ziutek/mymysql/godrv
- go get github.com/go-sql-driver/mysql
- go get golang.org/x/tools/cmd/cover
- go get github.com/mattn/goveralls
script: ./test_all.sh

View File

@ -1,139 +0,0 @@
// Copyright 2012 James Cooper. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gorp
import (
"fmt"
"reflect"
"strings"
)
// Implementation of Dialect for Oracle databases.
type OracleDialect struct{}
func (d OracleDialect) QuerySuffix() string { return "" }
func (d OracleDialect) CreateIndexSuffix() string { return "" }
func (d OracleDialect) DropIndexSuffix() string { return "" }
func (d OracleDialect) ToSqlType(val reflect.Type, maxsize int, isAutoIncr bool) string {
switch val.Kind() {
case reflect.Ptr:
return d.ToSqlType(val.Elem(), maxsize, isAutoIncr)
case reflect.Bool:
return "boolean"
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
if isAutoIncr {
return "serial"
}
return "integer"
case reflect.Int64, reflect.Uint64:
if isAutoIncr {
return "bigserial"
}
return "bigint"
case reflect.Float64:
return "double precision"
case reflect.Float32:
return "real"
case reflect.Slice:
if val.Elem().Kind() == reflect.Uint8 {
return "bytea"
}
}
switch val.Name() {
case "NullInt64":
return "bigint"
case "NullFloat64":
return "double precision"
case "NullBool":
return "boolean"
case "NullTime", "Time":
return "timestamp with time zone"
}
if maxsize > 0 {
return fmt.Sprintf("varchar(%d)", maxsize)
} else {
return "text"
}
}
// Returns empty string
func (d OracleDialect) AutoIncrStr() string {
return ""
}
func (d OracleDialect) AutoIncrBindValue() string {
return "NULL"
}
func (d OracleDialect) AutoIncrInsertSuffix(col *ColumnMap) string {
return ""
}
// Returns suffix
func (d OracleDialect) CreateTableSuffix() string {
return ""
}
func (d OracleDialect) TruncateClause() string {
return "truncate"
}
// Returns "$(i+1)"
func (d OracleDialect) BindVar(i int) string {
return fmt.Sprintf(":%d", i+1)
}
// After executing the insert uses the ColMap IdQuery to get the generated id
func (d OracleDialect) InsertQueryToTarget(exec SqlExecutor, insertSql, idSql string, target interface{}, params ...interface{}) error {
_, err := exec.Exec(insertSql, params...)
if err != nil {
return err
}
id, err := exec.SelectInt(idSql)
if err != nil {
return err
}
switch target.(type) {
case *int64:
*(target.(*int64)) = id
case *int32:
*(target.(*int32)) = int32(id)
case int:
*(target.(*int)) = int(id)
default:
return fmt.Errorf("Id field can be int, int32 or int64")
}
return nil
}
func (d OracleDialect) QuoteField(f string) string {
return `"` + strings.ToUpper(f) + `"`
}
func (d OracleDialect) QuotedTableForQuery(schema string, table string) string {
if strings.TrimSpace(schema) == "" {
return d.QuoteField(table)
}
return schema + "." + d.QuoteField(table)
}
func (d OracleDialect) IfSchemaNotExists(command, schema string) string {
return fmt.Sprintf("%s if not exists", command)
}
func (d OracleDialect) IfTableExists(command, schema, table string) string {
return fmt.Sprintf("%s if exists", command)
}
func (d OracleDialect) IfTableNotExists(command, schema, table string) string {
return fmt.Sprintf("%s if not exists", command)
}

View File

@ -1,149 +0,0 @@
// Copyright 2012 James Cooper. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gorp
import (
"fmt"
"reflect"
"strings"
"time"
)
type PostgresDialect struct {
suffix string
LowercaseFields bool
}
func (d PostgresDialect) QuerySuffix() string { return ";" }
func (d PostgresDialect) ToSqlType(val reflect.Type, maxsize int, isAutoIncr bool) string {
switch val.Kind() {
case reflect.Ptr:
return d.ToSqlType(val.Elem(), maxsize, isAutoIncr)
case reflect.Bool:
return "boolean"
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
if isAutoIncr {
return "serial"
}
return "integer"
case reflect.Int64, reflect.Uint64:
if isAutoIncr {
return "bigserial"
}
return "bigint"
case reflect.Float64:
return "double precision"
case reflect.Float32:
return "real"
case reflect.Slice:
if val.Elem().Kind() == reflect.Uint8 {
return "bytea"
}
}
switch val.Name() {
case "NullInt64":
return "bigint"
case "NullFloat64":
return "double precision"
case "NullBool":
return "boolean"
case "Time", "NullTime":
return "timestamp with time zone"
}
if maxsize > 0 {
return fmt.Sprintf("varchar(%d)", maxsize)
} else {
return "text"
}
}
// Returns empty string
func (d PostgresDialect) AutoIncrStr() string {
return ""
}
func (d PostgresDialect) AutoIncrBindValue() string {
return "default"
}
func (d PostgresDialect) AutoIncrInsertSuffix(col *ColumnMap) string {
return " returning " + d.QuoteField(col.ColumnName)
}
// Returns suffix
func (d PostgresDialect) CreateTableSuffix() string {
return d.suffix
}
func (d PostgresDialect) CreateIndexSuffix() string {
return "using"
}
func (d PostgresDialect) DropIndexSuffix() string {
return ""
}
func (d PostgresDialect) TruncateClause() string {
return "truncate"
}
func (d PostgresDialect) SleepClause(s time.Duration) string {
return fmt.Sprintf("pg_sleep(%f)", s.Seconds())
}
// Returns "$(i+1)"
func (d PostgresDialect) BindVar(i int) string {
return fmt.Sprintf("$%d", i+1)
}
func (d PostgresDialect) InsertAutoIncrToTarget(exec SqlExecutor, insertSql string, target interface{}, params ...interface{}) error {
rows, err := exec.Query(insertSql, params...)
if err != nil {
return err
}
defer rows.Close()
if !rows.Next() {
return fmt.Errorf("No serial value returned for insert: %s Encountered error: %s", insertSql, rows.Err())
}
if err := rows.Scan(target); err != nil {
return err
}
if rows.Next() {
return fmt.Errorf("more than two serial value returned for insert: %s", insertSql)
}
return rows.Err()
}
func (d PostgresDialect) QuoteField(f string) string {
if d.LowercaseFields {
return `"` + strings.ToLower(f) + `"`
}
return `"` + f + `"`
}
func (d PostgresDialect) QuotedTableForQuery(schema string, table string) string {
if strings.TrimSpace(schema) == "" {
return d.QuoteField(table)
}
return schema + "." + d.QuoteField(table)
}
func (d PostgresDialect) IfSchemaNotExists(command, schema string) string {
return fmt.Sprintf("%s if not exists", command)
}
func (d PostgresDialect) IfTableExists(command, schema, table string) string {
return fmt.Sprintf("%s if exists", command)
}
func (d PostgresDialect) IfTableNotExists(command, schema, table string) string {
return fmt.Sprintf("%s if not exists", command)
}

View File

@ -1,152 +0,0 @@
// Copyright 2012 James Cooper. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gorp
import (
"fmt"
"reflect"
"strings"
)
type SnowflakeDialect struct {
suffix string
LowercaseFields bool
}
func (d SnowflakeDialect) QuerySuffix() string { return ";" }
func (d SnowflakeDialect) ToSqlType(val reflect.Type, maxsize int, isAutoIncr bool) string {
switch val.Kind() {
case reflect.Ptr:
return d.ToSqlType(val.Elem(), maxsize, isAutoIncr)
case reflect.Bool:
return "boolean"
case reflect.Int,
reflect.Int8,
reflect.Int16,
reflect.Int32,
reflect.Uint,
reflect.Uint8,
reflect.Uint16,
reflect.Uint32:
if isAutoIncr {
return "serial"
}
return "integer"
case reflect.Int64, reflect.Uint64:
if isAutoIncr {
return "bigserial"
}
return "bigint"
case reflect.Float64:
return "double precision"
case reflect.Float32:
return "real"
case reflect.Slice:
if val.Elem().Kind() == reflect.Uint8 {
return "binary"
}
}
switch val.Name() {
case "NullInt64":
return "bigint"
case "NullFloat64":
return "double precision"
case "NullBool":
return "boolean"
case "Time", "NullTime":
return "timestamp with time zone"
}
if maxsize > 0 {
return fmt.Sprintf("varchar(%d)", maxsize)
} else {
return "text"
}
}
// Returns empty string
func (d SnowflakeDialect) AutoIncrStr() string {
return ""
}
func (d SnowflakeDialect) AutoIncrBindValue() string {
return "default"
}
func (d SnowflakeDialect) AutoIncrInsertSuffix(col *ColumnMap) string {
return ""
}
// Returns suffix
func (d SnowflakeDialect) CreateTableSuffix() string {
return d.suffix
}
func (d SnowflakeDialect) CreateIndexSuffix() string {
return ""
}
func (d SnowflakeDialect) DropIndexSuffix() string {
return ""
}
func (d SnowflakeDialect) TruncateClause() string {
return "truncate"
}
// Returns "$(i+1)"
func (d SnowflakeDialect) BindVar(i int) string {
return "?"
}
func (d SnowflakeDialect) InsertAutoIncrToTarget(exec SqlExecutor, insertSql string, target interface{}, params ...interface{}) error {
rows, err := exec.Query(insertSql, params...)
if err != nil {
return err
}
defer rows.Close()
if !rows.Next() {
return fmt.Errorf("No serial value returned for insert: %s Encountered error: %s", insertSql, rows.Err())
}
if err := rows.Scan(target); err != nil {
return err
}
if rows.Next() {
return fmt.Errorf("more than two serial value returned for insert: %s", insertSql)
}
return rows.Err()
}
func (d SnowflakeDialect) QuoteField(f string) string {
if d.LowercaseFields {
return `"` + strings.ToLower(f) + `"`
}
return `"` + f + `"`
}
func (d SnowflakeDialect) QuotedTableForQuery(schema string, table string) string {
if strings.TrimSpace(schema) == "" {
return d.QuoteField(table)
}
return schema + "." + d.QuoteField(table)
}
func (d SnowflakeDialect) IfSchemaNotExists(command, schema string) string {
return fmt.Sprintf("%s if not exists", command)
}
func (d SnowflakeDialect) IfTableExists(command, schema, table string) string {
return fmt.Sprintf("%s if exists", command)
}
func (d SnowflakeDialect) IfTableNotExists(command, schema, table string) string {
return fmt.Sprintf("%s if not exists", command)
}

View File

@ -1,145 +0,0 @@
// Copyright 2012 James Cooper. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gorp
import (
"fmt"
"reflect"
"strings"
)
// Implementation of Dialect for Microsoft SQL Server databases.
// Use gorp.SqlServerDialect{"2005"} for legacy datatypes.
// Tested with driver: github.com/denisenkom/go-mssqldb
type SqlServerDialect struct {
// If set to "2005" legacy datatypes will be used
Version string
}
func (d SqlServerDialect) ToSqlType(val reflect.Type, maxsize int, isAutoIncr bool) string {
switch val.Kind() {
case reflect.Ptr:
return d.ToSqlType(val.Elem(), maxsize, isAutoIncr)
case reflect.Bool:
return "bit"
case reflect.Int8:
return "tinyint"
case reflect.Uint8:
return "smallint"
case reflect.Int16:
return "smallint"
case reflect.Uint16:
return "int"
case reflect.Int, reflect.Int32:
return "int"
case reflect.Uint, reflect.Uint32:
return "bigint"
case reflect.Int64:
return "bigint"
case reflect.Uint64:
return "numeric(20,0)"
case reflect.Float32:
return "float(24)"
case reflect.Float64:
return "float(53)"
case reflect.Slice:
if val.Elem().Kind() == reflect.Uint8 {
return "varbinary"
}
}
switch val.Name() {
case "NullInt64":
return "bigint"
case "NullFloat64":
return "float(53)"
case "NullBool":
return "bit"
case "NullTime", "Time":
if d.Version == "2005" {
return "datetime"
}
return "datetime2"
}
if maxsize < 1 {
if d.Version == "2005" {
maxsize = 255
} else {
return fmt.Sprintf("nvarchar(max)")
}
}
return fmt.Sprintf("nvarchar(%d)", maxsize)
}
// Returns auto_increment
func (d SqlServerDialect) AutoIncrStr() string {
return "identity(0,1)"
}
// Empty string removes autoincrement columns from the INSERT statements.
func (d SqlServerDialect) AutoIncrBindValue() string {
return ""
}
func (d SqlServerDialect) AutoIncrInsertSuffix(col *ColumnMap) string {
return ""
}
func (d SqlServerDialect) CreateTableSuffix() string { return ";" }
func (d SqlServerDialect) TruncateClause() string {
return "truncate table"
}
// Returns "?"
func (d SqlServerDialect) BindVar(i int) string {
return "?"
}
func (d SqlServerDialect) InsertAutoIncr(exec SqlExecutor, insertSql string, params ...interface{}) (int64, error) {
return standardInsertAutoIncr(exec, insertSql, params...)
}
func (d SqlServerDialect) QuoteField(f string) string {
return "[" + strings.Replace(f, "]", "]]", -1) + "]"
}
func (d SqlServerDialect) QuotedTableForQuery(schema string, table string) string {
if strings.TrimSpace(schema) == "" {
return d.QuoteField(table)
}
return d.QuoteField(schema) + "." + d.QuoteField(table)
}
func (d SqlServerDialect) QuerySuffix() string { return ";" }
func (d SqlServerDialect) IfSchemaNotExists(command, schema string) string {
s := fmt.Sprintf("if schema_id(N'%s') is null %s", schema, command)
return s
}
func (d SqlServerDialect) IfTableExists(command, schema, table string) string {
var schema_clause string
if strings.TrimSpace(schema) != "" {
schema_clause = fmt.Sprintf("%s.", d.QuoteField(schema))
}
s := fmt.Sprintf("if object_id('%s%s') is not null %s", schema_clause, d.QuoteField(table), command)
return s
}
func (d SqlServerDialect) IfTableNotExists(command, schema, table string) string {
var schema_clause string
if strings.TrimSpace(schema) != "" {
schema_clause = fmt.Sprintf("%s.", schema)
}
s := fmt.Sprintf("if object_id('%s%s') is null %s", schema_clause, table, command)
return s
}
func (d SqlServerDialect) CreateIndexSuffix() string { return "" }
func (d SqlServerDialect) DropIndexSuffix() string { return "" }

View File

@ -1,11 +0,0 @@
// Copyright 2012 James Cooper. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// Package gorp provides a simple way to marshal Go structs to and from
// SQL databases. It uses the database/sql package, and should work with any
// compliant database/sql driver.
//
// Source code and project home:
// https://github.com/go-gorp/gorp
package gorp

1
vendor/github.com/letsencrypt/borp/CODEOWNERS generated vendored Normal file
View File

@ -0,0 +1 @@
* @letsencrypt/boulder-developers

View File

@ -1,107 +1,16 @@
# Go Relational Persistence
# Boulder's Object Relational Persistence
[![build status](https://github.com/go-gorp/gorp/actions/workflows/go.yml/badge.svg)](https://github.com/go-gorp/gorp/actions)
[![issues](https://img.shields.io/github/issues/go-gorp/gorp.svg)](https://github.com/go-gorp/gorp/issues)
[![Go Reference](https://pkg.go.dev/badge/github.com/go-gorp/gorp/v3.svg)](https://pkg.go.dev/github.com/go-gorp/gorp/v3)
Once upon a time, there was [gorp](https://github.com/go-gorp/gorp/). Gorp was
good, and Let's Encrypt's adopted it for Boulder's database persistence layer.
The maintainers became busy with other projects, and gorp stopped getting
updated. However, it's still very useful to Boulder, and there are a few tweaks
we'd like to make for our own purposes, so we've forked it.
### Update 2016-11-13: Future versions
We maintain this primarily for Boulder's own use, and intend to make some API
breaking changes during 2023. You are welcome to use our fork if you like it, but
we do not expect to spend a lot of time maintaining it for non-Boulder uses.
As many of the maintainers have become busy with other projects,
progress toward the ever-elusive v2 has slowed to the point that we're
only occasionally making progress outside of merging pull requests.
In the interest of continuing to release, I'd like to lean toward a
more maintainable path forward.
For the moment, I am releasing a v2 tag with the current feature set
from master, as some of those features have been actively used and
relied on by more than one project. Our next goal is to continue
cleaning up the code base with non-breaking changes as much as
possible, but if/when a breaking change is needed, we'll just release
new versions. This allows us to continue development at whatever pace
we're capable of, without delaying the release of features or refusing
PRs.
## Introduction
I hesitate to call gorp an ORM. Go doesn't really have objects, at
least not in the classic Smalltalk/Java sense. There goes the "O".
gorp doesn't know anything about the relationships between your
structs (at least not yet). So the "R" is questionable too (but I use
it in the name because, well, it seemed more clever).
The "M" is alive and well. Given some Go structs and a database, gorp
should remove a fair amount of boilerplate busy-work from your code.
I hope that gorp saves you time, minimizes the drudgery of getting
data in and out of your database, and helps your code focus on
algorithms, not infrastructure.
* Bind struct fields to table columns via API or tag
* Support for embedded structs
* Support for transactions
* Forward engineer db schema from structs (great for unit tests)
* Pre/post insert/update/delete hooks
* Automatically generate insert/update/delete statements for a struct
* Automatic binding of auto increment PKs back to struct after insert
* Delete by primary key(s)
* Select by primary key(s)
* Optional trace sql logging
* Bind arbitrary SQL queries to a struct
* Bind slice to SELECT query results without type assertions
* Use positional or named bind parameters in custom SELECT queries
* Optional optimistic locking using a version column (for
update/deletes)
## Installation
Use `go get` or your favorite vendoring tool, using whichever import
path you'd like.
## Versioning
We use semantic version tags. Feel free to import through `gopkg.in`
(e.g. `gopkg.in/gorp.v2`) to get the latest tag for a major version,
or check out the tag using your favorite vendoring tool.
Development is not very active right now, but we have plans to
restructure `gorp` as we continue to move toward a more extensible
system. Whenever a breaking change is needed, the major version will
be bumped.
The `master` branch is where all development is done, and breaking
changes may happen from time to time. That said, if you want to live
on the bleeding edge and are comfortable updating your code when we
make a breaking change, you may use `github.com/go-gorp/gorp` as your
import path.
Check the version tags to see what's available. We'll make a good
faith effort to add badges for new versions, but we make no
guarantees.
## Supported Go versions
This package is guaranteed to be compatible with the latest 2 major
versions of Go.
Any earlier versions are only supported on a best effort basis and can
be dropped any time. Go has a great compatibility promise. Upgrading
your program to a newer version of Go should never really be a
problem.
## Migration guide
#### Pre-v2 to v2
Automatic mapping of the version column used in optimistic locking has
been removed as it could cause problems if the type was not int. The
version column must now explicitly be set with
`tablemap.SetVersionCol()`.
## Help/Support
Use our [`gitter` channel](https://gitter.im/go-gorp/gorp). We used
to use IRC, but with most of us being pulled in many directions, we
often need the email notifications from `gitter` to yell at us to sign
in.
In particular we maintain this to use with MariaDB 10.5+ and InnoDB.
## Quickstart
@ -110,7 +19,7 @@ package main
import (
"database/sql"
"gopkg.in/gorp.v1"
"github.com/letsencrypt/borp"
_ "github.com/mattn/go-sqlite3"
"log"
"time"
@ -146,9 +55,6 @@ func main() {
// fetch one row - note use of "post_id" instead of "Id" since column is aliased
//
// Postgres users should use $1 instead of ? placeholders
// See 'Known Issues' below
//
err = dbmap.SelectOne(&p2, "select * from posts where post_id=?", p2.Id)
checkErr(err, "SelectOne failed")
log.Println("p2 row:", p2)
@ -195,14 +101,14 @@ func newPost(title, body string) Post {
}
}
func initDb() *gorp.DbMap {
func initDb() *borp.DbMap {
// connect to db using standard Go database/sql API
// use whatever database/sql driver you wish
db, err := sql.Open("sqlite3", "/tmp/post_db.bin")
checkErr(err, "sql.Open failed")
// construct a gorp DbMap
dbmap := &gorp.DbMap{Db: db, Dialect: gorp.SqliteDialect{}}
// construct a borp DbMap
dbmap := &borp.DbMap{Db: db, Dialect: borp.SqliteDialect{}}
// add a table, setting the table name to 'posts' and
// specifying that the Id property is an auto incrementing PK
@ -249,7 +155,7 @@ type Person struct {
// Example of using tags to alias fields to column names
// The 'db' value is the column name
//
// A hyphen will cause gorp to skip this field, similar to the
// A hyphen will cause borp to skip this field, similar to the
// Go json package.
//
// This is equivalent to using the ColMap methods:
@ -275,10 +181,10 @@ Then create a mapper, typically you'd do this one time at app startup:
// use whatever database/sql driver you wish
db, err := sql.Open("mymysql", "tcp:localhost:3306*mydb/myuser/mypassword")
// construct a gorp DbMap
dbmap := &gorp.DbMap{Db: db, Dialect: gorp.MySQLDialect{"InnoDB", "UTF8"}}
// construct a borp DbMap
dbmap := &borp.DbMap{Db: db, Dialect: borp.MySQLDialect{"InnoDB", "UTF8"}}
// register the structs you wish to use with gorp
// register the structs you wish to use with borp
// you can also use the shorter dbmap.AddTable() if you
// don't want to override the table name
//
@ -292,7 +198,7 @@ t3 := dbmap.AddTableWithName(Product{}, "product_test").SetKeys(true, "Id")
### Struct Embedding
gorp supports embedding structs. For example:
borp supports embedding structs. For example:
```go
type Names struct {
@ -309,12 +215,12 @@ es := &WithEmbeddedStruct{-1, Names{FirstName: "Alice", LastName: "Smith"}}
err := dbmap.Insert(es)
```
See the `TestWithEmbeddedStruct` function in `gorp_test.go` for a full example.
See the `TestWithEmbeddedStruct` function in `borp_test.go` for a full example.
### Create/Drop Tables ###
Automatically create / drop registered tables. This is useful for unit tests
but is entirely optional. You can of course use gorp with tables created manually,
but is entirely optional. You can of course use borp with tables created manually,
or with a separate migration tool (like [sql-migrate](https://github.com/rubenv/sql-migrate), [goose](https://bitbucket.org/liamstask/goose) or [migrate](https://github.com/mattes/migrate)).
```go
@ -333,9 +239,9 @@ dbmap.DropTables()
Optionally you can pass in a logger to trace all SQL statements.
I recommend enabling this initially while you're getting the feel for what
gorp is doing on your behalf.
borp is doing on your behalf.
Gorp defines a `GorpLogger` interface that Go's built in `log.Logger` satisfies.
Borp defines a `GorpLogger` interface that Go's built in `log.Logger` satisfies.
However, you can write your own `GorpLogger` implementation, or use a package such
as `glog` if you want more control over how statements are logged.
@ -415,7 +321,7 @@ var post Post
err := dbmap.SelectOne(&post, "select * from post where id=?", id)
```
Want to do joins? Just write the SQL and the struct. gorp will bind them:
Want to do joins? Just write the SQL and the struct. Borp will bind them:
```go
// Define a type for your join
@ -459,10 +365,10 @@ if reflect.DeepEqual(list[0], expected) {
#### SELECT string or int64
gorp provides a few convenience methods for selecting a single string or int64.
Borp provides a few convenience methods for selecting a single string or int64.
```go
// select single int64 from db (use $1 instead of ? for postgresql)
// select single int64 from db
i64, err := dbmap.SelectInt("select count(*) from foo where blah=?", blahVal)
// select single string from db:
@ -472,8 +378,9 @@ s, err := dbmap.SelectStr("select name from foo where blah=?", blahVal)
#### Named bind parameters
You may use a map or struct to bind parameters by name. This is currently
only supported in SELECT queries.
You may use a map or struct to bind parameters by name. This is supported in
the Exec and Select* functions on DbMap and Transaction. It is not supported by
Prepare, Query, or QueryRow.
```go
_, err := dbm.Select(&dest, "select * from Foo where name = :name and age = :age", map[string]interface{}{
@ -520,27 +427,26 @@ Use hooks to update data before/after saving to the db. Good for timestamps:
```go
// implement the PreInsert and PreUpdate hooks
func (i *Invoice) PreInsert(s gorp.SqlExecutor) error {
func (i *Invoice) PreInsert(s borp.SqlExecutor) error {
i.Created = time.Now().UnixNano()
i.Updated = i.Created
return nil
}
func (i *Invoice) PreUpdate(s gorp.SqlExecutor) error {
func (i *Invoice) PreUpdate(s borp.SqlExecutor) error {
i.Updated = time.Now().UnixNano()
return nil
}
// You can use the SqlExecutor to cascade additional SQL
// Take care to avoid cycles. gorp won't prevent them.
// Take care to avoid cycles. Borp won't prevent them.
//
// Here's an example of a cascading delete
//
func (p *Person) PreDelete(s gorp.SqlExecutor) error {
func (p *Person) PreDelete(s borp.SqlExecutor) error {
query := "delete from invoice_test where PersonId=?"
_, err := s.Exec(query, p.Id)
if err != nil {
return err
}
@ -560,20 +466,20 @@ Full list of hooks that you can implement:
All have the same signature. for example:
func (p *MyStruct) PostUpdate(s gorp.SqlExecutor) error
func (p *MyStruct) PostUpdate(s borp.SqlExecutor) error
### Optimistic Locking
#### Note that this behaviour has changed in v2. See [Migration Guide](#migration-guide).
gorp provides a simple optimistic locking feature, similar to Java's
Borp provides a simple optimistic locking feature, similar to Java's
JPA, that will raise an error if you try to update/delete a row whose
`version` column has a value different than the one in memory. This
provides a safe way to do "select then update" style operations
without explicit read and write locks.
```go
// Version is an auto-incremented number, managed by gorp
// Version is an auto-incremented number, managed by borp
// If this property is present on your struct, update
// operations will be constrained
//
@ -606,7 +512,7 @@ p1.LName = "Howard"
// Raises error because p1.Version == 1, which is out of date
count, err := dbmap.Update(p1)
_, ok := err.(gorp.OptimisticLockError)
_, ok := err.(borp.OptimisticLockError)
if ok {
// should reach this statement
@ -624,10 +530,6 @@ if ok {
Indexes are frequently critical for performance. Here is how to add
them to your tables.
NB: SqlServer and Oracle need testing and possible adjustment to the
CreateIndexSuffix() and DropIndexSuffix() methods to make AddIndex()
work for them.
In the example below we put an index both on the Id field, and on the
AcctId field.
@ -667,32 +569,21 @@ MariaDB [test]> show create table Account;
## Database Drivers
gorp uses the Go 1 `database/sql` package. A full list of compliant
borp uses the Go 1 `database/sql` package. A full list of compliant
drivers is available here:
http://code.google.com/p/go-wiki/wiki/SQLDrivers
Sadly, SQL databases differ on various issues. gorp provides a Dialect
Sadly, SQL databases differ on various issues. borp provides a Dialect
interface that should be implemented per database vendor. Dialects
are provided for:
* MySQL
* PostgreSQL
* sqlite3
Each of these three databases pass the test suite. See `gorp_test.go`
Each of these three databases pass the test suite. See `borp_test.go`
for example DSNs for these three databases.
Support is also provided for:
* Oracle (contributed by @klaidliadon)
* SQL Server (contributed by @qrawl) - use driver:
github.com/denisenkom/go-mssqldb
Note that these databases are not covered by CI and I (@coopernurse)
have no good way to test them locally. So please try them and send
patches as needed, but expect a bit more unpredicability.
## Sqlite3 Extensions
In order to use sqlite3 extensions you need to first register a custom driver:
@ -721,43 +612,9 @@ func customDriver() (*sql.DB, error) {
## Known Issues
### SQL placeholder portability
Different databases use different strings to indicate variable
placeholders in prepared SQL statements. Unlike some database
abstraction layers (such as JDBC), Go's `database/sql` does not
standardize this.
SQL generated by gorp in the `Insert`, `Update`, `Delete`, and `Get`
methods delegates to a Dialect implementation for each database, and
will generate portable SQL.
Raw SQL strings passed to `Exec`, `Select`, `SelectOne`, `SelectInt`,
etc will not be parsed. Consequently you may have portability issues
if you write a query like this:
```go
// works on MySQL and Sqlite3, but not with Postgresql err :=
dbmap.SelectOne(&val, "select * from foo where id = ?", 30)
```
In `Select` and `SelectOne` you can use named parameters to work
around this. The following is portable:
```go
err := dbmap.SelectOne(&val, "select * from foo where id = :id",
map[string]interface{} { "id": 30})
```
Additionally, when using Postgres as your database, you should utilize
`$1` instead of `?` placeholders as utilizing `?` placeholders when
querying Postgres will result in `pq: operator does not exist`
errors. Alternatively, use `dbMap.Dialect.BindVar(varIdx)` to get the
proper variable binding for your dialect.
### time.Time and time zones
gorp will pass `time.Time` fields through to the `database/sql`
borp will pass `time.Time` fields through to the `database/sql`
driver, but note that the behavior of this type varies across database
drivers.
@ -773,7 +630,7 @@ To avoid any potential issues with timezone/DST, consider:
## Running the tests
The included tests may be run against MySQL, Postgresql, or sqlite3.
The included tests may be run against MySQL or sqlite3.
You must set two environment variables so the test code knows which
driver to use, and how to connect to your database.
@ -790,17 +647,10 @@ go test -bench="Bench" -benchtime 10
```
Valid `GORP_TEST_DIALECT` values are: "mysql"(for mymysql),
"gomysql"(for go-sql-driver), "postgres", "sqlite" See the
"gomysql"(for go-sql-driver), or "sqlite" See the
`test_all.sh` script for examples of all 3 databases. This is the
script I run locally to test the library.
## Performance
gorp uses reflection to construct SQL queries and bind parameters.
See the BenchmarkNativeCrud vs BenchmarkGorpCrud in gorp_test.go for a
simple perf test. On my MacBook Pro gorp is about 2-3% slower than
hand written SQL.
## Contributors

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gorp
package borp
import "reflect"

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gorp
package borp
import (
"bytes"
@ -24,12 +24,9 @@ import (
//
// Example:
//
// dialect := gorp.MySQLDialect{"InnoDB", "UTF8"}
// dbmap := &gorp.DbMap{Db: db, Dialect: dialect}
//
// dialect := borp.MySQLDialect{"InnoDB", "UTF8"}
// dbmap := &borp.DbMap{Db: db, Dialect: dialect}
type DbMap struct {
ctx context.Context
// Db handle to use with this map
Db *sql.DB
@ -40,53 +37,7 @@ type DbMap struct {
// ExpandSlices when enabled will convert slice arguments in mappers into flat
// values. It will modify the query, adding more placeholders, and the mapper,
// adding each item of the slice as a new unique entry in the mapper. For
// example, given the scenario bellow:
//
// dbmap.Select(&output, "SELECT 1 FROM example WHERE id IN (:IDs)", map[string]interface{}{
// "IDs": []int64{1, 2, 3},
// })
//
// The executed query would be:
//
// SELECT 1 FROM example WHERE id IN (:IDs0,:IDs1,:IDs2)
//
// With the mapper:
//
// map[string]interface{}{
// "IDs": []int64{1, 2, 3},
// "IDs0": int64(1),
// "IDs1": int64(2),
// "IDs2": int64(3),
// }
//
// It is also flexible for custom slice types. The value just need to
// implement stringer or numberer interfaces.
//
// type CustomValue string
//
// const (
// CustomValueHey CustomValue = "hey"
// CustomValueOh CustomValue = "oh"
// )
//
// type CustomValues []CustomValue
//
// func (c CustomValues) ToStringSlice() []string {
// values := make([]string, len(c))
// for i := range c {
// values[i] = string(c[i])
// }
// return values
// }
//
// func query() {
// // ...
// result, err := dbmap.Select(&output, "SELECT 1 FROM example WHERE value IN (:Values)", map[string]interface{}{
// "Values": CustomValues([]CustomValue{CustomValueHey}),
// })
// // ...
// }
// adding each item of the slice as a new unique entry in the mapper.
ExpandSliceArgs bool
tables []*TableMap
@ -117,19 +68,12 @@ func (m *DbMap) dynamicTableMap() map[string]*TableMap {
return m.tablesDynamic
}
func (m *DbMap) WithContext(ctx context.Context) SqlExecutor {
copy := &DbMap{}
*copy = *m
copy.ctx = ctx
return copy
}
func (m *DbMap) CreateIndex() error {
func (m *DbMap) CreateIndex(ctx context.Context) error {
var err error
dialect := reflect.TypeOf(m.Dialect)
for _, table := range m.tables {
for _, index := range table.indexes {
err = m.createIndexImpl(dialect, table, index)
err = m.createIndexImpl(ctx, dialect, table, index)
if err != nil {
break
}
@ -138,7 +82,7 @@ func (m *DbMap) CreateIndex() error {
for _, table := range m.dynamicTableMap() {
for _, index := range table.indexes {
err = m.createIndexImpl(dialect, table, index)
err = m.createIndexImpl(ctx, dialect, table, index)
if err != nil {
break
}
@ -148,7 +92,7 @@ func (m *DbMap) CreateIndex() error {
return err
}
func (m *DbMap) createIndexImpl(dialect reflect.Type,
func (m *DbMap) createIndexImpl(ctx context.Context, dialect reflect.Type,
table *TableMap,
index *IndexMap) error {
s := bytes.Buffer{}
@ -158,9 +102,6 @@ func (m *DbMap) createIndexImpl(dialect reflect.Type,
}
s.WriteString(" index")
s.WriteString(fmt.Sprintf(" %s on %s", index.IndexName, table.TableName))
if dname := dialect.Name(); dname == "PostgresDialect" && index.IndexType != "" {
s.WriteString(fmt.Sprintf(" %s %s", m.Dialect.CreateIndexSuffix(), index.IndexType))
}
s.WriteString(" (")
for x, col := range index.columns {
if x > 0 {
@ -174,11 +115,11 @@ func (m *DbMap) createIndexImpl(dialect reflect.Type,
s.WriteString(fmt.Sprintf(" %s %s", m.Dialect.CreateIndexSuffix(), index.IndexType))
}
s.WriteString(";")
_, err := m.Exec(s.String())
_, err := m.ExecContext(ctx, s.String())
return err
}
func (t *TableMap) DropIndex(name string) error {
func (t *TableMap) DropIndex(ctx context.Context, name string) error {
var err error
dialect := reflect.TypeOf(t.dbmap.Dialect)
@ -191,7 +132,7 @@ func (t *TableMap) DropIndex(name string) error {
s.WriteString(fmt.Sprintf(" %s %s", t.dbmap.Dialect.DropIndexSuffix(), t.TableName))
}
s.WriteString(";")
_, e := t.dbmap.Exec(s.String())
_, e := t.dbmap.ExecContext(ctx, s.String())
if e != nil {
err = e
}
@ -202,7 +143,7 @@ func (t *TableMap) DropIndex(name string) error {
return err
}
// AddTable registers the given interface type with gorp. The table name
// AddTable registers the given interface type with borp. The table name
// will be given the name of the TypeOf(i). You must call this function,
// or AddTableWithName, for any struct type you wish to persist with
// the given DbMap.
@ -248,7 +189,7 @@ func (m *DbMap) AddTableWithNameAndSchema(i interface{}, schema string, name str
return tmap
}
// AddTableDynamic registers the given interface type with gorp.
// AddTableDynamic registers the given interface type with borp.
// The table name will be dynamically determined at runtime by
// using the GetTableName method on DynamicTable interface
func (m *DbMap) AddTableDynamic(inp DynamicTable, schema string) *TableMap {
@ -418,23 +359,23 @@ func (m *DbMap) readStructColumns(t reflect.Type) (cols []*ColumnMap, primaryKey
//
// This is particularly useful in unit tests where you want to create
// and destroy the schema automatically.
func (m *DbMap) CreateTables() error {
return m.createTables(false)
func (m *DbMap) CreateTables(ctx context.Context) error {
return m.createTables(ctx, false)
}
// CreateTablesIfNotExists is similar to CreateTables, but starts
// each statement with "create table if not exists" so that existing
// tables do not raise errors
func (m *DbMap) CreateTablesIfNotExists() error {
return m.createTables(true)
func (m *DbMap) CreateTablesIfNotExists(ctx context.Context) error {
return m.createTables(ctx, true)
}
func (m *DbMap) createTables(ifNotExists bool) error {
func (m *DbMap) createTables(ctx context.Context, ifNotExists bool) error {
var err error
for i := range m.tables {
table := m.tables[i]
sql := table.SqlForCreate(ifNotExists)
_, err = m.Exec(sql)
_, err = m.ExecContext(ctx, sql)
if err != nil {
return err
}
@ -442,7 +383,7 @@ func (m *DbMap) createTables(ifNotExists bool) error {
for _, tbl := range m.dynamicTableMap() {
sql := tbl.SqlForCreate(ifNotExists)
_, err = m.Exec(sql)
_, err = m.ExecContext(ctx, sql)
if err != nil {
return err
}
@ -453,7 +394,7 @@ func (m *DbMap) createTables(ifNotExists bool) error {
// DropTable drops an individual table.
// Returns an error when the table does not exist.
func (m *DbMap) DropTable(table interface{}) error {
func (m *DbMap) DropTable(ctx context.Context, table interface{}) error {
t := reflect.TypeOf(table)
tableName := ""
@ -461,11 +402,11 @@ func (m *DbMap) DropTable(table interface{}) error {
tableName = dyn.TableName()
}
return m.dropTable(t, tableName, false)
return m.dropTable(ctx, t, tableName, false)
}
// DropTableIfExists drops an individual table when the table exists.
func (m *DbMap) DropTableIfExists(table interface{}) error {
func (m *DbMap) DropTableIfExists(ctx context.Context, table interface{}) error {
t := reflect.TypeOf(table)
tableName := ""
@ -473,34 +414,34 @@ func (m *DbMap) DropTableIfExists(table interface{}) error {
tableName = dyn.TableName()
}
return m.dropTable(t, tableName, true)
return m.dropTable(ctx, t, tableName, true)
}
// DropTables iterates through TableMaps registered to this DbMap and
// executes "drop table" statements against the database for each.
func (m *DbMap) DropTables() error {
return m.dropTables(false)
func (m *DbMap) DropTables(ctx context.Context) error {
return m.dropTables(ctx, false)
}
// DropTablesIfExists is the same as DropTables, but uses the "if exists" clause to
// avoid errors for tables that do not exist.
func (m *DbMap) DropTablesIfExists() error {
return m.dropTables(true)
func (m *DbMap) DropTablesIfExists(ctx context.Context) error {
return m.dropTables(ctx, true)
}
// Goes through all the registered tables, dropping them one by one.
// If an error is encountered, then it is returned and the rest of
// the tables are not dropped.
func (m *DbMap) dropTables(addIfExists bool) (err error) {
func (m *DbMap) dropTables(ctx context.Context, addIfExists bool) (err error) {
for _, table := range m.tables {
err = m.dropTableImpl(table, addIfExists)
err = m.dropTableImpl(ctx, table, addIfExists)
if err != nil {
return err
}
}
for _, table := range m.dynamicTableMap() {
err = m.dropTableImpl(table, addIfExists)
err = m.dropTableImpl(ctx, table, addIfExists)
if err != nil {
return err
}
@ -510,21 +451,21 @@ func (m *DbMap) dropTables(addIfExists bool) (err error) {
}
// Implementation of dropping a single table.
func (m *DbMap) dropTable(t reflect.Type, name string, addIfExists bool) error {
func (m *DbMap) dropTable(ctx context.Context, t reflect.Type, name string, addIfExists bool) error {
table := tableOrNil(m, t, name)
if table == nil {
return fmt.Errorf("table %s was not registered", table.TableName)
}
return m.dropTableImpl(table, addIfExists)
return m.dropTableImpl(ctx, table, addIfExists)
}
func (m *DbMap) dropTableImpl(table *TableMap, ifExists bool) (err error) {
func (m *DbMap) dropTableImpl(ctx context.Context, table *TableMap, ifExists bool) (err error) {
tableDrop := "drop table"
if ifExists {
tableDrop = m.Dialect.IfTableExists(tableDrop, table.SchemaName, table.TableName)
}
_, err = m.Exec(fmt.Sprintf("%s %s;", tableDrop, m.Dialect.QuotedTableForQuery(table.SchemaName, table.TableName)))
_, err = m.ExecContext(ctx, fmt.Sprintf("%s %s;", tableDrop, m.Dialect.QuotedTableForQuery(table.SchemaName, table.TableName)))
return err
}
@ -532,18 +473,18 @@ func (m *DbMap) dropTableImpl(table *TableMap, ifExists bool) (err error) {
// executes "truncate table" statements against the database for each, or in the case of
// sqlite, a "delete from" with no "where" clause, which uses the truncate optimization
// (http://www.sqlite.org/lang_delete.html)
func (m *DbMap) TruncateTables() error {
func (m *DbMap) TruncateTables(ctx context.Context) error {
var err error
for i := range m.tables {
table := m.tables[i]
_, e := m.Exec(fmt.Sprintf("%s %s;", m.Dialect.TruncateClause(), m.Dialect.QuotedTableForQuery(table.SchemaName, table.TableName)))
_, e := m.ExecContext(ctx, fmt.Sprintf("%s %s;", m.Dialect.TruncateClause(), m.Dialect.QuotedTableForQuery(table.SchemaName, table.TableName)))
if e != nil {
err = e
}
}
for _, table := range m.dynamicTableMap() {
_, e := m.Exec(fmt.Sprintf("%s %s;", m.Dialect.TruncateClause(), m.Dialect.QuotedTableForQuery(table.SchemaName, table.TableName)))
_, e := m.ExecContext(ctx, fmt.Sprintf("%s %s;", m.Dialect.TruncateClause(), m.Dialect.QuotedTableForQuery(table.SchemaName, table.TableName)))
if e != nil {
err = e
}
@ -562,8 +503,8 @@ func (m *DbMap) TruncateTables() error {
// before/after the INSERT statement if the interface defines them.
//
// Panics if any interface in the list has not been registered with AddTable
func (m *DbMap) Insert(list ...interface{}) error {
return insert(m, m, list...)
func (m *DbMap) Insert(ctx context.Context, list ...interface{}) error {
return insert(ctx, m, m, list...)
}
// Update runs a SQL UPDATE statement for each element in list. List
@ -576,8 +517,8 @@ func (m *DbMap) Insert(list ...interface{}) error {
//
// Returns an error if SetKeys has not been called on the TableMap
// Panics if any interface in the list has not been registered with AddTable
func (m *DbMap) Update(list ...interface{}) (int64, error) {
return update(m, m, nil, list...)
func (m *DbMap) Update(ctx context.Context, list ...interface{}) (int64, error) {
return update(ctx, m, m, nil, list...)
}
// UpdateColumns runs a SQL UPDATE statement for each element in list. List
@ -592,8 +533,8 @@ func (m *DbMap) Update(list ...interface{}) (int64, error) {
//
// Returns an error if SetKeys has not been called on the TableMap
// Panics if any interface in the list has not been registered with AddTable
func (m *DbMap) UpdateColumns(filter ColumnFilter, list ...interface{}) (int64, error) {
return update(m, m, filter, list...)
func (m *DbMap) UpdateColumns(ctx context.Context, filter ColumnFilter, list ...interface{}) (int64, error) {
return update(ctx, m, m, filter, list...)
}
// Delete runs a SQL DELETE statement for each element in list. List
@ -606,8 +547,8 @@ func (m *DbMap) UpdateColumns(filter ColumnFilter, list ...interface{}) (int64,
//
// Returns an error if SetKeys has not been called on the TableMap
// Panics if any interface in the list has not been registered with AddTable
func (m *DbMap) Delete(list ...interface{}) (int64, error) {
return delete(m, m, list...)
func (m *DbMap) Delete(ctx context.Context, list ...interface{}) (int64, error) {
return delete(ctx, m, m, list...)
}
// Get runs a SQL SELECT to fetch a single row from the table based on the
@ -625,8 +566,8 @@ func (m *DbMap) Delete(list ...interface{}) (int64, error) {
//
// Returns an error if SetKeys has not been called on the TableMap
// Panics if any interface in the list has not been registered with AddTable
func (m *DbMap) Get(i interface{}, keys ...interface{}) (interface{}, error) {
return get(m, m, i, keys...)
func (m *DbMap) Get(ctx context.Context, i interface{}, keys ...interface{}) (interface{}, error) {
return get(ctx, m, m, i, keys...)
}
// Select runs an arbitrary SQL query, binding the columns in the result
@ -648,17 +589,17 @@ func (m *DbMap) Get(i interface{}, keys ...interface{}) (interface{}, error) {
// and nil returned.
//
// i does NOT need to be registered with AddTable()
func (m *DbMap) Select(i interface{}, query string, args ...interface{}) ([]interface{}, error) {
func (m *DbMap) Select(ctx context.Context, i interface{}, query string, args ...interface{}) ([]interface{}, error) {
if m.ExpandSliceArgs {
expandSliceArgs(&query, args...)
}
return hookedselect(m, m, i, query, args...)
return hookedselect(ctx, m, m, i, query, args...)
}
// Exec runs an arbitrary SQL statement. args represent the bind parameters.
// This is equivalent to running: Exec() using database/sql
func (m *DbMap) Exec(query string, args ...interface{}) (sql.Result, error) {
// This is equivalent to running: ExecContext() using database/sql
func (m *DbMap) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
if m.ExpandSliceArgs {
expandSliceArgs(&query, args...)
}
@ -667,79 +608,84 @@ func (m *DbMap) Exec(query string, args ...interface{}) (sql.Result, error) {
now := time.Now()
defer m.trace(now, query, args...)
}
return maybeExpandNamedQueryAndExec(m, query, args...)
return maybeExpandNamedQueryAndExec(ctx, m, query, args...)
}
// SelectInt is a convenience wrapper around the gorp.SelectInt function
func (m *DbMap) SelectInt(query string, args ...interface{}) (int64, error) {
// SelectInt is a convenience wrapper around the borp.SelectInt function
func (m *DbMap) SelectInt(ctx context.Context, query string, args ...interface{}) (int64, error) {
if m.ExpandSliceArgs {
expandSliceArgs(&query, args...)
}
return SelectInt(m, query, args...)
return SelectInt(ctx, m, query, args...)
}
// SelectNullInt is a convenience wrapper around the gorp.SelectNullInt function
func (m *DbMap) SelectNullInt(query string, args ...interface{}) (sql.NullInt64, error) {
// SelectNullInt is a convenience wrapper around the borp.SelectNullInt function
func (m *DbMap) SelectNullInt(ctx context.Context, query string, args ...interface{}) (sql.NullInt64, error) {
if m.ExpandSliceArgs {
expandSliceArgs(&query, args...)
}
return SelectNullInt(m, query, args...)
return SelectNullInt(ctx, m, query, args...)
}
// SelectFloat is a convenience wrapper around the gorp.SelectFloat function
func (m *DbMap) SelectFloat(query string, args ...interface{}) (float64, error) {
// SelectFloat is a convenience wrapper around the borp.SelectFloat function
func (m *DbMap) SelectFloat(ctx context.Context, query string, args ...interface{}) (float64, error) {
if m.ExpandSliceArgs {
expandSliceArgs(&query, args...)
}
return SelectFloat(m, query, args...)
return SelectFloat(ctx, m, query, args...)
}
// SelectNullFloat is a convenience wrapper around the gorp.SelectNullFloat function
func (m *DbMap) SelectNullFloat(query string, args ...interface{}) (sql.NullFloat64, error) {
// SelectNullFloat is a convenience wrapper around the borp.SelectNullFloat function
func (m *DbMap) SelectNullFloat(ctx context.Context, query string, args ...interface{}) (sql.NullFloat64, error) {
if m.ExpandSliceArgs {
expandSliceArgs(&query, args...)
}
return SelectNullFloat(m, query, args...)
return SelectNullFloat(ctx, m, query, args...)
}
// SelectStr is a convenience wrapper around the gorp.SelectStr function
func (m *DbMap) SelectStr(query string, args ...interface{}) (string, error) {
// SelectStr is a convenience wrapper around the borp.SelectStr function
func (m *DbMap) SelectStr(ctx context.Context, query string, args ...interface{}) (string, error) {
if m.ExpandSliceArgs {
expandSliceArgs(&query, args...)
}
return SelectStr(m, query, args...)
return SelectStr(ctx, m, query, args...)
}
// SelectNullStr is a convenience wrapper around the gorp.SelectNullStr function
func (m *DbMap) SelectNullStr(query string, args ...interface{}) (sql.NullString, error) {
// SelectNullStr is a convenience wrapper around the borp.SelectNullStr function
func (m *DbMap) SelectNullStr(ctx context.Context, query string, args ...interface{}) (sql.NullString, error) {
if m.ExpandSliceArgs {
expandSliceArgs(&query, args...)
}
return SelectNullStr(m, query, args...)
return SelectNullStr(ctx, m, query, args...)
}
// SelectOne is a convenience wrapper around the gorp.SelectOne function
func (m *DbMap) SelectOne(holder interface{}, query string, args ...interface{}) error {
// SelectOne is a convenience wrapper around the borp.SelectOne function
func (m *DbMap) SelectOne(ctx context.Context, holder interface{}, query string, args ...interface{}) error {
if m.ExpandSliceArgs {
expandSliceArgs(&query, args...)
}
return SelectOne(m, m, holder, query, args...)
return SelectOne(ctx, m, m, holder, query, args...)
}
// Begin starts a gorp Transaction
func (m *DbMap) Begin() (*Transaction, error) {
// BeginTx starts a borp Transaction. It uses database/sql.DB.BeginTx under the hood so the same
// guarantees apply. https://pkg.go.dev/database/sql#DB.BeginTx
//
// > The provided context is used until the transaction is committed or rolled back. If the context
// > is canceled, the sql package will roll back the transaction. Tx.Commit will return an error if
// > the context provided to BeginTx is canceled.
func (m *DbMap) BeginTx(ctx context.Context) (*Transaction, error) {
if m.logger != nil {
now := time.Now()
defer m.trace(now, "begin;")
}
tx, err := begin(m)
tx, err := m.Db.BeginTx(ctx, nil)
if err != nil {
return nil, err
}
@ -789,13 +735,13 @@ func (m *DbMap) DynamicTableFor(tableName string, checkPK bool) (*TableMap, erro
// Prepare creates a prepared statement for later queries or executions.
// Multiple queries or executions may be run concurrently from the returned statement.
// This is equivalent to running: Prepare() using database/sql
func (m *DbMap) Prepare(query string) (*sql.Stmt, error) {
// This is equivalent to running: PrepareContext() using database/sql
func (m *DbMap) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) {
if m.logger != nil {
now := time.Now()
defer m.trace(now, query, nil)
}
return prepare(m, query)
return m.Db.PrepareContext(ctx, query)
}
func tableOrNil(m *DbMap, t reflect.Type, name string) *TableMap {
@ -843,7 +789,8 @@ func (m *DbMap) tableForPointer(ptr interface{}, checkPK bool) (*TableMap, refle
return t, elem, nil
}
func (m *DbMap) QueryRow(query string, args ...interface{}) *sql.Row {
// This is equivalent to running: QueryRowContext() using database/sql
func (m *DbMap) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row {
if m.ExpandSliceArgs {
expandSliceArgs(&query, args...)
}
@ -852,10 +799,11 @@ func (m *DbMap) QueryRow(query string, args ...interface{}) *sql.Row {
now := time.Now()
defer m.trace(now, query, args...)
}
return queryRow(m, query, args...)
return m.Db.QueryRowContext(ctx, query, args...)
}
func (m *DbMap) Query(q string, args ...interface{}) (*sql.Rows, error) {
// This is equivalent to running: QueryContext() using database/sql
func (m *DbMap) QueryContext(ctx context.Context, q string, args ...interface{}) (*sql.Rows, error) {
if m.ExpandSliceArgs {
expandSliceArgs(&q, args...)
}
@ -864,7 +812,7 @@ func (m *DbMap) Query(q string, args ...interface{}) (*sql.Rows, error) {
now := time.Now()
defer m.trace(now, q, args...)
}
return query(m, q, args...)
return m.Db.QueryContext(ctx, q, args...)
}
func (m *DbMap) trace(started time.Time, query string, args ...interface{}) {
@ -874,7 +822,7 @@ func (m *DbMap) trace(started time.Time, query string, args ...interface{}) {
if m.logger != nil {
var margs = argsString(args...)
m.logger.Printf("%s%s [%s] (%v)", m.logPrefix, query, margs, (time.Now().Sub(started)))
m.logger.Printf("%s%s [%s] (%v)", m.logPrefix, query, margs, (time.Since(started)))
}
}

View File

@ -2,9 +2,10 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gorp
package borp
import (
"context"
"reflect"
)
@ -43,8 +44,8 @@ type Dialect interface {
// string to truncate tables
TruncateClause() string
// bind variable string to use when forming SQL statements
// in many dbs it is "?", but Postgres appears to use $1
// Bind variable string to use when forming SQL statements
// in many dbs it is "?".
//
// i is a zero based index of the bind variable in this statement
//
@ -72,7 +73,7 @@ type Dialect interface {
// the dialect can handle automatic assignment of more than just
// integers, see TargetedAutoIncrInserter.
type IntegerAutoIncrInserter interface {
InsertAutoIncr(exec SqlExecutor, insertSql string, params ...interface{}) (int64, error)
InsertAutoIncr(ctx context.Context, exec SqlExecutor, insertSql string, params ...interface{}) (int64, error)
}
// TargetedAutoIncrInserter is implemented by dialects that can
@ -83,7 +84,7 @@ type TargetedAutoIncrInserter interface {
// automatically generated primary key directly to the passed in
// target. The target should be a pointer to the primary key
// field of the value being inserted.
InsertAutoIncrToTarget(exec SqlExecutor, insertSql string, target interface{}, params ...interface{}) error
InsertAutoIncrToTarget(ctx context.Context, xec SqlExecutor, insertSql string, target interface{}, params ...interface{}) error
}
// TargetQueryInserter is implemented by dialects that can perform
@ -93,11 +94,11 @@ type TargetQueryInserter interface {
// TargetQueryInserter runs an insert operation and assigns the
// automatically generated primary key retrived by the query
// extracted from the GeneratedIdQuery field of the id column.
InsertQueryToTarget(exec SqlExecutor, insertSql, idSql string, target interface{}, params ...interface{}) error
InsertQueryToTarget(ctx context.Context, xec SqlExecutor, insertSql, idSql string, target interface{}, params ...interface{}) error
}
func standardInsertAutoIncr(exec SqlExecutor, insertSql string, params ...interface{}) (int64, error) {
res, err := exec.Exec(insertSql, params...)
func standardInsertAutoIncr(ctx context.Context, exec SqlExecutor, insertSql string, params ...interface{}) (int64, error) {
res, err := exec.ExecContext(ctx, insertSql, params...)
if err != nil {
return 0, err
}

View File

@ -2,9 +2,10 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gorp
package borp
import (
"context"
"fmt"
"reflect"
"strings"
@ -140,8 +141,8 @@ func (d MySQLDialect) BindVar(i int) string {
return "?"
}
func (d MySQLDialect) InsertAutoIncr(exec SqlExecutor, insertSql string, params ...interface{}) (int64, error) {
return standardInsertAutoIncr(exec, insertSql, params...)
func (d MySQLDialect) InsertAutoIncr(ctx context.Context, exec SqlExecutor, insertSql string, params ...interface{}) (int64, error) {
return standardInsertAutoIncr(ctx, exec, insertSql, params...)
}
func (d MySQLDialect) QuoteField(f string) string {

View File

@ -2,9 +2,10 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gorp
package borp
import (
"context"
"fmt"
"reflect"
)
@ -86,8 +87,8 @@ func (d SqliteDialect) BindVar(i int) string {
return "?"
}
func (d SqliteDialect) InsertAutoIncr(exec SqlExecutor, insertSql string, params ...interface{}) (int64, error) {
return standardInsertAutoIncr(exec, insertSql, params...)
func (d SqliteDialect) InsertAutoIncr(ctx context.Context, exec SqlExecutor, insertSql string, params ...interface{}) (int64, error) {
return standardInsertAutoIncr(ctx, exec, insertSql, params...)
}
func (d SqliteDialect) QuoteField(f string) string {

87
vendor/github.com/letsencrypt/borp/doc.go generated vendored Normal file
View File

@ -0,0 +1,87 @@
// Copyright 2012 James Cooper. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// Package borp provides a simple way to marshal Go structs to and from
// SQL databases. It uses the database/sql package, and should work with any
// compliant database/sql driver.
//
// Source code, additional documentation, and examples:
// https://github.com/letsencrypt/borp
//
// # Query Parameters
//
// Borp's Exec, Select*, Query, and QueryRow methods accept placeholder
// parameters in the query, to be filled from the args parameters to these
// functions. Borp supports some additional styles for placeholder parameters:
//
// # Named Bind Parameters
//
// For the Exec and Select* methods on DbMap and Transaction, Borp supports
// named bind parameters. To use named bind parameters, instead of a list of
// parameters, pass a single `map[string]interface{}` to these functions. And
// instead of using ? in the query, use placeholder parameters of the form :word.
// Before running the query, Borp will bind each named placeholder parameter to the
// corresponding value found by looking up "word" in the map.
//
// Example:
//
// _, err := dbm.Select(&dest, "select * from Foo where name = :name and age = :age",
// map[string]interface{}{
// "name": "Rob",
// "age": 31,
// })
//
// # Expanding Slices
//
// If you set the ExpandSlices field of DbMap to true, placeholders that bind to
// slices will be handled specially. Borp will modify the query, adding more
// placeholders to match the number of entries in the slice.
//
// For example, given the scenario bellow:
//
// dbmap.Select(&output, "SELECT 1 FROM example WHERE id IN (:IDs)", map[string]interface{}{
// "IDs": []int64{1, 2, 3},
// })
//
// The executed query would be:
//
// SELECT 1 FROM example WHERE id IN (:IDs0,:IDs1,:IDs2)
//
// With the mapper:
//
// map[string]interface{}{
// "IDs": []int64{1, 2, 3},
// "IDs0": int64(1),
// "IDs1": int64(2),
// "IDs2": int64(3),
// }
//
// It is also flexible for custom slice types. The value just need to
// implement stringer or numberer interfaces.
//
// type CustomValue string
//
// const (
// CustomValueHey CustomValue = "hey"
// CustomValueOh CustomValue = "oh"
// )
//
// type CustomValues []CustomValue
//
// func (c CustomValues) ToStringSlice() []string {
// values := make([]string, len(c))
// for i := range c {
// values[i] = string(c[i])
// }
// return values
// }
//
// func query() {
// // ...
// result, err := dbmap.Select(&output, "SELECT 1 FROM example WHERE value IN (:Values)", map[string]interface{}{
// "Values": CustomValues([]CustomValue{CustomValueHey}),
// })
// // ...
// }
package borp

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gorp
package borp
import (
"fmt"

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gorp
package borp
import (
"context"
@ -15,30 +15,6 @@ import (
"time"
)
// OracleString (empty string is null)
// TODO: move to dialect/oracle?, rename to String?
type OracleString struct {
sql.NullString
}
// Scan implements the Scanner interface.
func (os *OracleString) Scan(value interface{}) error {
if value == nil {
os.String, os.Valid = "", false
return nil
}
os.Valid = true
return os.NullString.Scan(value)
}
// Value implements the driver Valuer interface.
func (os OracleString) Value() (driver.Value, error) {
if !os.Valid || os.String == "" {
return nil, nil
}
return os.String, nil
}
// SqlTyper is a type that returns its database type. Most of the
// time, the type can just use "database/sql/driver".Valuer; but when
// it returns nil for its empty value, it needs to implement SqlTyper
@ -61,7 +37,6 @@ func (nt *dummyField) Scan(value interface{}) error {
return nil
}
var zeroVal reflect.Value
var versFieldConst = "[gorp_ver_field]"
// The TypeConverter interface provides a way to map a value of one
@ -90,22 +65,28 @@ type TypeConverter interface {
// See the DbMap function docs for each of the functions below for more
// information.
type SqlExecutor interface {
WithContext(ctx context.Context) SqlExecutor
Get(i interface{}, keys ...interface{}) (interface{}, error)
Insert(list ...interface{}) error
Update(list ...interface{}) (int64, error)
Delete(list ...interface{}) (int64, error)
Exec(query string, args ...interface{}) (sql.Result, error)
Select(i interface{}, query string, args ...interface{}) ([]interface{}, error)
SelectInt(query string, args ...interface{}) (int64, error)
SelectNullInt(query string, args ...interface{}) (sql.NullInt64, error)
SelectFloat(query string, args ...interface{}) (float64, error)
SelectNullFloat(query string, args ...interface{}) (sql.NullFloat64, error)
SelectStr(query string, args ...interface{}) (string, error)
SelectNullStr(query string, args ...interface{}) (sql.NullString, error)
SelectOne(holder interface{}, query string, args ...interface{}) error
Query(query string, args ...interface{}) (*sql.Rows, error)
QueryRow(query string, args ...interface{}) *sql.Row
Get(ctx context.Context, i interface{}, keys ...interface{}) (interface{}, error)
Insert(ctx context.Context, list ...interface{}) error
Update(ctx context.Context, list ...interface{}) (int64, error)
Delete(ctx context.Context, list ...interface{}) (int64, error)
Select(ctx context.Context, i interface{}, query string, args ...interface{}) ([]interface{}, error)
SelectInt(ctx context.Context, query string, args ...interface{}) (int64, error)
SelectNullInt(ctx context.Context, query string, args ...interface{}) (sql.NullInt64, error)
SelectFloat(ctx context.Context, query string, args ...interface{}) (float64, error)
SelectNullFloat(ctx context.Context, query string, args ...interface{}) (sql.NullFloat64, error)
SelectStr(ctx context.Context, query string, args ...interface{}) (string, error)
SelectNullStr(ctx context.Context, query string, args ...interface{}) (sql.NullString, error)
SelectOne(ctx context.Context, holder interface{}, query string, args ...interface{}) error
// These method signatures are shared with *sql.DB.
// Stylistically, `Context` in the name is redundant. It is the future, everything takes a context.
// But since these three functions delegate to functions of the same name on *sql.DB, it would be
// a little confusing if we had, e.g. `Exec` (taking a context), which delegates to
// `sql.DB.ExecContext` (taking a context) as opposed to `sql.DB.Exec` (taking no context).
// So we don't bother with `Context` in the name for Get, Insert, etc., but we do for these.
ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row
}
// DynamicTable allows the users of gorp to dynamically
@ -156,14 +137,21 @@ func argsString(args ...interface{}) string {
// Calls the Exec function on the executor, but attempts to expand any eligible named
// query arguments first.
func maybeExpandNamedQueryAndExec(e SqlExecutor, query string, args ...interface{}) (sql.Result, error) {
func maybeExpandNamedQueryAndExec(ctx context.Context, e SqlExecutor, query string, args ...interface{}) (sql.Result, error) {
dbMap := extractDbMap(e)
if len(args) == 1 {
query, args = maybeExpandNamedQuery(dbMap, query, args)
}
return exec(e, query, args...)
switch m := e.(type) {
case *DbMap:
return m.Db.ExecContext(ctx, query, args...)
case *Transaction:
return m.tx.ExecContext(ctx, query, args...)
default:
return nil, fmt.Errorf("gorp: unknown SqlExecutor type: %T", m)
}
}
func extractDbMap(e SqlExecutor) *DbMap {
@ -176,29 +164,6 @@ func extractDbMap(e SqlExecutor) *DbMap {
return nil
}
// executor exposes the sql.DB and sql.Tx functions so that it can be used
// on internal functions that need to be agnostic to the underlying object.
type executor interface {
Exec(query string, args ...interface{}) (sql.Result, error)
Prepare(query string) (*sql.Stmt, error)
QueryRow(query string, args ...interface{}) *sql.Row
Query(query string, args ...interface{}) (*sql.Rows, error)
ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)
QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row
QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
}
func extractExecutorAndContext(e SqlExecutor) (executor, context.Context) {
switch m := e.(type) {
case *DbMap:
return m.Db, m.ctx
case *Transaction:
return m.tx, m.ctx
}
return nil, nil
}
// maybeExpandNamedQuery checks the given arg to see if it's eligible to be used
// as input to a named query. If so, it rewrites the query to use
// dialect-dependent bindvars and instantiates the corresponding slice of
@ -311,12 +276,13 @@ func fieldByName(val reflect.Value, fieldName string) *reflect.Value {
// try to find field by exact match
f := val.FieldByName(fieldName)
var zeroVal reflect.Value
if f != zeroVal {
return &f
}
// try to find by case insensitive match - only the Postgres driver
// seems to require this - in the case where columns are aliased in the sql
// try to find by case insensitive match in the case where columns are
// aliased in the sql
fieldNameL := strings.ToLower(fieldName)
fieldCount := val.NumField()
t := val.Type()
@ -387,7 +353,7 @@ func tableFor(m *DbMap, t reflect.Type, i interface{}) (*foundTable, error) {
return &foundTable{table: table}, nil
}
func get(m *DbMap, exec SqlExecutor, i interface{},
func get(ctx context.Context, m *DbMap, exec SqlExecutor, i interface{},
keys ...interface{}) (interface{}, error) {
t, err := toType(i)
@ -427,7 +393,7 @@ func get(m *DbMap, exec SqlExecutor, i interface{},
dest[x] = target
}
row := exec.QueryRow(plan.query, keys...)
row := exec.QueryRowContext(ctx, plan.query, keys...)
err = row.Scan(dest...)
if err != nil {
if err == sql.ErrNoRows {
@ -453,7 +419,7 @@ func get(m *DbMap, exec SqlExecutor, i interface{},
return v.Interface(), nil
}
func delete(m *DbMap, exec SqlExecutor, list ...interface{}) (int64, error) {
func delete(ctx context.Context, m *DbMap, exec SqlExecutor, list ...interface{}) (int64, error) {
count := int64(0)
for _, ptr := range list {
table, elem, err := m.tableForPointer(ptr, true)
@ -474,7 +440,7 @@ func delete(m *DbMap, exec SqlExecutor, list ...interface{}) (int64, error) {
return -1, err
}
res, err := exec.Exec(bi.query, bi.args...)
res, err := exec.ExecContext(ctx, bi.query, bi.args...)
if err != nil {
return -1, err
}
@ -484,7 +450,7 @@ func delete(m *DbMap, exec SqlExecutor, list ...interface{}) (int64, error) {
}
if rows == 0 && bi.existingVersion > 0 {
return lockError(m, exec, table.TableName,
return lockError(ctx, m, exec, table.TableName,
bi.existingVersion, elem, bi.keys...)
}
@ -501,7 +467,7 @@ func delete(m *DbMap, exec SqlExecutor, list ...interface{}) (int64, error) {
return count, nil
}
func update(m *DbMap, exec SqlExecutor, colFilter ColumnFilter, list ...interface{}) (int64, error) {
func update(ctx context.Context, m *DbMap, exec SqlExecutor, colFilter ColumnFilter, list ...interface{}) (int64, error) {
count := int64(0)
for _, ptr := range list {
table, elem, err := m.tableForPointer(ptr, true)
@ -522,7 +488,7 @@ func update(m *DbMap, exec SqlExecutor, colFilter ColumnFilter, list ...interfac
return -1, err
}
res, err := exec.Exec(bi.query, bi.args...)
res, err := exec.ExecContext(ctx, bi.query, bi.args...)
if err != nil {
return -1, err
}
@ -533,7 +499,7 @@ func update(m *DbMap, exec SqlExecutor, colFilter ColumnFilter, list ...interfac
}
if rows == 0 && bi.existingVersion > 0 {
return lockError(m, exec, table.TableName,
return lockError(ctx, m, exec, table.TableName,
bi.existingVersion, elem, bi.keys...)
}
@ -553,7 +519,7 @@ func update(m *DbMap, exec SqlExecutor, colFilter ColumnFilter, list ...interfac
return count, nil
}
func insert(m *DbMap, exec SqlExecutor, list ...interface{}) error {
func insert(ctx context.Context, m *DbMap, exec SqlExecutor, list ...interface{}) error {
for _, ptr := range list {
table, elem, err := m.tableForPointer(ptr, false)
if err != nil {
@ -577,7 +543,7 @@ func insert(m *DbMap, exec SqlExecutor, list ...interface{}) error {
f := elem.FieldByName(bi.autoIncrFieldName)
switch inserter := m.Dialect.(type) {
case IntegerAutoIncrInserter:
id, err := inserter.InsertAutoIncr(exec, bi.query, bi.args...)
id, err := inserter.InsertAutoIncr(ctx, exec, bi.query, bi.args...)
if err != nil {
return err
}
@ -590,7 +556,7 @@ func insert(m *DbMap, exec SqlExecutor, list ...interface{}) error {
return fmt.Errorf("gorp: cannot set autoincrement value on non-Int field. SQL=%s autoIncrIdx=%d autoIncrFieldName=%s", bi.query, bi.autoIncrIdx, bi.autoIncrFieldName)
}
case TargetedAutoIncrInserter:
err := inserter.InsertAutoIncrToTarget(exec, bi.query, f.Addr().Interface(), bi.args...)
err := inserter.InsertAutoIncrToTarget(ctx, exec, bi.query, f.Addr().Interface(), bi.args...)
if err != nil {
return err
}
@ -599,7 +565,7 @@ func insert(m *DbMap, exec SqlExecutor, list ...interface{}) error {
if idQuery == "" {
return fmt.Errorf("gorp: cannot set %s value if its ColumnMap.GeneratedIdQuery is empty", bi.autoIncrFieldName)
}
err := inserter.InsertQueryToTarget(exec, bi.query, idQuery, f.Addr().Interface(), bi.args...)
err := inserter.InsertQueryToTarget(ctx, exec, bi.query, idQuery, f.Addr().Interface(), bi.args...)
if err != nil {
return err
}
@ -607,7 +573,7 @@ func insert(m *DbMap, exec SqlExecutor, list ...interface{}) error {
return fmt.Errorf("gorp: cannot use autoincrement fields on dialects that do not implement an autoincrementing interface")
}
} else {
_, err := exec.Exec(bi.query, bi.args...)
_, err := exec.ExecContext(ctx, bi.query, bi.args...)
if err != nil {
return err
}
@ -622,51 +588,3 @@ func insert(m *DbMap, exec SqlExecutor, list ...interface{}) error {
}
return nil
}
func exec(e SqlExecutor, query string, args ...interface{}) (sql.Result, error) {
executor, ctx := extractExecutorAndContext(e)
if ctx != nil {
return executor.ExecContext(ctx, query, args...)
}
return executor.Exec(query, args...)
}
func prepare(e SqlExecutor, query string) (*sql.Stmt, error) {
executor, ctx := extractExecutorAndContext(e)
if ctx != nil {
return executor.PrepareContext(ctx, query)
}
return executor.Prepare(query)
}
func queryRow(e SqlExecutor, query string, args ...interface{}) *sql.Row {
executor, ctx := extractExecutorAndContext(e)
if ctx != nil {
return executor.QueryRowContext(ctx, query, args...)
}
return executor.QueryRow(query, args...)
}
func query(e SqlExecutor, query string, args ...interface{}) (*sql.Rows, error) {
executor, ctx := extractExecutorAndContext(e)
if ctx != nil {
return executor.QueryContext(ctx, query, args...)
}
return executor.Query(query, args...)
}
func begin(m *DbMap) (*sql.Tx, error) {
if m.ctx != nil {
return m.Db.BeginTx(m.ctx, nil)
}
return m.Db.Begin()
}

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gorp
package borp
//++ TODO v2-phase3: HasPostGet => PostGetter, HasPostDelete => PostDeleter, etc.

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gorp
package borp
// IndexMap represents a mapping between a Go struct field and a single
// index in a table.
@ -17,7 +17,6 @@ type IndexMap struct {
Unique bool
// Index type supported by Dialect
// Postgres: B-tree, Hash, GiST and GIN.
// Mysql: Btree, Hash.
// Sqlite: nil.
IndexType string
@ -29,7 +28,6 @@ type IndexMap struct {
// Rename allows you to specify the index name in the table
//
// Example: table.IndMap("customer_test_idx").Rename("customer_idx")
//
func (idx *IndexMap) Rename(indname string) *IndexMap {
idx.IndexName = indname
return idx

View File

@ -2,9 +2,10 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gorp
package borp
import (
"context"
"fmt"
"reflect"
)
@ -39,11 +40,11 @@ func (e OptimisticLockError) Error() string {
return fmt.Sprintf("gorp: OptimisticLockError no row found for table=%s keys=%v", e.TableName, e.Keys)
}
func lockError(m *DbMap, exec SqlExecutor, tableName string,
func lockError(ctx context.Context, m *DbMap, exec SqlExecutor, tableName string,
existingVer int64, elem reflect.Value,
keys ...interface{}) (int64, error) {
existing, err := get(m, exec, elem.Interface(), keys...)
existing, err := get(ctx, m, exec, elem.Interface(), keys...)
if err != nil {
return -1, err
}

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gorp
package borp
import "fmt"

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gorp
package borp
import (
"database/sql/driver"

View File

@ -2,9 +2,10 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gorp
package borp
import (
"context"
"database/sql"
"fmt"
"reflect"
@ -13,9 +14,9 @@ import (
// SelectInt executes the given query, which should be a SELECT statement for a single
// integer column, and returns the value of the first row returned. If no rows are
// found, zero is returned.
func SelectInt(e SqlExecutor, query string, args ...interface{}) (int64, error) {
func SelectInt(ctx context.Context, e SqlExecutor, query string, args ...interface{}) (int64, error) {
var h int64
err := selectVal(e, &h, query, args...)
err := selectVal(ctx, e, &h, query, args...)
if err != nil && err != sql.ErrNoRows {
return 0, err
}
@ -25,9 +26,9 @@ func SelectInt(e SqlExecutor, query string, args ...interface{}) (int64, error)
// SelectNullInt executes the given query, which should be a SELECT statement for a single
// integer column, and returns the value of the first row returned. If no rows are
// found, the empty sql.NullInt64 value is returned.
func SelectNullInt(e SqlExecutor, query string, args ...interface{}) (sql.NullInt64, error) {
func SelectNullInt(ctx context.Context, e SqlExecutor, query string, args ...interface{}) (sql.NullInt64, error) {
var h sql.NullInt64
err := selectVal(e, &h, query, args...)
err := selectVal(ctx, e, &h, query, args...)
if err != nil && err != sql.ErrNoRows {
return h, err
}
@ -37,9 +38,9 @@ func SelectNullInt(e SqlExecutor, query string, args ...interface{}) (sql.NullIn
// SelectFloat executes the given query, which should be a SELECT statement for a single
// float column, and returns the value of the first row returned. If no rows are
// found, zero is returned.
func SelectFloat(e SqlExecutor, query string, args ...interface{}) (float64, error) {
func SelectFloat(ctx context.Context, e SqlExecutor, query string, args ...interface{}) (float64, error) {
var h float64
err := selectVal(e, &h, query, args...)
err := selectVal(ctx, e, &h, query, args...)
if err != nil && err != sql.ErrNoRows {
return 0, err
}
@ -49,9 +50,9 @@ func SelectFloat(e SqlExecutor, query string, args ...interface{}) (float64, err
// SelectNullFloat executes the given query, which should be a SELECT statement for a single
// float column, and returns the value of the first row returned. If no rows are
// found, the empty sql.NullInt64 value is returned.
func SelectNullFloat(e SqlExecutor, query string, args ...interface{}) (sql.NullFloat64, error) {
func SelectNullFloat(ctx context.Context, e SqlExecutor, query string, args ...interface{}) (sql.NullFloat64, error) {
var h sql.NullFloat64
err := selectVal(e, &h, query, args...)
err := selectVal(ctx, e, &h, query, args...)
if err != nil && err != sql.ErrNoRows {
return h, err
}
@ -61,9 +62,9 @@ func SelectNullFloat(e SqlExecutor, query string, args ...interface{}) (sql.Null
// SelectStr executes the given query, which should be a SELECT statement for a single
// char/varchar column, and returns the value of the first row returned. If no rows are
// found, an empty string is returned.
func SelectStr(e SqlExecutor, query string, args ...interface{}) (string, error) {
func SelectStr(ctx context.Context, e SqlExecutor, query string, args ...interface{}) (string, error) {
var h string
err := selectVal(e, &h, query, args...)
err := selectVal(ctx, e, &h, query, args...)
if err != nil && err != sql.ErrNoRows {
return "", err
}
@ -74,9 +75,9 @@ func SelectStr(e SqlExecutor, query string, args ...interface{}) (string, error)
// statement for a single char/varchar column, and returns the value
// of the first row returned. If no rows are found, the empty
// sql.NullString is returned.
func SelectNullStr(e SqlExecutor, query string, args ...interface{}) (sql.NullString, error) {
func SelectNullStr(ctx context.Context, e SqlExecutor, query string, args ...interface{}) (sql.NullString, error) {
var h sql.NullString
err := selectVal(e, &h, query, args...)
err := selectVal(ctx, e, &h, query, args...)
if err != nil && err != sql.ErrNoRows {
return h, err
}
@ -86,11 +87,10 @@ func SelectNullStr(e SqlExecutor, query string, args ...interface{}) (sql.NullSt
// SelectOne executes the given query (which should be a SELECT statement)
// and binds the result to holder, which must be a pointer.
//
// If no row is found, an error (sql.ErrNoRows specifically) will be returned
// If no row is found, an error (sql.ErrNoRows specifically) will be returned.
//
// If more than one row is found, an error will be returned.
//
func SelectOne(m *DbMap, e SqlExecutor, holder interface{}, query string, args ...interface{}) error {
func SelectOne(ctx context.Context, m *DbMap, e SqlExecutor, holder interface{}, query string, args ...interface{}) error {
t := reflect.TypeOf(holder)
if t.Kind() == reflect.Ptr {
t = t.Elem()
@ -108,7 +108,7 @@ func SelectOne(m *DbMap, e SqlExecutor, holder interface{}, query string, args .
if t.Kind() == reflect.Struct {
var nonFatalErr error
list, err := hookedselect(m, e, holder, query, args...)
list, err := hookedselect(ctx, m, e, holder, query, args...)
if err != nil {
if !NonFatalError(err) { // FIXME: double negative, rename NonFatalError to FatalError
return err
@ -143,10 +143,10 @@ func SelectOne(m *DbMap, e SqlExecutor, holder interface{}, query string, args .
return nonFatalErr
}
return selectVal(e, holder, query, args...)
return selectVal(ctx, e, holder, query, args...)
}
func selectVal(e SqlExecutor, holder interface{}, query string, args ...interface{}) error {
func selectVal(ctx context.Context, e SqlExecutor, holder interface{}, query string, args ...interface{}) error {
if len(args) == 1 {
switch m := e.(type) {
case *DbMap:
@ -155,7 +155,7 @@ func selectVal(e SqlExecutor, holder interface{}, query string, args ...interfac
query, args = maybeExpandNamedQuery(m.dbmap, query, args)
}
}
rows, err := e.Query(query, args...)
rows, err := e.QueryContext(ctx, query, args...)
if err != nil {
return err
}
@ -168,15 +168,20 @@ func selectVal(e SqlExecutor, holder interface{}, query string, args ...interfac
return sql.ErrNoRows
}
return rows.Scan(holder)
err = rows.Scan(holder)
if err != nil {
return err
}
func hookedselect(m *DbMap, exec SqlExecutor, i interface{}, query string,
return rows.Close()
}
func hookedselect(ctx context.Context, m *DbMap, exec SqlExecutor, i interface{}, query string,
args ...interface{}) ([]interface{}, error) {
var nonFatalErr error
list, err := rawselect(m, exec, i, query, args...)
list, err := rawselect(ctx, m, exec, i, query, args...)
if err != nil {
if !NonFatalError(err) {
return nil, err
@ -208,7 +213,7 @@ func hookedselect(m *DbMap, exec SqlExecutor, i interface{}, query string,
return list, nonFatalErr
}
func rawselect(m *DbMap, exec SqlExecutor, i interface{}, query string,
func rawselect(ctx context.Context, m *DbMap, exec SqlExecutor, i interface{}, query string,
args ...interface{}) ([]interface{}, error) {
var (
appendToSlice = false // Write results to i directly?
@ -251,7 +256,7 @@ func rawselect(m *DbMap, exec SqlExecutor, i interface{}, query string,
}
// Run the query
rows, err := exec.Query(query, args...)
rows, err := exec.QueryContext(ctx, query, args...)
if err != nil {
return nil, err
}
@ -351,6 +356,11 @@ func rawselect(m *DbMap, exec SqlExecutor, i interface{}, query string,
}
}
err = rows.Close()
if err != nil {
return nil, err
}
if appendToSlice && sliceValue.IsNil() {
sliceValue.Set(reflect.MakeSlice(sliceValue.Type(), 0, 0))
}

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gorp
package borp
import (
"bytes"
@ -47,7 +47,6 @@ func (t *TableMap) ResetSql() {
// Automatically calls ResetSql() to ensure SQL statements are regenerated.
//
// Panics if isAutoIncr is true, and fieldNames length != 1
//
func (t *TableMap) SetKeys(isAutoIncr bool, fieldNames ...string) *TableMap {
if isAutoIncr && len(fieldNames) != 1 {
panic(fmt.Sprintf(
@ -73,17 +72,13 @@ func (t *TableMap) SetKeys(isAutoIncr bool, fieldNames ...string) *TableMap {
// Automatically calls ResetSql() to ensure SQL statements are regenerated.
//
// Panics if fieldNames length < 2.
//
func (t *TableMap) SetUniqueTogether(fieldNames ...string) *TableMap {
if len(fieldNames) < 2 {
panic(fmt.Sprintf(
"gorp: SetUniqueTogether: must provide at least two fieldNames to set uniqueness constraint."))
panic("gorp: SetUniqueTogether: must provide at least two fieldNames to set uniqueness constraint.")
}
columns := make([]string, 0, len(fieldNames))
for _, name := range fieldNames {
columns = append(columns, name)
}
columns = append(columns, fieldNames...)
for _, existingColumns := range t.uniqueTogether {
if equal(existingColumns, columns) {
@ -135,7 +130,6 @@ func (t *TableMap) IdxMap(field string) *IndexMap {
// Function will panic if one of the given for index columns does not exists
//
// Automatically calls ResetSql() to ensure SQL statements are regenerated.
//
func (t *TableMap) AddIndex(name string, idxtype string, columns []string) *IndexMap {
// check if we have a index with this name already
for _, idx := range t.indexes {

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gorp
package borp
import (
"bytes"

View File

@ -6,11 +6,6 @@
echo "Running unit tests"
go test -race
echo "Testing against postgres"
export GORP_TEST_DSN="host=postgres user=gorptest password=gorptest dbname=gorptest sslmode=disable"
export GORP_TEST_DIALECT=postgres
go test -tags integration $GOBUILDFLAG $@ .
echo "Testing against sqlite"
export GORP_TEST_DSN=/tmp/gorptest.bin
export GORP_TEST_DIALECT=sqlite

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gorp
package borp
import (
"context"
@ -15,55 +15,47 @@ import (
// of that transaction. Transactions should be terminated with
// a call to Commit() or Rollback()
type Transaction struct {
ctx context.Context
dbmap *DbMap
tx *sql.Tx
closed bool
}
func (t *Transaction) WithContext(ctx context.Context) SqlExecutor {
copy := &Transaction{}
*copy = *t
copy.ctx = ctx
return copy
}
// Insert has the same behavior as DbMap.Insert(), but runs in a transaction.
func (t *Transaction) Insert(list ...interface{}) error {
return insert(t.dbmap, t, list...)
func (t *Transaction) Insert(ctx context.Context, list ...interface{}) error {
return insert(ctx, t.dbmap, t, list...)
}
// Update had the same behavior as DbMap.Update(), but runs in a transaction.
func (t *Transaction) Update(list ...interface{}) (int64, error) {
return update(t.dbmap, t, nil, list...)
func (t *Transaction) Update(ctx context.Context, list ...interface{}) (int64, error) {
return update(ctx, t.dbmap, t, nil, list...)
}
// UpdateColumns had the same behavior as DbMap.UpdateColumns(), but runs in a transaction.
func (t *Transaction) UpdateColumns(filter ColumnFilter, list ...interface{}) (int64, error) {
return update(t.dbmap, t, filter, list...)
func (t *Transaction) UpdateColumns(ctx context.Context, filter ColumnFilter, list ...interface{}) (int64, error) {
return update(ctx, t.dbmap, t, filter, list...)
}
// Delete has the same behavior as DbMap.Delete(), but runs in a transaction.
func (t *Transaction) Delete(list ...interface{}) (int64, error) {
return delete(t.dbmap, t, list...)
func (t *Transaction) Delete(ctx context.Context, list ...interface{}) (int64, error) {
return delete(ctx, t.dbmap, t, list...)
}
// Get has the same behavior as DbMap.Get(), but runs in a transaction.
func (t *Transaction) Get(i interface{}, keys ...interface{}) (interface{}, error) {
return get(t.dbmap, t, i, keys...)
func (t *Transaction) Get(ctx context.Context, i interface{}, keys ...interface{}) (interface{}, error) {
return get(ctx, t.dbmap, t, i, keys...)
}
// Select has the same behavior as DbMap.Select(), but runs in a transaction.
func (t *Transaction) Select(i interface{}, query string, args ...interface{}) ([]interface{}, error) {
func (t *Transaction) Select(ctx context.Context, i interface{}, query string, args ...interface{}) ([]interface{}, error) {
if t.dbmap.ExpandSliceArgs {
expandSliceArgs(&query, args...)
}
return hookedselect(t.dbmap, t, i, query, args...)
return hookedselect(ctx, t.dbmap, t, i, query, args...)
}
// Exec has the same behavior as DbMap.Exec(), but runs in a transaction.
func (t *Transaction) Exec(query string, args ...interface{}) (sql.Result, error) {
// ExecContext has the same behavior as DbMap.ExecContext(), but runs in a transaction.
func (t *Transaction) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
if t.dbmap.ExpandSliceArgs {
expandSliceArgs(&query, args...)
}
@ -72,70 +64,70 @@ func (t *Transaction) Exec(query string, args ...interface{}) (sql.Result, error
now := time.Now()
defer t.dbmap.trace(now, query, args...)
}
return maybeExpandNamedQueryAndExec(t, query, args...)
return maybeExpandNamedQueryAndExec(ctx, t, query, args...)
}
// SelectInt is a convenience wrapper around the gorp.SelectInt function.
func (t *Transaction) SelectInt(query string, args ...interface{}) (int64, error) {
// SelectInt is a convenience wrapper around the borp.SelectInt function.
func (t *Transaction) SelectInt(ctx context.Context, query string, args ...interface{}) (int64, error) {
if t.dbmap.ExpandSliceArgs {
expandSliceArgs(&query, args...)
}
return SelectInt(t, query, args...)
return SelectInt(ctx, t, query, args...)
}
// SelectNullInt is a convenience wrapper around the gorp.SelectNullInt function.
func (t *Transaction) SelectNullInt(query string, args ...interface{}) (sql.NullInt64, error) {
// SelectNullInt is a convenience wrapper around the borp.SelectNullInt function.
func (t *Transaction) SelectNullInt(ctx context.Context, query string, args ...interface{}) (sql.NullInt64, error) {
if t.dbmap.ExpandSliceArgs {
expandSliceArgs(&query, args...)
}
return SelectNullInt(t, query, args...)
return SelectNullInt(ctx, t, query, args...)
}
// SelectFloat is a convenience wrapper around the gorp.SelectFloat function.
func (t *Transaction) SelectFloat(query string, args ...interface{}) (float64, error) {
// SelectFloat is a convenience wrapper around the borp.SelectFloat function.
func (t *Transaction) SelectFloat(ctx context.Context, query string, args ...interface{}) (float64, error) {
if t.dbmap.ExpandSliceArgs {
expandSliceArgs(&query, args...)
}
return SelectFloat(t, query, args...)
return SelectFloat(ctx, t, query, args...)
}
// SelectNullFloat is a convenience wrapper around the gorp.SelectNullFloat function.
func (t *Transaction) SelectNullFloat(query string, args ...interface{}) (sql.NullFloat64, error) {
// SelectNullFloat is a convenience wrapper around the borp.SelectNullFloat function.
func (t *Transaction) SelectNullFloat(ctx context.Context, query string, args ...interface{}) (sql.NullFloat64, error) {
if t.dbmap.ExpandSliceArgs {
expandSliceArgs(&query, args...)
}
return SelectNullFloat(t, query, args...)
return SelectNullFloat(ctx, t, query, args...)
}
// SelectStr is a convenience wrapper around the gorp.SelectStr function.
func (t *Transaction) SelectStr(query string, args ...interface{}) (string, error) {
// SelectStr is a convenience wrapper around the borp.SelectStr function.
func (t *Transaction) SelectStr(ctx context.Context, query string, args ...interface{}) (string, error) {
if t.dbmap.ExpandSliceArgs {
expandSliceArgs(&query, args...)
}
return SelectStr(t, query, args...)
return SelectStr(ctx, t, query, args...)
}
// SelectNullStr is a convenience wrapper around the gorp.SelectNullStr function.
func (t *Transaction) SelectNullStr(query string, args ...interface{}) (sql.NullString, error) {
// SelectNullStr is a convenience wrapper around the borp.SelectNullStr function.
func (t *Transaction) SelectNullStr(ctx context.Context, query string, args ...interface{}) (sql.NullString, error) {
if t.dbmap.ExpandSliceArgs {
expandSliceArgs(&query, args...)
}
return SelectNullStr(t, query, args...)
return SelectNullStr(ctx, t, query, args...)
}
// SelectOne is a convenience wrapper around the gorp.SelectOne function.
func (t *Transaction) SelectOne(holder interface{}, query string, args ...interface{}) error {
// SelectOne is a convenience wrapper around the borp.SelectOne function.
func (t *Transaction) SelectOne(ctx context.Context, holder interface{}, query string, args ...interface{}) error {
if t.dbmap.ExpandSliceArgs {
expandSliceArgs(&query, args...)
}
return SelectOne(t.dbmap, t, holder, query, args...)
return SelectOne(ctx, t.dbmap, t, holder, query, args...)
}
// Commit commits the underlying database transaction.
@ -169,52 +161,52 @@ func (t *Transaction) Rollback() error {
// Savepoint creates a savepoint with the given name. The name is interpolated
// directly into the SQL SAVEPOINT statement, so you must sanitize it if it is
// derived from user input.
func (t *Transaction) Savepoint(name string) error {
func (t *Transaction) Savepoint(ctx context.Context, name string) error {
query := "savepoint " + t.dbmap.Dialect.QuoteField(name)
if t.dbmap.logger != nil {
now := time.Now()
defer t.dbmap.trace(now, query, nil)
}
_, err := exec(t, query)
_, err := t.ExecContext(ctx, query)
return err
}
// RollbackToSavepoint rolls back to the savepoint with the given name. The
// name is interpolated directly into the SQL SAVEPOINT statement, so you must
// sanitize it if it is derived from user input.
func (t *Transaction) RollbackToSavepoint(savepoint string) error {
func (t *Transaction) RollbackToSavepoint(ctx context.Context, savepoint string) error {
query := "rollback to savepoint " + t.dbmap.Dialect.QuoteField(savepoint)
if t.dbmap.logger != nil {
now := time.Now()
defer t.dbmap.trace(now, query, nil)
}
_, err := exec(t, query)
_, err := t.ExecContext(ctx, query)
return err
}
// ReleaseSavepint releases the savepoint with the given name. The name is
// interpolated directly into the SQL SAVEPOINT statement, so you must sanitize
// it if it is derived from user input.
func (t *Transaction) ReleaseSavepoint(savepoint string) error {
func (t *Transaction) ReleaseSavepoint(ctx context.Context, savepoint string) error {
query := "release savepoint " + t.dbmap.Dialect.QuoteField(savepoint)
if t.dbmap.logger != nil {
now := time.Now()
defer t.dbmap.trace(now, query, nil)
}
_, err := exec(t, query)
_, err := t.ExecContext(ctx, query)
return err
}
// Prepare has the same behavior as DbMap.Prepare(), but runs in a transaction.
func (t *Transaction) Prepare(query string) (*sql.Stmt, error) {
func (t *Transaction) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) {
if t.dbmap.logger != nil {
now := time.Now()
defer t.dbmap.trace(now, query, nil)
}
return prepare(t, query)
return t.tx.PrepareContext(ctx, query)
}
func (t *Transaction) QueryRow(query string, args ...interface{}) *sql.Row {
func (t *Transaction) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row {
if t.dbmap.ExpandSliceArgs {
expandSliceArgs(&query, args...)
}
@ -223,10 +215,10 @@ func (t *Transaction) QueryRow(query string, args ...interface{}) *sql.Row {
now := time.Now()
defer t.dbmap.trace(now, query, args...)
}
return queryRow(t, query, args...)
return t.tx.QueryRowContext(ctx, query, args...)
}
func (t *Transaction) Query(q string, args ...interface{}) (*sql.Rows, error) {
func (t *Transaction) QueryContext(ctx context.Context, q string, args ...interface{}) (*sql.Rows, error) {
if t.dbmap.ExpandSliceArgs {
expandSliceArgs(&q, args...)
}
@ -235,5 +227,5 @@ func (t *Transaction) Query(q string, args ...interface{}) (*sql.Rows, error) {
now := time.Now()
defer t.dbmap.trace(now, q, args...)
}
return query(t, q, args...)
return t.tx.QueryContext(ctx, q, args...)
}

14
vendor/modules.txt vendored
View File

@ -132,9 +132,6 @@ github.com/eggsampler/acme/v3
# github.com/felixge/httpsnoop v1.0.3
## explicit; go 1.13
github.com/felixge/httpsnoop
# github.com/go-gorp/gorp/v3 v3.1.0
## explicit; go 1.18
github.com/go-gorp/gorp/v3
# github.com/go-logr/logr v1.2.4
## explicit; go 1.16
github.com/go-logr/logr
@ -204,6 +201,11 @@ github.com/hpcloud/tail/winfile
# github.com/jmhodges/clock v1.2.0
## explicit; go 1.17
github.com/jmhodges/clock
# github.com/kr/pretty v0.3.1
## explicit; go 1.12
# github.com/letsencrypt/borp v0.0.0-20230707160741-6cc6ce580243
## explicit; go 1.20
github.com/letsencrypt/borp
# github.com/letsencrypt/challtestsrv v1.2.1
## explicit; go 1.13
github.com/letsencrypt/challtestsrv
@ -225,6 +227,8 @@ github.com/miekg/pkcs11
# github.com/pelletier/go-toml v1.9.3
## explicit; go 1.12
github.com/pelletier/go-toml
# github.com/poy/onpar v1.1.2
## explicit; go 1.14
# github.com/prometheus/client_golang v1.14.0
## explicit; go 1.17
github.com/prometheus/client_golang/prometheus
@ -244,8 +248,6 @@ github.com/prometheus/common/model
github.com/prometheus/procfs
github.com/prometheus/procfs/internal/fs
github.com/prometheus/procfs/internal/util
# github.com/rogpeppe/go-internal v1.9.0
## explicit; go 1.17
# github.com/syndtr/goleveldb v1.0.0
## explicit
github.com/syndtr/goleveldb/leveldb
@ -513,6 +515,8 @@ google.golang.org/protobuf/types/known/fieldmaskpb
google.golang.org/protobuf/types/known/structpb
google.golang.org/protobuf/types/known/timestamppb
google.golang.org/protobuf/types/known/wrapperspb
# gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
## explicit; go 1.11
# gopkg.in/fsnotify.v1 v1.4.7
## explicit
gopkg.in/fsnotify.v1