Skip to content

Commit 90ef28a

Browse files
committed
asset+scripts+tapd: allow querying multiple script key types
This introduces a workaround that gets us WHERE xxx IN (...) queries working with a little trick. See the comment in scripts/gen_sqlc_docker.sh for more information on how this works and why the workaround is needed.
1 parent 62b7841 commit 90ef28a

File tree

6 files changed

+225
-99
lines changed

6 files changed

+225
-99
lines changed

asset/asset.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,61 @@ const (
152152
ScriptKeyScriptPathChannel ScriptKeyType = 5
153153
)
154154

155+
var (
156+
// AllScriptKeyTypes is a slice of all known script key types.
157+
AllScriptKeyTypes = []ScriptKeyType{
158+
ScriptKeyUnknown,
159+
ScriptKeyBip86,
160+
ScriptKeyScriptPathExternal,
161+
ScriptKeyBurn,
162+
ScriptKeyTombstone,
163+
ScriptKeyScriptPathChannel,
164+
}
165+
166+
// ScriptKeyTypesNoChannel is a slice of all known script key types
167+
// that are not related to channels. This is used to filter out channel
168+
// related script keys when querying for assets that are not related to
169+
// channels.
170+
ScriptKeyTypesNoChannel = []ScriptKeyType{
171+
ScriptKeyUnknown,
172+
ScriptKeyBip86,
173+
ScriptKeyScriptPathExternal,
174+
ScriptKeyBurn,
175+
ScriptKeyTombstone,
176+
}
177+
)
178+
179+
// ScriptKeyTypeForDatabaseQuery returns a slice of script key types that should
180+
// be used when querying the database for assets. The returned slice will either
181+
// contain all script key types or only those that are not related to channels,
182+
// depending on the `filterChannelRelated` parameter. Unless the user specifies
183+
// a specific script key type, in which case the returned slice will only
184+
// contain that specific script key type.
185+
func ScriptKeyTypeForDatabaseQuery(filterChannelRelated bool,
186+
userSpecified fn.Option[ScriptKeyType]) []ScriptKeyType {
187+
188+
// For some queries, we want to get all the assets with all possible
189+
// script key types. For those, we use the full set of script key types.
190+
dbTypes := fn.CopySlice(AllScriptKeyTypes)
191+
192+
// For some RPCs (mostly balance related), we exclude the assets that
193+
// are specifically used for funding custom channels by default. The
194+
// balance of those assets is reported through lnd channel balance.
195+
// Those assets are identified by the specific script key type for
196+
// channel keys. We exclude them unless explicitly queried for.
197+
if filterChannelRelated {
198+
dbTypes = fn.CopySlice(ScriptKeyTypesNoChannel)
199+
}
200+
201+
// If the user specified a script key type, we use that to filter the
202+
// results.
203+
userSpecified.WhenSome(func(t ScriptKeyType) {
204+
dbTypes = []ScriptKeyType{t}
205+
})
206+
207+
return dbTypes
208+
}
209+
155210
var (
156211
// ZeroPrevID is the blank prev ID used for genesis assets and also
157212
// asset split leaves.

scripts/gen_sqlc_docker.sh

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ trap restore_files EXIT
1616

1717
# Directory of the script file, independent of where it's called from.
1818
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
19-
# Use the user's cache directories
19+
20+
# Use the user's cache directories.
2021
GOCACHE=$(go env GOCACHE)
2122
GOMODCACHE=$(go env GOMODCACHE)
2223

@@ -46,3 +47,25 @@ docker run \
4647
-v "$DIR/../:/build" \
4748
-w /build \
4849
sqlc/sqlc:1.29.0 generate
50+
51+
# Because we're using the Postgres dialect of sqlc, we can't use sqlc.slice()
52+
# normally, because sqlc just thinks it can pass the Golang slice directly to
53+
# the database driver. So it doesn't put the /*SLICE:<field_name>*/ workaround
54+
# comment into the actual SQL query. But we add the comment ourselves and now
55+
# just need to replace the '$X/*SLICE:<field_name>*/' placeholders with the
56+
# actual placeholder that's going to be replaced by the sqlc generated code.
57+
echo "Applying sqlc.slice() workaround..."
58+
for file in tapdb/sqlc/*.sql.go; do
59+
echo "Patching $file"
60+
61+
# First, we replace the `$X/*SLICE:<field_name>*/` placeholders with
62+
# the actual placeholder that sqlc will use: `/*SLICE:<field_name>*/?`.
63+
sed -i.bak -E 's/\$([0-9]+)\/\*SLICE:([a-zA-Z_][a-zA-Z0-9_]*)\*\//\/\*SLICE:\2\*\/\?/g' "$file"
64+
65+
# Then, we replace the `strings.Repeat(",?", len(arg.<golang_name>))[1:]` with
66+
# a function call that generates the correct number of placeholders:
67+
# `makeQueryParams(len(queryParams), len(arg.<golang_name>))`.
68+
sed -i.bak -E 's/strings\.Repeat\(",\?", len\(([^)]+)\)\)\[1:\]/makeQueryParams(len(queryParams), len(\1))/g' "$file"
69+
70+
rm "$file.bak"
71+
done

tapdb/assets_store.go

Lines changed: 36 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -895,16 +895,34 @@ func (a *AssetStore) constraintsToDbFilter(
895895
assetFilter.AssetIDFilter = nil
896896
}
897897

898-
// The fn.None option means we don't restrict on script key type
899-
// at all.
900-
query.ScriptKeyType.WhenSome(func(t asset.ScriptKeyType) {
901-
assetFilter.ScriptKeyType = sqlInt16(t)
902-
})
898+
// Let's figure out the set of script key types we want to
899+
// query for. If the user specified a script key type, then we
900+
// use that to filter the results. If the user didn't specify a
901+
// script key type, then we use the full set of script key
902+
// types, as this might be an internal query that also needs to
903+
// know channel related assets.
904+
assetFilter.ScriptKeyType = scriptKeyTypesForQuery(
905+
false, query.ScriptKeyType,
906+
)
903907
}
904908

905909
return assetFilter, nil
906910
}
907911

912+
// scriptKeyTypesForQuery returns the set of script key types we use in the
913+
// SQL queries based on the passed parameters. If the user specified a script
914+
// key type, then we use that to filter the results. If the user didn't
915+
// specify a script key type, then we use the full set of script key types
916+
// or the set of script key types that excludes channel related assets,
917+
// depending on the filterChannelRelated parameter.
918+
func scriptKeyTypesForQuery(filterChannelRelated bool,
919+
userSpecified fn.Option[asset.ScriptKeyType]) []sql.NullInt16 {
920+
921+
return fn.Map(asset.ScriptKeyTypeForDatabaseQuery(
922+
filterChannelRelated, userSpecified,
923+
), sqlInt16)
924+
}
925+
908926
// specificAssetFilter maps the given asset parameters to the set of filters
909927
// we use in the SQL queries.
910928
func (a *AssetStore) specificAssetFilter(id asset.ID, anchorPoint wire.OutPoint,
@@ -945,6 +963,17 @@ func fetchAssetsWithWitness(ctx context.Context, q ActiveAssetsStore,
945963
assetFilter QueryAssetFilters) ([]ConfirmedAsset, assetWitnesses,
946964
error) {
947965

966+
// We're using a slice of types to query for the set of script key
967+
// types, which is turned into a `xxx IN (...)` SQL query. But that
968+
// doesn't work for empty slices, as that would result in
969+
// `xxx IN (NULL)` which evaluates to false. So we need to use all
970+
// available types instead.
971+
if len(assetFilter.ScriptKeyType) == 0 {
972+
assetFilter.ScriptKeyType = fn.Map(
973+
asset.AllScriptKeyTypes, sqlInt16,
974+
)
975+
}
976+
948977
// First, we'll fetch all the assets we know of on disk.
949978
dbAssets, err := q.QueryAssets(ctx, assetFilter)
950979
if err != nil {
@@ -1002,21 +1031,7 @@ func (a *AssetStore) QueryBalancesByAsset(ctx context.Context,
10021031
// channels. The balance of those assets is reported through lnd channel
10031032
// balance. Those assets are identified by the specific script key type
10041033
// for channel keys. We exclude them unless explicitly queried for.
1005-
assetBalancesFilter.ExcludeScriptKeyType = sqlInt16(
1006-
asset.ScriptKeyScriptPathChannel,
1007-
)
1008-
1009-
// The fn.None option means we don't restrict on script key type at all.
1010-
skt.WhenSome(func(t asset.ScriptKeyType) {
1011-
assetBalancesFilter.ScriptKeyType = sqlInt16(t)
1012-
1013-
// If the user explicitly wants to see the channel related asset
1014-
// balances, we need to set the exclude type to NULL.
1015-
if t == asset.ScriptKeyScriptPathChannel {
1016-
nullValue := sql.NullInt16{}
1017-
assetBalancesFilter.ExcludeScriptKeyType = nullValue
1018-
}
1019-
})
1034+
assetBalancesFilter.ScriptKeyType = scriptKeyTypesForQuery(true, skt)
10201035

10211036
// By default, we only show assets that are not leased.
10221037
if !includeLeased {
@@ -1094,21 +1109,7 @@ func (a *AssetStore) QueryAssetBalancesByGroup(ctx context.Context,
10941109
// channels. The balance of those assets is reported through lnd channel
10951110
// balance. Those assets are identified by the specific script key type
10961111
// for channel keys. We exclude them unless explicitly queried for.
1097-
assetBalancesFilter.ExcludeScriptKeyType = sqlInt16(
1098-
asset.ScriptKeyScriptPathChannel,
1099-
)
1100-
1101-
// The fn.None option means we don't restrict on script key type at all.
1102-
skt.WhenSome(func(t asset.ScriptKeyType) {
1103-
assetBalancesFilter.ScriptKeyType = sqlInt16(t)
1104-
1105-
// If the user explicitly wants to see the channel related asset
1106-
// balances, we need to set the exclude type to NULL.
1107-
if t == asset.ScriptKeyScriptPathChannel {
1108-
nullValue := sql.NullInt16{}
1109-
assetBalancesFilter.ExcludeScriptKeyType = nullValue
1110-
}
1111-
})
1112+
assetBalancesFilter.ScriptKeyType = scriptKeyTypesForQuery(true, skt)
11121113

11131114
// By default, we only show assets that are not leased.
11141115
if !includeLeased {

tapdb/sqlc/assets.sql.go

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

0 commit comments

Comments
 (0)