// Copyright (C) 2024 Riften Labs AS // // This software is licensed under the GNU Affero General Public License (AGPL), version 3.0 or later. // A copy of the license can be found in the LICENSE file or at https://www.gnu.org/licenses/agpl-3.0.html use anyhow::Result; use rusqlite::Connection; pub enum CachedSort { ScoreDesc, ScoreAsc, NameAsc, NameDesc, SymbolAsc, SymbolDesc, TvlDesc, TvlAsc, VolumeDesc, VolumeAsc, Change24hDesc, Change24hAsc, Change7dDesc, Change7dAsc, PriceUsdDesc, PriceUsdAsc, Change24hUsdDesc, Change24hUsdAsc, Change7dUsdDesc, Change7dUsdAsc, Apy30dDesc, Apy30dAsc, } pub fn order_clause(sort: CachedSort) -> &'static str { match sort { CachedSort::ScoreDesc => "ORDER BY score DESC, trade_volume DESC, token_id ASC", CachedSort::ScoreAsc => "ORDER BY score ASC, trade_volume ASC, token_id ASC", CachedSort::NameAsc => "ORDER BY display_name COLLATE NOCASE ASC, token_id ASC", CachedSort::NameDesc => "ORDER BY display_name COLLATE NOCASE DESC, token_id ASC", CachedSort::SymbolAsc => "ORDER BY display_symbol COLLATE NOCASE ASC, token_id ASC", CachedSort::SymbolDesc => "ORDER BY display_symbol COLLATE NOCASE DESC, token_id ASC", CachedSort::TvlDesc => "ORDER BY tvl_sats DESC, token_id ASC", CachedSort::TvlAsc => "ORDER BY tvl_sats ASC, token_id ASC", CachedSort::VolumeDesc => "ORDER BY trade_volume DESC, token_id ASC", CachedSort::VolumeAsc => "ORDER BY trade_volume ASC, token_id ASC", CachedSort::Change24hDesc => "ORDER BY change_24h_bp DESC, token_id ASC", CachedSort::Change24hAsc => "ORDER BY change_24h_bp ASC, token_id ASC", CachedSort::Change7dDesc => "ORDER BY change_7d_bp DESC, token_id ASC", CachedSort::Change7dAsc => "ORDER BY change_7d_bp ASC, token_id ASC", CachedSort::PriceUsdDesc => "ORDER BY price_now_usd DESC, token_id ASC", CachedSort::PriceUsdAsc => "ORDER BY price_now_usd ASC, token_id ASC", CachedSort::Change24hUsdDesc => "ORDER BY change_24h_usd_bp DESC, token_id ASC", CachedSort::Change24hUsdAsc => "ORDER BY change_24h_usd_bp ASC, token_id ASC", CachedSort::Change7dUsdDesc => "ORDER BY change_7d_usd_bp DESC, token_id ASC", CachedSort::Change7dUsdAsc => "ORDER BY change_7d_usd_bp ASC, token_id ASC", CachedSort::Apy30dDesc => "ORDER BY apy_30d_bp DESC, token_id ASC", CachedSort::Apy30dAsc => "ORDER BY apy_30d_bp ASC, token_id ASC", } } pub fn create_cached_token_metrics_indexes(conn: &Connection) -> Result<()> { conn.execute_batch( r#" -- ====== cached_token_metrics: list/sort indexes ====== -- Score (matches: ORDER BY score DESC, trade_volume DESC, token_id ASC) CREATE INDEX IF NOT EXISTS idx_ctm_score_desc ON cached_token_metrics(score DESC, trade_volume DESC, token_id ASC); -- Score ASC CREATE INDEX IF NOT EXISTS idx_ctm_score_asc ON cached_token_metrics(score ASC, trade_volume ASC, token_id ASC); -- Price USD CREATE INDEX IF NOT EXISTS idx_ctm_price_usd_desc ON cached_token_metrics(price_now_usd DESC, token_id ASC); CREATE INDEX IF NOT EXISTS idx_ctm_price_usd_asc ON cached_token_metrics(price_now_usd ASC, token_id ASC); -- APY 30d CREATE INDEX IF NOT EXISTS idx_ctm_apy_desc ON cached_token_metrics(apy_30d_bp DESC, token_id ASC); CREATE INDEX IF NOT EXISTS idx_ctm_apy_asc ON cached_token_metrics(apy_30d_bp ASC, token_id ASC); -- Name/Symbol sorts (note the collation!) CREATE INDEX IF NOT EXISTS idx_ctm_name_asc ON cached_token_metrics(display_name COLLATE NOCASE ASC, token_id ASC); CREATE INDEX IF NOT EXISTS idx_ctm_name_desc ON cached_token_metrics(display_name COLLATE NOCASE DESC, token_id ASC); CREATE INDEX IF NOT EXISTS idx_ctm_symbol_asc ON cached_token_metrics(display_symbol COLLATE NOCASE ASC, token_id ASC); CREATE INDEX IF NOT EXISTS idx_ctm_symbol_desc ON cached_token_metrics(display_symbol COLLATE NOCASE DESC, token_id ASC); -- TVL & Volume CREATE INDEX IF NOT EXISTS idx_ctm_tvl_desc ON cached_token_metrics(tvl_sats DESC, token_id ASC); CREATE INDEX IF NOT EXISTS idx_ctm_tvl_asc ON cached_token_metrics(tvl_sats ASC, token_id ASC); CREATE INDEX IF NOT EXISTS idx_ctm_volume_desc ON cached_token_metrics(trade_volume DESC, token_id ASC); CREATE INDEX IF NOT EXISTS idx_ctm_volume_asc ON cached_token_metrics(trade_volume ASC, token_id ASC); -- % change (sats) CREATE INDEX IF NOT EXISTS idx_ctm_ch24_desc ON cached_token_metrics(change_24h_bp DESC, token_id ASC); CREATE INDEX IF NOT EXISTS idx_ctm_ch24_asc ON cached_token_metrics(change_24h_bp ASC, token_id ASC); CREATE INDEX IF NOT EXISTS idx_ctm_ch7d_desc ON cached_token_metrics(change_7d_bp DESC, token_id ASC); CREATE INDEX IF NOT EXISTS idx_ctm_ch7d_asc ON cached_token_metrics(change_7d_bp ASC, token_id ASC); -- % change (USD) CREATE INDEX IF NOT EXISTS idx_ctm_ch24_usd_desc ON cached_token_metrics(change_24h_usd_bp DESC, token_id ASC); CREATE INDEX IF NOT EXISTS idx_ctm_ch24_usd_asc ON cached_token_metrics(change_24h_usd_bp ASC, token_id ASC); CREATE INDEX IF NOT EXISTS idx_ctm_ch7d_usd_desc ON cached_token_metrics(change_7d_usd_bp DESC, token_id ASC); CREATE INDEX IF NOT EXISTS idx_ctm_ch7d_usd_asc ON cached_token_metrics(change_7d_usd_bp ASC, token_id ASC); -- ====== aggregation path (update job) ====== -- Speeds WHERE tx.effective_timestamp >= ? and join by txid CREATE INDEX IF NOT EXISTS idx_tx_effective_ts ON tx(effective_timestamp, txid); -- Speeds JOIN pool_history_entry(pool) → pool(creation_utxo) CREATE INDEX IF NOT EXISTS idx_pool_creation_utxo ON pool(creation_utxo, token_id); -- Speeds SUM(...) GROUP BY p.token_id over phe JOINs CREATE INDEX IF NOT EXISTS idx_phe_pool_txid ON pool_history_entry(pool, txid); -- Helpful for other token lookups CREATE INDEX IF NOT EXISTS idx_pool_token_id ON pool(token_id, creation_utxo); -- Analyze stats for the new indexes ANALYZE; PRAGMA optimize; "#, )?; Ok(()) } pub fn create_cached_token_metrics_table(conn: &Connection) -> Result<()> { conn.execute_batch( r#" CREATE TABLE IF NOT EXISTS cached_token_metrics ( token_id TEXT PRIMARY KEY, trade_volume INTEGER NOT NULL DEFAULT 0, tvl_sats INTEGER NOT NULL DEFAULT 0, tvl_tokens INTEGER NOT NULL DEFAULT 0, score INTEGER NOT NULL DEFAULT 0, score_rank INTEGER NOT NULL DEFAULT 0, display_name TEXT NOT NULL DEFAULT '', display_symbol TEXT NOT NULL DEFAULT '', -- sats per human token (already cached) price_now REAL NOT NULL DEFAULT 0, price_24h REAL NOT NULL DEFAULT 0, price_7d REAL NOT NULL DEFAULT 0, change_24h_bp INTEGER NOT NULL DEFAULT 0, change_7d_bp INTEGER NOT NULL DEFAULT 0, -- NEW: USD per human token (computed with time-matched BCH/USD oracle) price_now_usd REAL NOT NULL DEFAULT 0, price_24h_usd REAL NOT NULL DEFAULT 0, price_7d_usd REAL NOT NULL DEFAULT 0, change_24h_usd_bp INTEGER NOT NULL DEFAULT 0, change_7d_usd_bp INTEGER NOT NULL DEFAULT 0, apy_30d_bp INTEGER NOT NULL DEFAULT 0, last_trade_ts INTEGER, updated_at INTEGER NOT NULL ); "#, )?; Ok(()) }