diff --git a/tapdb/assets_common.go b/tapdb/assets_common.go index 34d03c8ed..17dc04916 100644 --- a/tapdb/assets_common.go +++ b/tapdb/assets_common.go @@ -70,9 +70,9 @@ type UpsertAssetStore interface { QueryAssets(context.Context, QueryAssetFilters) ([]ConfirmedAsset, error) - // InsertNewAsset inserts a new asset on disk. - InsertNewAsset(ctx context.Context, - arg sqlc.InsertNewAssetParams) (int64, error) + // UpsertAsset upserts an asset on disk. + UpsertAsset(ctx context.Context, + arg sqlc.UpsertAssetParams) (int64, error) // UpsertAssetMeta inserts a new asset meta into the DB. UpsertAssetMeta(ctx context.Context, arg NewAssetMeta) (int64, error) @@ -204,28 +204,10 @@ func upsertAssetsWithGenesis(ctx context.Context, q UpsertAssetStore, anchorUtxoID = anchorUtxoIDs[idx] } - // Check for matching assets in the database. If we find one, - // we'll just use its primary key ID, and we won't attempt to - // insert the asset again. - existingAssets, err := q.QueryAssets(ctx, QueryAssetFilters{ - AnchorUtxoID: anchorUtxoID, - GenesisID: sqlInt64(genAssetID), - ScriptKeyID: sqlInt64(scriptKeyID), - }) - if err != nil { - return 0, nil, fmt.Errorf("unable to query assets: %w", - err) - } - - if len(existingAssets) > 0 { - assetIDs[idx] = existingAssets[0].AssetPrimaryKey - continue - } - - // With all the dependent data inserted, we can now insert the + // With all the dependent data inserted, we can now upsert the // base asset information itself. - assetIDs[idx], err = q.InsertNewAsset( - ctx, sqlc.InsertNewAssetParams{ + assetIDs[idx], err = q.UpsertAsset( + ctx, sqlc.UpsertAssetParams{ GenesisID: genAssetID, Version: int32(a.Version), ScriptKeyID: scriptKeyID, diff --git a/tapdb/sqlc/assets.sql.go b/tapdb/sqlc/assets.sql.go index 5a30c91ea..b31082b39 100644 --- a/tapdb/sqlc/assets.sql.go +++ b/tapdb/sqlc/assets.sql.go @@ -1713,46 +1713,6 @@ func (q *Queries) InsertAssetWitness(ctx context.Context, arg InsertAssetWitness return err } -const insertNewAsset = `-- name: InsertNewAsset :one -INSERT INTO assets ( - genesis_id, version, script_key_id, asset_group_witness_id, script_version, - amount, lock_time, relative_lock_time, anchor_utxo_id, spent -) VALUES ( - $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 -) RETURNING asset_id -` - -type InsertNewAssetParams struct { - GenesisID int64 - Version int32 - ScriptKeyID int64 - AssetGroupWitnessID sql.NullInt64 - ScriptVersion int32 - Amount int64 - LockTime sql.NullInt32 - RelativeLockTime sql.NullInt32 - AnchorUtxoID sql.NullInt64 - Spent bool -} - -func (q *Queries) InsertNewAsset(ctx context.Context, arg InsertNewAssetParams) (int64, error) { - row := q.db.QueryRowContext(ctx, insertNewAsset, - arg.GenesisID, - arg.Version, - arg.ScriptKeyID, - arg.AssetGroupWitnessID, - arg.ScriptVersion, - arg.Amount, - arg.LockTime, - arg.RelativeLockTime, - arg.AnchorUtxoID, - arg.Spent, - ) - var asset_id int64 - err := row.Scan(&asset_id) - return asset_id, err -} - const newMintingBatch = `-- name: NewMintingBatch :exec INSERT INTO asset_minting_batches ( batch_state, batch_id, height_hint, creation_time_unix @@ -2190,6 +2150,57 @@ func (q *Queries) UpdateUTXOLease(ctx context.Context, arg UpdateUTXOLeaseParams return err } +const upsertAsset = `-- name: UpsertAsset :one +INSERT INTO assets ( + genesis_id, version, script_key_id, asset_group_witness_id, script_version, + amount, lock_time, relative_lock_time, anchor_utxo_id, spent +) VALUES ( + $1, $2, $3, $4, + $5, $6, $7, $8, $9, + $10 +) ON CONFLICT (anchor_utxo_id, genesis_id, script_key_id) +DO UPDATE SET + version = $2, + asset_group_witness_id = $4, + script_version = $5, + amount = $6, + lock_time = $7, + relative_lock_time = $8, + spent = $10 +RETURNING asset_id +` + +type UpsertAssetParams struct { + GenesisID int64 + Version int32 + ScriptKeyID int64 + AssetGroupWitnessID sql.NullInt64 + ScriptVersion int32 + Amount int64 + LockTime sql.NullInt32 + RelativeLockTime sql.NullInt32 + AnchorUtxoID sql.NullInt64 + Spent bool +} + +func (q *Queries) UpsertAsset(ctx context.Context, arg UpsertAssetParams) (int64, error) { + row := q.db.QueryRowContext(ctx, upsertAsset, + arg.GenesisID, + arg.Version, + arg.ScriptKeyID, + arg.AssetGroupWitnessID, + arg.ScriptVersion, + arg.Amount, + arg.LockTime, + arg.RelativeLockTime, + arg.AnchorUtxoID, + arg.Spent, + ) + var asset_id int64 + err := row.Scan(&asset_id) + return asset_id, err +} + const upsertAssetGroupKey = `-- name: UpsertAssetGroupKey :one INSERT INTO asset_groups ( tweaked_group_key, tapscript_root, internal_key_id, genesis_point_id diff --git a/tapdb/sqlc/migrations/000013_assets_unique_constraint.down.sql b/tapdb/sqlc/migrations/000013_assets_unique_constraint.down.sql new file mode 100644 index 000000000..e69de29bb diff --git a/tapdb/sqlc/migrations/000013_assets_unique_constraint.up.sql b/tapdb/sqlc/migrations/000013_assets_unique_constraint.up.sql new file mode 100644 index 000000000..495f717df --- /dev/null +++ b/tapdb/sqlc/migrations/000013_assets_unique_constraint.up.sql @@ -0,0 +1,31 @@ +-- We will apply a new unique constraint on the assets table. This constraint +-- will be on the columns (anchor_utxo_id, genesis_id, script_key_id). +-- Rows in the existing table may violate this constraint, so we need to delete +-- all but one row in each violating set of rows before applying the constraint. +WITH duplicate_rows AS ( + SELECT + asset_id, + anchor_utxo_id, + genesis_id, + script_key_id, + + -- This is the row number of the row within the set of rows that violate + -- the constraint. We will delete all rows with a row number greater + -- than 1. + ROW_NUMBER() OVER (PARTITION BY anchor_utxo_id, genesis_id, script_key_id ORDER BY asset_id) AS row_num + FROM assets +) +DELETE FROM assets +WHERE (anchor_utxo_id, genesis_id, script_key_id) IN ( + SELECT anchor_utxo_id, genesis_id, script_key_id + FROM duplicate_rows + -- Delete all rows with a row number greater than 1. This should leave + -- exactly one row (row number 0) for each set of rows that violate the + -- constraint. + WHERE row_num > 1 +); + +-- Create a unique index on the new table +CREATE UNIQUE INDEX assets_uniqueness_index_anchor_utxo_id_genesis_id_script_key_id +ON assets (anchor_utxo_id, genesis_id, script_key_id); + diff --git a/tapdb/sqlc/querier.go b/tapdb/sqlc/querier.go index ee001f911..55e185305 100644 --- a/tapdb/sqlc/querier.go +++ b/tapdb/sqlc/querier.go @@ -88,7 +88,6 @@ type Querier interface { InsertBranch(ctx context.Context, arg InsertBranchParams) error InsertCompactedLeaf(ctx context.Context, arg InsertCompactedLeafParams) error InsertLeaf(ctx context.Context, arg InsertLeafParams) error - InsertNewAsset(ctx context.Context, arg InsertNewAssetParams) (int64, error) InsertNewProofEvent(ctx context.Context, arg InsertNewProofEventParams) error InsertNewSyncEvent(ctx context.Context, arg InsertNewSyncEventParams) error InsertPassiveAsset(ctx context.Context, arg InsertPassiveAssetParams) error @@ -139,6 +138,7 @@ type Querier interface { UpdateMintingBatchState(ctx context.Context, arg UpdateMintingBatchStateParams) error UpdateUTXOLease(ctx context.Context, arg UpdateUTXOLeaseParams) error UpsertAddrEvent(ctx context.Context, arg UpsertAddrEventParams) (int64, error) + UpsertAsset(ctx context.Context, arg UpsertAssetParams) (int64, error) UpsertAssetGroupKey(ctx context.Context, arg UpsertAssetGroupKeyParams) (int64, error) UpsertAssetGroupWitness(ctx context.Context, arg UpsertAssetGroupWitnessParams) (int64, error) UpsertAssetMeta(ctx context.Context, arg UpsertAssetMetaParams) (int64, error) diff --git a/tapdb/sqlc/queries/assets.sql b/tapdb/sqlc/queries/assets.sql index cc33881e7..2be82073d 100644 --- a/tapdb/sqlc/queries/assets.sql +++ b/tapdb/sqlc/queries/assets.sql @@ -178,13 +178,24 @@ INSERT INTO genesis_assets ( DO UPDATE SET asset_id = EXCLUDED.asset_id RETURNING gen_asset_id; --- name: InsertNewAsset :one +-- name: UpsertAsset :one INSERT INTO assets ( genesis_id, version, script_key_id, asset_group_witness_id, script_version, amount, lock_time, relative_lock_time, anchor_utxo_id, spent ) VALUES ( - $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 -) RETURNING asset_id; + @genesis_id, @version, @script_key_id, @asset_group_witness_id, + @script_version, @amount, @lock_time, @relative_lock_time, @anchor_utxo_id, + @spent +) ON CONFLICT (anchor_utxo_id, genesis_id, script_key_id) +DO UPDATE SET + version = @version, + asset_group_witness_id = @asset_group_witness_id, + script_version = @script_version, + amount = @amount, + lock_time = @lock_time, + relative_lock_time = @relative_lock_time, + spent = @spent +RETURNING asset_id; -- name: FetchAssetsForBatch :many WITH genesis_info AS (