@@ -2,6 +2,7 @@ package queries
22
33import (
44 "fmt"
5+ "strings"
56)
67
78func TotalCountQuery (inner string ) string {
@@ -1030,76 +1031,6 @@ const (
10301031 FROM chain.runtime_nodes
10311032 WHERE runtime_id = $1::text`
10321033
1033- RuntimeRoflApps = `
1034- WITH
1035- -- Latest epoch, to identify active instances.
1036- max_epoch AS (
1037- SELECT id FROM chain.epochs ORDER BY id DESC LIMIT 1
1038- ),
1039-
1040- -- Aggregate active instance data per app.
1041- active_instances AS (
1042- SELECT
1043- ri.app_id,
1044- COUNT(*) AS num_active_instances,
1045- jsonb_agg(
1046- jsonb_build_object(
1047- 'rak', ri.rak,
1048- 'endorsing_node_id', ri.endorsing_node_id,
1049- 'endorsing_entity_id', ri.endorsing_entity_id,
1050- 'rek', ri.rek,
1051- 'expiration_epoch', ri.expiration_epoch,
1052- 'extra_keys', ri.extra_keys
1053- ) ORDER BY ri.expiration_epoch DESC
1054- ) AS instance_json
1055- FROM chain.rofl_instances ri
1056- JOIN max_epoch ON true
1057- WHERE ri.expiration_epoch > max_epoch.id
1058- GROUP BY ri.app_id
1059- )
1060-
1061- SELECT
1062- ra.id,
1063- ra.admin,
1064- preimages.context_identifier,
1065- preimages.context_version,
1066- preimages.address_data,
1067- ra.stake,
1068- ra.policy,
1069- ra.sek,
1070- ra.metadata,
1071- ra.secrets,
1072- ra.removed,
1073- COALESCE(ai.num_active_instances, 0) as num_active_instances,
1074- COALESCE(ai.instance_json, '[]'::jsonb) AS active_instances
1075- FROM chain.rofl_apps AS ra
1076-
1077- -- Resolve admin address preimage.
1078- LEFT JOIN chain.address_preimages AS preimages ON (
1079- preimages.address = ra.admin AND
1080- -- For now, the only user is the explorer, where we only care
1081- -- about Ethereum-compatible addresses, so only get those. Can
1082- -- easily enable for other address types though.
1083- preimages.context_identifier = 'oasis-runtime-sdk/address: secp256k1eth' AND
1084- preimages.context_version = 0
1085- )
1086-
1087- -- Join aggregated active instance data.
1088- LEFT JOIN active_instances ai ON ai.app_id = ra.id
1089-
1090- LEFT JOIN chain.accounts AS a ON a.address = ra.admin
1091-
1092- WHERE
1093- ra.runtime = $1::runtime AND
1094- ($2::text IS NULL OR ra.id = $2::text) AND
1095- ($3::text IS NULL OR ra.metadata_name ILIKE '%' || $3::text || '%') AND
1096- -- Exclude not yet processed apps.
1097- ra.last_processed_round IS NOT NULL
1098-
1099- ORDER BY num_active_instances DESC, ra.num_transactions DESC, ra.id DESC
1100- LIMIT $4::bigint
1101- OFFSET $5::bigint`
1102-
11031034 RuntimeRoflAppTransactionsFirstLast = `
11041035 WITH
11051036 app_ids AS (
@@ -1329,3 +1260,114 @@ const (
13291260 LIMIT $2::bigint
13301261 OFFSET $3::bigint`
13311262)
1263+
1264+ // RuntimeRoflApps returns a SQL query and argument list for selecting ROFL apps,
1265+ // optionally filtered by a list of metadata name substrings.
1266+ //
1267+ // The query is dynamically constructed to support efficient AND-based substring
1268+ // filtering using ILIKE '%term%' per name fragment.
1269+ //
1270+ // Dynamic query generation is necessary here to allow PostgreSQL to utilize the
1271+ // pg_trgm GIN index on `metadata_name` — index usage is only possible when each
1272+ // ILIKE condition is written explicitly.
1273+ //
1274+ // Parameters:
1275+ //
1276+ // $1: runtime (required)
1277+ // $2: optional app ID for exact match
1278+ // $3...$N: optional name substrings (one per term, matched via ILIKE with AND logic)
1279+ // $N+1: limit
1280+ // $N+2: offset
1281+ func RuntimeRoflApps (rawNames * []string ) (string , []interface {}) {
1282+ var clauses []string
1283+ var args []interface {}
1284+ argOffset := 3 // first 2 args are $1 = runtime, $2 = optional id.
1285+
1286+ if rawNames != nil {
1287+ for i , name := range * rawNames {
1288+ // Escape the name for the LIKE operator.
1289+ escaped := strings .ReplaceAll (name , `\` , `\\` )
1290+ escaped = strings .ReplaceAll (escaped , `%` , `\%` )
1291+ escaped = strings .ReplaceAll (escaped , `_` , `\_` )
1292+
1293+ clauses = append (clauses , fmt .Sprintf ("ra.metadata_name ILIKE $%d ESCAPE '\\ '" , argOffset + i ))
1294+ args = append (args , "%" + escaped + "%" )
1295+ }
1296+ }
1297+
1298+ nameCondition := "TRUE"
1299+ if len (clauses ) > 0 {
1300+ nameCondition = "(" + strings .Join (clauses , " AND " ) + ")"
1301+ }
1302+ query := fmt .Sprintf (`
1303+ WITH
1304+ -- Latest epoch, to identify active instances.
1305+ max_epoch AS (
1306+ SELECT id FROM chain.epochs ORDER BY id DESC LIMIT 1
1307+ ),
1308+
1309+ -- Aggregate active instance data per app.
1310+ active_instances AS (
1311+ SELECT
1312+ ri.app_id,
1313+ COUNT(*) AS num_active_instances,
1314+ jsonb_agg(
1315+ jsonb_build_object(
1316+ 'rak', ri.rak,
1317+ 'endorsing_node_id', ri.endorsing_node_id,
1318+ 'endorsing_entity_id', ri.endorsing_entity_id,
1319+ 'rek', ri.rek,
1320+ 'expiration_epoch', ri.expiration_epoch,
1321+ 'extra_keys', ri.extra_keys
1322+ ) ORDER BY ri.expiration_epoch DESC
1323+ ) AS instance_json
1324+ FROM chain.rofl_instances ri
1325+ JOIN max_epoch ON true
1326+ WHERE ri.expiration_epoch > max_epoch.id
1327+ GROUP BY ri.app_id
1328+ )
1329+
1330+ SELECT
1331+ ra.id,
1332+ ra.admin,
1333+ preimages.context_identifier,
1334+ preimages.context_version,
1335+ preimages.address_data,
1336+ ra.stake,
1337+ ra.policy,
1338+ ra.sek,
1339+ ra.metadata,
1340+ ra.secrets,
1341+ ra.removed,
1342+ COALESCE(ai.num_active_instances, 0) as num_active_instances,
1343+ COALESCE(ai.instance_json, '[]'::jsonb) AS active_instances
1344+ FROM chain.rofl_apps AS ra
1345+
1346+ -- Resolve admin address preimage.
1347+ LEFT JOIN chain.address_preimages AS preimages ON (
1348+ preimages.address = ra.admin AND
1349+ -- For now, the only user is the explorer, where we only care
1350+ -- about Ethereum-compatible addresses, so only get those. Can
1351+ -- easily enable for other address types though.
1352+ preimages.context_identifier = 'oasis-runtime-sdk/address: secp256k1eth' AND
1353+ preimages.context_version = 0
1354+ )
1355+
1356+ -- Join aggregated active instance data.
1357+ LEFT JOIN active_instances ai ON ai.app_id = ra.id
1358+
1359+ LEFT JOIN chain.accounts AS a ON a.address = ra.admin
1360+
1361+ WHERE
1362+ ra.runtime = $1::runtime AND
1363+ ($2::text IS NULL OR ra.id = $2::text) AND
1364+ %s AND
1365+ -- Exclude not yet processed apps.
1366+ ra.last_processed_round IS NOT NULL
1367+
1368+ ORDER BY num_active_instances DESC, ra.num_transactions DESC, ra.id DESC
1369+ LIMIT $%d::bigint
1370+ OFFSET $%d::bigint` , nameCondition , argOffset + len (clauses ), argOffset + len (clauses )+ 1 )
1371+
1372+ return query , args
1373+ }
0 commit comments