Commit 1cbd4a6
Backfill sol_purchases from usdc_purchases (#815)
## Summary
Step 1 of the purchases-domain cutover. This PR populates the new
Go-indexer tables with the historical data they're missing today, adds
the compatibility view + parallel notification trigger, but **leaves all
readers on the legacy `usdc_purchases` table**. The route swap is a
separate PR (step 2) that lands after this one is verified on
production.
The shape mirrors PR #809 (challenges cutover): bounded migration,
view-based read translation, parallel trigger that dedupes via shared
`group_id`.
## What's in this PR
**Schema / migration** —
`ddl/migrations/0199_backfill_sol_purchases.sql`
- Adds `created_at TIMESTAMP DEFAULT NOW()` to `sol_purchases`. Same gap
we hit on `sol_reward_disbursements` in #809.
- Copies historical purchases from `usdc_purchases` into
`sol_purchases`. `from_account` is resolved to the buyer's USDC
user_bank via `usdc_user_bank_accounts` so the NOT NULL column has a
real value.
- Patches `created_at` on rows the Go indexer wrote before this
migration (their `created_at` was just `NOW()` from the default;
corrects them from the legacy table where the legacy value is older).
- Explodes `usdc_purchases.splits` JSONB into one `sol_payments` row per
element. Element shape is `{payout_wallet, amount, percentage, user_id,
eth_wallet}` per `add_wallet_info_to_splits()` in the Python source.
- Adds `sol_purchases_created_at_idx` so the route-side default sort by
`created_at` doesn't degrade.
**View** — `ddl/views/v_usdc_purchases.sql`
- Exposes `sol_purchases` + `sol_payments` in the legacy column shape so
step 2's route swap is mostly a one-token rename.
- `seller_user_id` is derived from current content ownership
(`tracks.owner_id` / `playlists.playlist_owner_id`). Note: this is
current owner, not snapshotted at purchase time. Legacy was a snapshot —
accepting this drift per design discussion.
- `extra_amount` is derived as `amount - base_price` via a correlated
subquery against `track_price_history` / `album_price_history`
(block_timestamp <= purchase created_at, ORDER BY DESC LIMIT 1).
- `splits` JSON is aggregated over `sol_payments` with user_id resolved
via `COALESCE(users.spl_usdc_payout_wallet match, sol_claimable_accounts
mint=USDC match)`. Network-cut payments (to the staking bridge wallet)
emit `user_id: null`.
- `vendor` is intentionally dropped from the view.
- Filtered to `is_valid IS TRUE` to match the legacy table's semantics
(Python only wrote validated purchases).
**Trigger** — `ddl/functions/handle_usdc_purchase.sql`
- Appends a `handle_sol_purchase` function and `on_sol_purchase AFTER
INSERT ON sol_purchases` trigger.
- Notification shape and `group_id` format match the legacy trigger
byte-for-byte (verified against the existing function body), so during
the backfill — where every inserted row fires the new trigger and tries
to recreate notifications whose `group_id`s were created by the legacy
trigger long ago — `ON CONFLICT DO NOTHING` makes them no-ops.
- `vendor` and `extra_amount` are emitted as `null` in the new payload;
downstream consumers must tolerate this.
**Cleanup** — `sql/01_schema.sql`
- Dropped a stale `block_timestamp` column from the `sol_purchases`
table definition. No migration creates it and nothing in the repo
references it; the dump had drifted from reality.
## What's NOT in this PR
- **Reader changes.** All 14+ Go routes that join `usdc_purchases`
(`v1_users_purchases`, `v1_users_sales`, `v1_users_purchasers`,
`v1_explore_best_selling`, `v1_users_library_*`, `v1_fan_club_feed`,
`comms_blasts`, `dbv1/access.go`, `comms/chat.go`, etc.) are unchanged.
Until this PR's backfill is verified in production, swapping readers
would risk old purchases disappearing.
- **Python decommission.** `index_payment_router` keeps writing
`usdc_purchases` (legacy trigger keeps firing on insert). The new
trigger dedupes against it via `group_id`.
- **Go indexer update for `created_at`.** A small follow-up:
`solana/indexer/program/payment_router.go` should explicitly write
`created_at` so new rows get the on-chain time rather than `NOW()` from
the column default. Default is correct-enough until then.
- **Vendor field.** Lost in the view; if anything frontend-side breaks
on `vendor: null` notifications we'll need a workaround.
## Test plan
After this lands, before opening the step-2 PR, verify on a prod
replica:
- [ ] Row-count parity:
```sql
SELECT (SELECT count(*) FROM usdc_purchases) AS legacy,
(SELECT count(*) FROM sol_purchases WHERE is_valid IS TRUE) AS
new_valid;
```
- [ ] Splits parity for a 50-row sample:
```sql
SELECT up.signature,
jsonb_array_length(up.splits) AS legacy_splits,
(SELECT count(*) FROM sol_payments WHERE signature = up.signature AND
instruction_index = 0) AS new_splits
FROM usdc_purchases up ORDER BY random() LIMIT 50;
```
- [ ] Spot-check `v_usdc_purchases` against `usdc_purchases` for a few
signatures: same `buyer_user_id`, same `amount`, comparable
`splits[*].user_id` and `payout_wallet`.
- [ ] Confirm trigger dedupe: insert a `sol_purchases` row matching an
existing `usdc_purchases` row in dev; assert no new notification row
appears.
- [ ] Cloud SQL logs during deploy: no `statement_timeout`, no
`pg_type_typname_nsp_index`, no `deadlock detected`.
- [ ] `go test ./api/...` green (no reader changes, no test changes
expected).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>1 parent 002c0cf commit 1cbd4a6
5 files changed
Lines changed: 353 additions & 6 deletions
File tree
- api/dbv1
- ddl
- functions
- migrations
- views
- sql
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
56 | 56 | | |
57 | 57 | | |
58 | 58 | | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8320 | 8320 | | |
8321 | 8321 | | |
8322 | 8322 | | |
8323 | | - | |
| 8323 | + | |
8324 | 8324 | | |
8325 | 8325 | | |
8326 | 8326 | | |
| |||
11956 | 11956 | | |
11957 | 11957 | | |
11958 | 11958 | | |
| 11959 | + | |
| 11960 | + | |
| 11961 | + | |
| 11962 | + | |
| 11963 | + | |
| 11964 | + | |
| 11965 | + | |
11959 | 11966 | | |
11960 | 11967 | | |
11961 | 11968 | | |
| |||
12889 | 12896 | | |
12890 | 12897 | | |
12891 | 12898 | | |
| 12899 | + | |
| 12900 | + | |
| 12901 | + | |
| 12902 | + | |
| 12903 | + | |
| 12904 | + | |
| 12905 | + | |
| 12906 | + | |
| 12907 | + | |
| 12908 | + | |
| 12909 | + | |
| 12910 | + | |
| 12911 | + | |
| 12912 | + | |
| 12913 | + | |
| 12914 | + | |
| 12915 | + | |
| 12916 | + | |
| 12917 | + | |
| 12918 | + | |
| 12919 | + | |
| 12920 | + | |
| 12921 | + | |
| 12922 | + | |
| 12923 | + | |
| 12924 | + | |
| 12925 | + | |
| 12926 | + | |
| 12927 | + | |
| 12928 | + | |
| 12929 | + | |
| 12930 | + | |
| 12931 | + | |
| 12932 | + | |
| 12933 | + | |
| 12934 | + | |
| 12935 | + | |
| 12936 | + | |
| 12937 | + | |
| 12938 | + | |
| 12939 | + | |
| 12940 | + | |
| 12941 | + | |
| 12942 | + | |
| 12943 | + | |
| 12944 | + | |
| 12945 | + | |
| 12946 | + | |
| 12947 | + | |
| 12948 | + | |
| 12949 | + | |
| 12950 | + | |
| 12951 | + | |
| 12952 | + | |
| 12953 | + | |
| 12954 | + | |
| 12955 | + | |
| 12956 | + | |
| 12957 | + | |
| 12958 | + | |
| 12959 | + | |
| 12960 | + | |
| 12961 | + | |
| 12962 | + | |
| 12963 | + | |
| 12964 | + | |
| 12965 | + | |
| 12966 | + | |
| 12967 | + | |
| 12968 | + | |
| 12969 | + | |
| 12970 | + | |
| 12971 | + | |
| 12972 | + | |
| 12973 | + | |
| 12974 | + | |
| 12975 | + | |
| 12976 | + | |
| 12977 | + | |
| 12978 | + | |
| 12979 | + | |
| 12980 | + | |
| 12981 | + | |
12892 | 12982 | | |
12893 | 12983 | | |
12894 | 12984 | | |
| |||
0 commit comments