Skip to content

Commit 99df66f

Browse files
authored
Merge pull request #1549 from bhandras/upsert-addr
tapdb: allow reinsertion of an existing address to the book
2 parents e323b9e + 531f64e commit 99df66f

File tree

6 files changed

+156
-53
lines changed

6 files changed

+156
-53
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*.dll
55
*.so
66
*.dylib
7+
.DS_Store
78
tapcli-debug
89
tapd-debug
910
/tapcli

tapdb/addrs.go

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ type (
3232
AddrQuery = sqlc.FetchAddrsParams
3333

3434
// NewAddr is a type alias for the params to create a new address.
35-
NewAddr = sqlc.InsertAddrParams
35+
NewAddr = sqlc.UpsertAddrParams
3636

3737
// Addresses is a type alias for the full address row with key locator
3838
// information.
@@ -82,6 +82,14 @@ type (
8282
AssetMeta = sqlc.FetchAssetMetaForAssetRow
8383
)
8484

85+
var (
86+
// ErrConflictingAddress is returned when an address (with the same
87+
// output key) already exists in the database and we're attempting to
88+
// re-insert it but at least one of the fields (except creation_time)
89+
// is different.
90+
ErrConflictingAddress = errors.New("failed to add conflicting address")
91+
)
92+
8593
// AddrBook is an interface that represents the storage backed needed to create
8694
// the TapAddressBook book. We need to be able to insert/fetch addresses, and
8795
// also make internal keys since each address has an internal key and a script
@@ -109,8 +117,9 @@ type AddrBook interface {
109117
FetchAddrByTaprootOutputKey(ctx context.Context,
110118
arg []byte) (AddrByTaprootOutput, error)
111119

112-
// InsertAddr inserts a new address into the database.
113-
InsertAddr(ctx context.Context, arg NewAddr) (int64, error)
120+
// UpsertAddr upserts a new address into the database returning the
121+
// primary key.
122+
UpsertAddr(ctx context.Context, arg NewAddr) (int64, error)
114123

115124
// UpsertInternalKey inserts a new or updates an existing internal key
116125
// into the database and returns the primary key.
@@ -302,7 +311,7 @@ func (t *TapAddressBook) InsertAddrs(ctx context.Context,
302311
addr.Tap.ProofCourierAddr.String(),
303312
)
304313

305-
_, err = db.InsertAddr(ctx, NewAddr{
314+
_, err = db.UpsertAddr(ctx, NewAddr{
306315
Version: int16(addr.Version),
307316
AssetVersion: int16(addr.AssetVersion),
308317
GenesisAssetID: genAssetID,
@@ -319,6 +328,10 @@ func (t *TapAddressBook) InsertAddrs(ctx context.Context,
319328
ProofCourierAddr: proofCourierAddrBytes,
320329
})
321330
if err != nil {
331+
if errors.Is(err, sql.ErrNoRows) {
332+
return ErrConflictingAddress
333+
}
334+
322335
return fmt.Errorf("unable to insert addr: %w",
323336
err)
324337
}

tapdb/addrs_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,23 @@ func TestAddressInsertion(t *testing.T) {
172172
}
173173
require.NoError(t, addrBook.InsertAddrs(ctx, addrs...))
174174

175+
// Now insert the addresses again, to test that upsert works as well.
176+
require.NoError(t, addrBook.InsertAddrs(ctx, addrs...))
177+
178+
// Changing an address' creation time should not affect the insertion
179+
// process but should not change the database either.
180+
addrTime := addrs[0].CreationTime
181+
addrs[0].CreationTime = addrTime.Add(time.Second)
182+
require.NoError(t, addrBook.InsertAddrs(ctx, addrs[0]))
183+
addrs[0].CreationTime = addrTime
184+
185+
// Now change the amount, which upon upsert should trigger an error.
186+
addrs[0].Amount += 1
187+
require.Error(t,
188+
addrBook.InsertAddrs(ctx, addrs[0]), ErrConflictingAddress,
189+
)
190+
addrs[0].Amount -= 1
191+
175192
// Now we should be able to fetch the complete set of addresses with
176193
// the query method without specifying any special params.
177194
dbAddrs, err := addrBook.QueryAddrs(ctx, address.QueryParams{})

tapdb/sqlc/addrs.sql.go

Lines changed: 79 additions & 43 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tapdb/sqlc/querier.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tapdb/sqlc/queries/addrs.sql

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,45 @@
1-
-- name: InsertAddr :one
1+
-- name: UpsertAddr :one
22
INSERT INTO addrs (
3-
version, asset_version, genesis_asset_id, group_key, script_key_id,
4-
taproot_key_id, tapscript_sibling, taproot_output_key, amount, asset_type,
5-
creation_time, proof_courier_addr
6-
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING id;
3+
version,
4+
asset_version,
5+
genesis_asset_id,
6+
group_key,
7+
script_key_id,
8+
taproot_key_id,
9+
tapscript_sibling,
10+
taproot_output_key,
11+
amount,
12+
asset_type,
13+
creation_time,
14+
proof_courier_addr
15+
) VALUES (
16+
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12
17+
)
18+
ON CONFLICT (taproot_output_key) DO UPDATE
19+
SET
20+
-- If the WHERE clause below is true (exact match on all other fields,
21+
-- except for creation_time), we set taproot_output_key to its current
22+
-- conflicting value. This is a no-op in terms of data change but allows
23+
-- RETURNING id to work on the existing row.
24+
taproot_output_key = excluded.taproot_output_key
25+
WHERE
26+
addrs.version = excluded.version
27+
AND addrs.asset_version = excluded.asset_version
28+
AND addrs.genesis_asset_id = excluded.genesis_asset_id
29+
AND (
30+
(addrs.group_key IS NULL AND excluded.group_key IS NULL)
31+
OR addrs.group_key = excluded.group_key
32+
)
33+
AND addrs.script_key_id = excluded.script_key_id
34+
AND addrs.taproot_key_id = excluded.taproot_key_id
35+
AND (
36+
(addrs.tapscript_sibling IS NULL AND excluded.tapscript_sibling IS NULL)
37+
OR addrs.tapscript_sibling = excluded.tapscript_sibling
38+
)
39+
AND addrs.amount = excluded.amount
40+
AND addrs.asset_type = excluded.asset_type
41+
AND addrs.proof_courier_addr = excluded.proof_courier_addr
42+
RETURNING id;
743

844
-- name: FetchAddrs :many
945
SELECT

0 commit comments

Comments
 (0)