diff --git a/integration_tests/src/data/accounts/search_by_owner_with_show_zero_balance/3LYNZgmq8B1wbDzm2SZvvSeKULXaPBPp9HDkvGtcccZP b/integration_tests/src/data/accounts/search_by_owner_with_show_zero_balance/3LYNZgmq8B1wbDzm2SZvvSeKULXaPBPp9HDkvGtcccZP new file mode 100644 index 00000000..8c634066 Binary files /dev/null and b/integration_tests/src/data/accounts/search_by_owner_with_show_zero_balance/3LYNZgmq8B1wbDzm2SZvvSeKULXaPBPp9HDkvGtcccZP differ diff --git a/integration_tests/src/data/accounts/search_by_owner_with_show_zero_balance/3oiDhCQMDzxj8NcLfRBCQj3R9mQkE1DnDZfrNbAgruQk b/integration_tests/src/data/accounts/search_by_owner_with_show_zero_balance/3oiDhCQMDzxj8NcLfRBCQj3R9mQkE1DnDZfrNbAgruQk new file mode 100644 index 00000000..0d7c734a Binary files /dev/null and b/integration_tests/src/data/accounts/search_by_owner_with_show_zero_balance/3oiDhCQMDzxj8NcLfRBCQj3R9mQkE1DnDZfrNbAgruQk differ diff --git a/integration_tests/src/data/accounts/search_by_owner_with_show_zero_balance/3rzjtWZcZyvADaT5rrkRwGKWjnuzvK3PDedGMUwpnrrP b/integration_tests/src/data/accounts/search_by_owner_with_show_zero_balance/3rzjtWZcZyvADaT5rrkRwGKWjnuzvK3PDedGMUwpnrrP new file mode 100644 index 00000000..000f201b Binary files /dev/null and b/integration_tests/src/data/accounts/search_by_owner_with_show_zero_balance/3rzjtWZcZyvADaT5rrkRwGKWjnuzvK3PDedGMUwpnrrP differ diff --git a/integration_tests/src/data/accounts/search_by_owner_with_show_zero_balance/3yMfqHsajYFw2Yw6C4kwrvHRESMg9U7isNVJuzNETJKG b/integration_tests/src/data/accounts/search_by_owner_with_show_zero_balance/3yMfqHsajYFw2Yw6C4kwrvHRESMg9U7isNVJuzNETJKG new file mode 100644 index 00000000..d38fc3f6 Binary files /dev/null and b/integration_tests/src/data/accounts/search_by_owner_with_show_zero_balance/3yMfqHsajYFw2Yw6C4kwrvHRESMg9U7isNVJuzNETJKG differ diff --git a/integration_tests/src/data/accounts/search_by_owner_with_show_zero_balance/6kU9EA8ApkD3eCYjoR3e8MkJzvjcVb8nTFUQhGKMjA7r b/integration_tests/src/data/accounts/search_by_owner_with_show_zero_balance/6kU9EA8ApkD3eCYjoR3e8MkJzvjcVb8nTFUQhGKMjA7r new file mode 100644 index 00000000..44b0edc4 Binary files /dev/null and b/integration_tests/src/data/accounts/search_by_owner_with_show_zero_balance/6kU9EA8ApkD3eCYjoR3e8MkJzvjcVb8nTFUQhGKMjA7r differ diff --git a/integration_tests/src/data/accounts/search_by_owner_with_show_zero_balance/94eSnb5qBWTvxj3gqP6Ukq8bPhRTNNVZrE7zR5yTZd9E b/integration_tests/src/data/accounts/search_by_owner_with_show_zero_balance/94eSnb5qBWTvxj3gqP6Ukq8bPhRTNNVZrE7zR5yTZd9E new file mode 100644 index 00000000..2140b7dd Binary files /dev/null and b/integration_tests/src/data/accounts/search_by_owner_with_show_zero_balance/94eSnb5qBWTvxj3gqP6Ukq8bPhRTNNVZrE7zR5yTZd9E differ diff --git a/integration_tests/src/data/accounts/search_by_owner_with_show_zero_balance/AZL8nj9woKgjsGE11BJ7J4BFoo9SgmLn7vYu3Ek3mPEy b/integration_tests/src/data/accounts/search_by_owner_with_show_zero_balance/AZL8nj9woKgjsGE11BJ7J4BFoo9SgmLn7vYu3Ek3mPEy new file mode 100644 index 00000000..d166b058 Binary files /dev/null and b/integration_tests/src/data/accounts/search_by_owner_with_show_zero_balance/AZL8nj9woKgjsGE11BJ7J4BFoo9SgmLn7vYu3Ek3mPEy differ diff --git a/integration_tests/src/data/accounts/search_by_owner_with_show_zero_balance/AvQXpnPDt6NazCuFeXZxBYcuL46gsBBJ4CPqmcgkg3Hd b/integration_tests/src/data/accounts/search_by_owner_with_show_zero_balance/AvQXpnPDt6NazCuFeXZxBYcuL46gsBBJ4CPqmcgkg3Hd new file mode 100644 index 00000000..5c4dcad2 Binary files /dev/null and b/integration_tests/src/data/accounts/search_by_owner_with_show_zero_balance/AvQXpnPDt6NazCuFeXZxBYcuL46gsBBJ4CPqmcgkg3Hd differ diff --git a/integration_tests/src/data/accounts/search_by_owner_with_show_zero_balance/BFjgKzLNKZEbZoDrESi79ai8jXgyBth1HXCJPXBGs8sj b/integration_tests/src/data/accounts/search_by_owner_with_show_zero_balance/BFjgKzLNKZEbZoDrESi79ai8jXgyBth1HXCJPXBGs8sj new file mode 100644 index 00000000..07aaeb94 Binary files /dev/null and b/integration_tests/src/data/accounts/search_by_owner_with_show_zero_balance/BFjgKzLNKZEbZoDrESi79ai8jXgyBth1HXCJPXBGs8sj differ diff --git a/integration_tests/src/data/accounts/search_by_owner_with_show_zero_balance/HxhWkVpk5NS4Ltg5nij2G671CKXFRKPK8vy271Ub4uEK b/integration_tests/src/data/accounts/search_by_owner_with_show_zero_balance/HxhWkVpk5NS4Ltg5nij2G671CKXFRKPK8vy271Ub4uEK new file mode 100644 index 00000000..6f659482 Binary files /dev/null and b/integration_tests/src/data/accounts/search_by_owner_with_show_zero_balance/HxhWkVpk5NS4Ltg5nij2G671CKXFRKPK8vy271Ub4uEK differ diff --git a/integration_tests/src/data/accounts/search_by_owner_with_show_zero_balance/sFxPHhiQWptvrc2YA2HyfNjCvqxpsEtmjmREBvWf7NJ b/integration_tests/src/data/accounts/search_by_owner_with_show_zero_balance/sFxPHhiQWptvrc2YA2HyfNjCvqxpsEtmjmREBvWf7NJ new file mode 100644 index 00000000..7ec9e41b Binary files /dev/null and b/integration_tests/src/data/accounts/search_by_owner_with_show_zero_balance/sFxPHhiQWptvrc2YA2HyfNjCvqxpsEtmjmREBvWf7NJ differ diff --git a/integration_tests/src/data/largest_token_account_ids/3yMfqHsajYFw2Yw6C4kwrvHRESMg9U7isNVJuzNETJKG/3yMfqHsajYFw2Yw6C4kwrvHRESMg9U7isNVJuzNETJKG b/integration_tests/src/data/largest_token_account_ids/3yMfqHsajYFw2Yw6C4kwrvHRESMg9U7isNVJuzNETJKG/3yMfqHsajYFw2Yw6C4kwrvHRESMg9U7isNVJuzNETJKG new file mode 100644 index 00000000..adc7f636 --- /dev/null +++ b/integration_tests/src/data/largest_token_account_ids/3yMfqHsajYFw2Yw6C4kwrvHRESMg9U7isNVJuzNETJKG/3yMfqHsajYFw2Yw6C4kwrvHRESMg9U7isNVJuzNETJKG @@ -0,0 +1 @@ + ßó4'p) Ÿ%¦ GáëÎX·2G¿%Šù£< y‹ \ No newline at end of file diff --git a/integration_tests/src/data/largest_token_account_ids/BFjgKzLNKZEbZoDrESi79ai8jXgyBth1HXCJPXBGs8sj/BFjgKzLNKZEbZoDrESi79ai8jXgyBth1HXCJPXBGs8sj b/integration_tests/src/data/largest_token_account_ids/BFjgKzLNKZEbZoDrESi79ai8jXgyBth1HXCJPXBGs8sj/BFjgKzLNKZEbZoDrESi79ai8jXgyBth1HXCJPXBGs8sj new file mode 100644 index 00000000..1363c78d Binary files /dev/null and b/integration_tests/src/data/largest_token_account_ids/BFjgKzLNKZEbZoDrESi79ai8jXgyBth1HXCJPXBGs8sj/BFjgKzLNKZEbZoDrESi79ai8jXgyBth1HXCJPXBGs8sj differ diff --git a/integration_tests/src/data/largest_token_account_ids/HxhWkVpk5NS4Ltg5nij2G671CKXFRKPK8vy271Ub4uEK/HxhWkVpk5NS4Ltg5nij2G671CKXFRKPK8vy271Ub4uEK b/integration_tests/src/data/largest_token_account_ids/HxhWkVpk5NS4Ltg5nij2G671CKXFRKPK8vy271Ub4uEK/HxhWkVpk5NS4Ltg5nij2G671CKXFRKPK8vy271Ub4uEK new file mode 100644 index 00000000..6384d98b Binary files /dev/null and b/integration_tests/src/data/largest_token_account_ids/HxhWkVpk5NS4Ltg5nij2G671CKXFRKPK8vy271Ub4uEK/HxhWkVpk5NS4Ltg5nij2G671CKXFRKPK8vy271Ub4uEK differ diff --git a/integration_tests/src/mpl_core_tests.rs b/integration_tests/src/mpl_core_tests.rs index 84b67f5c..1136e460 100644 --- a/integration_tests/src/mpl_core_tests.rs +++ b/integration_tests/src/mpl_core_tests.rs @@ -67,7 +67,6 @@ async fn test_mpl_core_get_collection() { } #[tokio::test] -#[ignore = "TODO: must recheck snapshot incompatibility"] #[serial] #[named] async fn test_mpl_core_get_assets_by_authority() { diff --git a/integration_tests/src/regular_nft_tests.rs b/integration_tests/src/regular_nft_tests.rs index 31e5ec85..1594ac44 100644 --- a/integration_tests/src/regular_nft_tests.rs +++ b/integration_tests/src/regular_nft_tests.rs @@ -201,6 +201,55 @@ async fn test_regular_nft_collection() { insta::assert_json_snapshot!(name.clone(), response); } +#[tokio::test] +#[serial] +#[named] +async fn test_search_by_owner_with_show_zero_balance() { + let name = trim_test_name(function_name!()); + let setup = TestSetup::new_with_options( + name.clone(), + TestSetupOptions { network: Some(Network::Mainnet), clear_db: true }, + ) + .await; + + let seeds: Vec = seed_token_mints([ + "HxhWkVpk5NS4Ltg5nij2G671CKXFRKPK8vy271Ub4uEK", // mint for fungible acc + ]); + index_seed_events(&setup, seeds.iter().collect_vec()).await; + + let seeds: Vec = seed_accounts([ + "3rzjtWZcZyvADaT5rrkRwGKWjnuzvK3PDedGMUwpnrrP", // empty token acc from NFT (3yMfqHsajYFw2Yw6C4kwrvHRESMg9U7isNVJuzNETJKG) + "94eSnb5qBWTvxj3gqP6Ukq8bPhRTNNVZrE7zR5yTZd9E", // fungible token with zero balance + ]); + + index_seed_events(&setup, seeds.iter().collect_vec()).await; + + let seeds: Vec = seed_nfts([ + "BFjgKzLNKZEbZoDrESi79ai8jXgyBth1HXCJPXBGs8sj", // NFT wallet has + "3yMfqHsajYFw2Yw6C4kwrvHRESMg9U7isNVJuzNETJKG", // NFT wallet used to have + ]); + + index_seed_events(&setup, seeds.iter().collect_vec()).await; + + let request = r#" + { + "page": 1, + "limit": 500, + "ownerAddress": "3VvLDXqJbw3heyRwFxv8MmurPznmDVUJS9gPMX2BDqfM", + "tokenType": "all", + "options": { + "showNativeBalance": true, "showZeroBalance": true + } + } + "#; + + let mutexed_tasks = Arc::new(Mutex::new(JoinSet::new())); + + let request: SearchAssets = serde_json::from_str(request).unwrap(); + let response = setup.das_api.search_assets(request, mutexed_tasks.clone()).await.unwrap(); + insta::assert_json_snapshot!(name, response); +} + #[tokio::test] #[serial] #[named] diff --git a/integration_tests/src/snapshots/integration_tests__general_scenario_tests__get_different_assets_by_owner.snap b/integration_tests/src/snapshots/integration_tests__general_scenario_tests__get_different_assets_by_owner.snap index 0a1cb86d..5fd38834 100644 --- a/integration_tests/src/snapshots/integration_tests__general_scenario_tests__get_different_assets_by_owner.snap +++ b/integration_tests/src/snapshots/integration_tests__general_scenario_tests__get_different_assets_by_owner.snap @@ -1,6 +1,6 @@ --- source: integration_tests/src/general_scenario_tests.rs -assertion_line: 101 +assertion_line: 83 expression: response snapshot_kind: text --- diff --git a/integration_tests/src/snapshots/integration_tests__general_scenario_tests__get_different_assets_by_owner_show_unverif_coll.snap b/integration_tests/src/snapshots/integration_tests__general_scenario_tests__get_different_assets_by_owner_show_unverif_coll.snap index 0e05aca8..8569b6ff 100644 --- a/integration_tests/src/snapshots/integration_tests__general_scenario_tests__get_different_assets_by_owner_show_unverif_coll.snap +++ b/integration_tests/src/snapshots/integration_tests__general_scenario_tests__get_different_assets_by_owner_show_unverif_coll.snap @@ -1,6 +1,6 @@ --- source: integration_tests/src/general_scenario_tests.rs -assertion_line: 124 +assertion_line: 102 expression: response snapshot_kind: text --- diff --git a/integration_tests/src/snapshots/integration_tests__regular_nft_tests__search_by_owner_with_show_zero_balance.snap b/integration_tests/src/snapshots/integration_tests__regular_nft_tests__search_by_owner_with_show_zero_balance.snap new file mode 100644 index 00000000..3b1f03e7 --- /dev/null +++ b/integration_tests/src/snapshots/integration_tests__regular_nft_tests__search_by_owner_with_show_zero_balance.snap @@ -0,0 +1,151 @@ +--- +source: integration_tests/src/regular_nft_tests.rs +assertion_line: 250 +expression: response +snapshot_kind: text +--- +{ + "total": 2, + "limit": 500, + "page": 1, + "items": [ + { + "interface": "Custom", + "id": "HxhWkVpk5NS4Ltg5nij2G671CKXFRKPK8vy271Ub4uEK", + "content": { + "$schema": "https://schema.metaplex.com/nft1.0.json", + "json_uri": "", + "files": [], + "metadata": { + "name": "Hxro (Wormhole)", + "symbol": "HXRO" + }, + "links": {} + }, + "authorities": [ + { + "address": "BCD75RNBHrJJpW4dXVagL5mPjzRLnVZq4YirJdjEYMV7", + "scopes": [ + "full" + ] + } + ], + "compression": { + "eligible": false, + "compressed": false, + "data_hash": "", + "creator_hash": "", + "asset_hash": "", + "tree": "", + "seq": 0, + "leaf_id": 0 + }, + "grouping": [], + "royalty": { + "royalty_model": "creators", + "target": null, + "percent": 0.0, + "basis_points": 0, + "primary_sale_happened": false, + "locked": false + }, + "creators": [], + "ownership": { + "frozen": false, + "delegated": false, + "delegate": null, + "ownership_model": "token", + "owner": "3VvLDXqJbw3heyRwFxv8MmurPznmDVUJS9gPMX2BDqfM" + }, + "supply": { + "print_max_supply": 0, + "print_current_supply": 0, + "edition_nonce": 252 + }, + "mutable": true, + "burnt": false, + "lamports": 5616720, + "executable": false, + "metadata_owner": "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s", + "rent_epoch": 18446744073709551615, + "token_info": { + "balance": 0, + "supply": 69421234056477841, + "decimals": 8, + "token_program": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "associated_token_address": "94eSnb5qBWTvxj3gqP6Ukq8bPhRTNNVZrE7zR5yTZd9E", + "mint_authority": "BCD75RNBHrJJpW4dXVagL5mPjzRLnVZq4YirJdjEYMV7" + } + }, + { + "interface": "V1_NFT", + "id": "BFjgKzLNKZEbZoDrESi79ai8jXgyBth1HXCJPXBGs8sj", + "content": { + "$schema": "https://schema.metaplex.com/nft1.0.json", + "json_uri": "", + "files": [], + "metadata": { + "name": "Degen Ape", + "symbol": "DAPE", + "token_standard": "NonFungible" + }, + "links": {} + }, + "authorities": [ + { + "address": "3VvLDXqJbw3heyRwFxv8MmurPznmDVUJS9gPMX2BDqfM", + "scopes": [ + "full" + ] + } + ], + "compression": { + "eligible": false, + "compressed": false, + "data_hash": "", + "creator_hash": "", + "asset_hash": "", + "tree": "", + "seq": 0, + "leaf_id": 0 + }, + "grouping": [], + "royalty": { + "royalty_model": "creators", + "target": null, + "percent": 0.0, + "basis_points": 0, + "primary_sale_happened": false, + "locked": false + }, + "creators": [], + "ownership": { + "frozen": false, + "delegated": false, + "delegate": null, + "ownership_model": "single", + "owner": "3VvLDXqJbw3heyRwFxv8MmurPznmDVUJS9gPMX2BDqfM" + }, + "supply": { + "print_max_supply": 0, + "print_current_supply": 0, + "edition_nonce": 255 + }, + "mutable": true, + "burnt": false, + "lamports": 5616720, + "executable": false, + "metadata_owner": "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s", + "rent_epoch": 18446744073709551615, + "token_info": { + "balance": 1, + "supply": 1, + "decimals": 0, + "token_program": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "associated_token_address": "3oiDhCQMDzxj8NcLfRBCQj3R9mQkE1DnDZfrNbAgruQk", + "mint_authority": "565tK79EKhQSYybx52awrrFhD4m2ty198rrUBmiqq7xo", + "freeze_authority": "565tK79EKhQSYybx52awrrFhD4m2ty198rrUBmiqq7xo" + } + } + ] +} diff --git a/migrations/12_fungible_and_assets_indexes.sql b/migrations/12_fungible_and_assets_indexes.sql new file mode 100644 index 00000000..a55da181 --- /dev/null +++ b/migrations/12_fungible_and_assets_indexes.sql @@ -0,0 +1,3 @@ +CREATE INDEX fungible_tokens_fbt_owner_fbt_asset_fbt_balance_idx ON fungible_tokens (fbt_owner, fbt_asset, fbt_balance); + +CREATE INDEX assets_ast_owner_type_ast_pubkey_idx ON assets_v3 (ast_owner_type, ast_pubkey); \ No newline at end of file diff --git a/postgre-client/src/asset_filter_client.rs b/postgre-client/src/asset_filter_client.rs index ae9f252f..a68271dc 100644 --- a/postgre-client/src/asset_filter_client.rs +++ b/postgre-client/src/asset_filter_client.rs @@ -104,7 +104,12 @@ impl PgClient { if group_clause_required { query_builder.push(" GROUP BY ast_pubkey, ast_slot_created, ast_slot_updated "); } - query_builder.push(") "); + + // Add ORDER BY clause + let direction = match (&order.sort_direction, order_reversed) { + (AssetSortDirection::Asc, true) | (AssetSortDirection::Desc, false) => " DESC ", + (AssetSortDirection::Asc, false) | (AssetSortDirection::Desc, true) => " ASC ", + }; // the function with a side-effect that mutates the query_builder if let Some(owner_address) = &filter.owner_address { @@ -113,9 +118,29 @@ impl PgClient { TokenType::Fungible | TokenType::NonFungible | TokenType::RegularNFT - | TokenType::CompressedNFT => {}, + | TokenType::CompressedNFT => { + query_builder.push(")"); + }, TokenType::All => { - query_builder.push(" UNION "); + // For this type of query we do union and apply limit with ordering for both parts of a query. + // It allows us to speed up the query for queries with wallets which has lots of assets. + // Not the cleanest approach. + // TODO: this may be improved + query_builder.push(" ORDER BY "); + query_builder.push(order.sort_by.to_string().replace("ast_", "")); + query_builder.push(direction); + + query_builder.push(" LIMIT "); + if let Some(page_num) = page.filter(|&p| p > 1) { + let lim = (page_num - 1) * limit; + query_builder.push_bind(lim as i64); + } else { + query_builder.push_bind(limit as i64); + } + + query_builder.push(")"); + + query_builder.push(" UNION ALL "); query_builder.push(" ( "); query_builder.push( "SELECT @@ -123,7 +148,7 @@ impl PgClient { ast_slot_created, ast_slot_updated FROM assets_v3 - JOIN fungible_tokens ON ast_pubkey = fungible_tokens.fbt_asset + JOIN fungible_tokens ON ast_pubkey = fungible_tokens.fbt_asset AND ast_owner_type = 'token' WHERE ast_supply > 0 AND fbt_owner = ", ); query_builder.push_bind(owner_address); @@ -137,18 +162,29 @@ impl PgClient { " AND assets_v3.ast_is_collection_verified IS DISTINCT FROM FALSE", ); } + + query_builder.push(" ORDER BY "); + query_builder.push(order.sort_by.to_string()); + query_builder.push(direction); + + query_builder.push(" LIMIT "); + if let Some(page_num) = page.filter(|&p| p > 1) { + let lim = (page_num - 1) * limit; + query_builder.push_bind(lim as i64); + } else { + query_builder.push_bind(limit as i64); + } + query_builder.push(")"); }, } + } else { + query_builder.push(")"); } + } else { + query_builder.push(")"); } - // Add ORDER BY clause - let direction = match (&order.sort_direction, order_reversed) { - (AssetSortDirection::Asc, true) | (AssetSortDirection::Desc, false) => " DESC ", - (AssetSortDirection::Asc, false) | (AssetSortDirection::Desc, true) => " ASC ", - }; - query_builder.push(" ORDER BY "); query_builder.push(order.sort_by.to_string().replace("ast_", "")); query_builder.push(direction);